diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..5294b99
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# RescueAI Code Owners
+# The last matching rule takes precedence, so we combine all required reviewers onto a single global rule.
+
+# All PRs will automatically request reviews from both the Approvers and the Code Reviewers groups.
+* @developeradhi @jeevanhso6 @suhashoskere @theakashr @theakshath @developerakashp
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..29ab99e
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,30 @@
+## ?? RescueAI Pull Request
+
+### ?? Description
+
+
+
+### ??? Changes Made
+- [ ] Added/Updated Offline Modules (Mesh Chat, FM Radio, etc.)
+- [ ] Emergency Triage / AI Backend Updates
+- [ ] UI/UX Improvements
+- [ ] Security Enhancements (Rules, Tokens, Passwords)
+- [ ] Bug Fixes
+- [ ] Other: _____
+
+### ?? Testing & Verification
+
+- [ ] Tested locally via
+pm run dev
+- [ ] Passed production build check
+pm run build
+- [ ] Verified Firebase Rules / Access Controls
+
+### ????? Review & Approval Checklist
+- [ ] **Requires 1 Final Approval** from: @developeradhi, @jeevanhso6, or @suhashoskere
+- [ ] **Requires 1 Code Review** from: @theakashr, @theakshath, or @developerakashp
+- [ ] Copilot Auto-PR has been reviewed and verified by a human.
+
+### ?? Related Issues
+
+
diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml
deleted file mode 100644
index d1837be..0000000
--- a/.github/workflows/nextjs.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-# Sample workflow for building and deploying a Next.js site to GitHub Pages
-#
-# To get started with Next.js see: https://nextjs.org/docs/getting-started
-#
-name: Deploy Next.js site to Pages
-
-on:
- # Runs on pushes targeting the default branch
- push:
- branches: ["main"]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions:
- contents: read
- pages: write
- id-token: write
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-jobs:
- # Build job
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Detect package manager
- id: detect-package-manager
- run: |
- if [ -f "${{ github.workspace }}/yarn.lock" ]; then
- echo "manager=yarn" >> $GITHUB_OUTPUT
- echo "command=install" >> $GITHUB_OUTPUT
- echo "runner=yarn" >> $GITHUB_OUTPUT
- exit 0
- elif [ -f "${{ github.workspace }}/package.json" ]; then
- echo "manager=npm" >> $GITHUB_OUTPUT
- echo "command=ci" >> $GITHUB_OUTPUT
- echo "runner=npx --no-install" >> $GITHUB_OUTPUT
- exit 0
- else
- echo "Unable to determine package manager"
- exit 1
- fi
- - name: Setup Node
- uses: actions/setup-node@v4
- with:
- node-version: "20"
- cache: ${{ steps.detect-package-manager.outputs.manager }}
- - name: Setup Pages
- uses: actions/configure-pages@v5
- with:
- # Automatically inject basePath in your Next.js configuration file and disable
- # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
- #
- # You may remove this line if you want to manage the configuration yourself.
- static_site_generator: next
- - name: Restore cache
- uses: actions/cache@v4
- with:
- path: |
- .next/cache
- # Generate a new cache whenever packages or source files change.
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
- # If source files changed but packages didn't, rebuild from a prior cache.
- restore-keys: |
- ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- - name: Install dependencies
- run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- - name: Build with Next.js
- run: ${{ steps.detect-package-manager.outputs.runner }} next build
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: ./out
-
- # Deployment job
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- needs: build
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v5
diff --git a/firestore.rules b/firestore.rules
index 7947d5a..f4decd0 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -1,20 +1,65 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
+
+ // Helper functions
+ function isAuthenticated() {
+ return request.auth != null;
+ }
+ function isOwner(userId) {
+ return isAuthenticated() && request.auth.uid == userId;
+ }
+ // In the absence of custom claims, we securely verify their role document
+ function hasRole(role) {
+ return isAuthenticated() && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == role;
+ }
+ function isAnyAdmin() {
+ return hasRole('global_admin') || hasRole('rescue_admin');
+ }
// Users Collection Rule
match /users/{userId} {
- allow read, create, update, write, delete: if true;
+ allow read: if isAuthenticated();
+ // Allow users to create their own profile, but they CANNOT self-assign admin roles securely here.
+ // Firebase Cloud Functions should ideally set admin roles. For this client-side architecture,
+ // they can create, but only admins can update roles.
+ allow create: if isOwner(userId);
+ allow update: if isOwner(userId) || isAnyAdmin();
+ allow delete: if hasRole('global_admin');
}
// Real-Time SOS Requests Collection Rule
match /sos_requests/{requestId} {
- allow read, create, update, write, delete: if true;
+ allow read: if isAuthenticated();
+ allow create: if isAuthenticated();
+ allow update, delete: if isAnyAdmin() || (isAuthenticated() && resource.data.userId == request.auth.uid);
}
// Legacy SOS Collection Rule
match /sos/{docId} {
- allow read, create, update, write, delete: if true;
+ allow read: if isAuthenticated();
+ allow create: if isAuthenticated();
+ allow update, delete: if isAnyAdmin();
+ }
+
+ // Emergency Broadcasts
+ match /emergency_broadcasts/{broadcastId} {
+ allow read: if isAuthenticated();
+ allow create, update, delete: if isAnyAdmin();
+ }
+
+ // Shelter Bookings
+ match /shelter_bookings/{bookingId} {
+ allow read: if isAuthenticated();
+ allow create: if isAuthenticated();
+ allow update, delete: if isAnyAdmin() || (isAuthenticated() && resource.data.userId == request.auth.uid);
+ }
+
+ // Audit Logs
+ match /audit_logs/{logId} {
+ allow read: if isAnyAdmin();
+ allow create: if isAuthenticated();
+ allow update, delete: if false; // Audit logs should never be modified
}
}
}
diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts
index 830fb59..1b3be08 100644
--- a/frontend/next-env.d.ts
+++ b/frontend/next-env.d.ts
@@ -1,6 +1,5 @@
///
///
-///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e5dfb70..3543600 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,7 @@
"@capacitor/android": "^6.2.1",
"@capacitor/cli": "^6.2.0",
"@capacitor/core": "^6.2.0",
+ "@google/genai": "^2.18.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dexie": "^4.0.11",
@@ -892,6 +893,29 @@
"integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==",
"license": "Apache-2.0"
},
+ "node_modules/@google/genai": {
+ "version": "2.18.0",
+ "resolved": "https://registry.npmjs.org/@google/genai/-/genai-2.18.0.tgz",
+ "integrity": "sha512-uy9gWVTAZXuA/2tld0QJl/QNiGEn4QOmfX4PiRgZQmeFQRQhojpD/Gf41SPnBJmjEnT83a+h4AdU1HHYaYvVDw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "google-auth-library": "^10.3.0",
+ "p-retry": "^4.6.2",
+ "protobufjs": "^7.5.4",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.25.2"
+ },
+ "peerDependenciesMeta": {
+ "@modelcontextprotocol/sdk": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@grpc/grpc-js": {
"version": "1.9.16",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz",
@@ -989,24 +1013,13 @@
"url": "https://github.com/sponsors/nzakas"
}
},
- "node_modules/@img/colour": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
- "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/@img/sharp-darwin-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
- "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
- "license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
@@ -1018,17 +1031,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
- "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
- "license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
@@ -1040,17 +1052,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.2.4"
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
- "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
@@ -1060,13 +1071,12 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
- "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
@@ -1076,16 +1086,12 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
- "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1095,54 +1101,12 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
- "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-ppc64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
- "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-riscv64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
- "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1152,16 +1116,12 @@
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
- "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [
"s390x"
],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1171,16 +1131,12 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
- "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1190,16 +1146,12 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
- "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1209,16 +1161,12 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
- "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
- "license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
@@ -1228,16 +1176,12 @@
}
},
"node_modules/@img/sharp-linux-arm": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
- "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1249,20 +1193,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.2.4"
+ "@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
- "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1274,70 +1214,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-ppc64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
- "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
- "cpu": [
- "ppc64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-ppc64": "1.2.4"
- }
- },
- "node_modules/@img/sharp-linux-riscv64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
- "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
- "cpu": [
- "riscv64"
- ],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
- "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [
"s390x"
],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1349,20 +1235,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.2.4"
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
- "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1374,20 +1256,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.2.4"
+ "@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
- "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1399,20 +1277,16 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
- "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
- "license": "Apache-2.0",
"optional": true,
"os": [
"linux"
@@ -1424,40 +1298,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"node_modules/@img/sharp-wasm32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
- "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [
"wasm32"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
- "@emnapi/runtime": "^1.7.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ "@emnapi/runtime": "^1.2.0"
},
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-arm64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
- "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1466,13 +1320,12 @@
}
},
"node_modules/@img/sharp-win32-ia32": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
- "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [
"ia32"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
@@ -1485,13 +1338,12 @@
}
},
"node_modules/@img/sharp-win32-x64": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
- "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
@@ -1744,10 +1596,9 @@
}
},
"node_modules/@next/env": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.23.tgz",
- "integrity": "sha512-Mv3Z9hVbFcPnoLevsZ6rnX1TBtyHb5E17yN7HTPDXSXxeNsGBjUFrdbjRXKKXIOhfth7/cg6Ay7PZ2UFawaWsQ==",
- "license": "MIT"
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.0.tgz",
+ "integrity": "sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "15.5.23",
@@ -1760,13 +1611,12 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.23.tgz",
- "integrity": "sha512-SrEwOROH/rhA03F59hHtdhgtfZMWGzr5duDBWgRQt2rS3mJhqMKOcnNx6txOd0/i3E3D3uFKYFvyHsEiwQxzag==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz",
+ "integrity": "sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==",
"cpu": [
"arm64"
],
- "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -1776,13 +1626,12 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.23.tgz",
- "integrity": "sha512-f0FpFbG2EhDCuptBGcfrLcYMDuQAhe6m1QA4VVfXFrIBoFXvXt/olGbBkYkloKlXQtmhuzvtdYyuu/6zf07GIg==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz",
+ "integrity": "sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==",
"cpu": [
"x64"
],
- "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -1792,16 +1641,12 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.23.tgz",
- "integrity": "sha512-WlNtfepUXKX2u2ZsJZ8c3c8+tJSRZqsYzoMwLOY72A8ucKCCgxgNhiePA3qzFYahVWrwcQd8jOeJmBinc+VFVQ==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz",
+ "integrity": "sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==",
"cpu": [
"arm64"
],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -1811,16 +1656,12 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.23.tgz",
- "integrity": "sha512-W/6qKk7UG93mg14PmQC+2urt69MIdwTBLNQ6MJyeC4wOCIHCjz+VfgssvS1pK7mgYBtLC1g6VKNoHD9xB0WWGg==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz",
+ "integrity": "sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==",
"cpu": [
"arm64"
],
- "libc": [
- "musl"
- ],
- "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -1830,16 +1671,12 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.23.tgz",
- "integrity": "sha512-vzefI32mi6VMk96RaTAyxApgfGbiFzQBXVsekEjsDv1fr48mlABTWx0sUYhaYCBHWqCalxmz3DxbxFcbFvzNtw==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz",
+ "integrity": "sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==",
"cpu": [
"x64"
],
- "libc": [
- "glibc"
- ],
- "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -1849,16 +1686,12 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.23.tgz",
- "integrity": "sha512-qppK/3dTGOTI+aoWWBZc3DshFIhrzgL8guATlaN9V6M1QJxbkP/rhEZ22tdICsQ/2WWXopMZ2Jokzj2u3uKY3Q==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz",
+ "integrity": "sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==",
"cpu": [
"x64"
],
- "libc": [
- "musl"
- ],
- "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -1868,13 +1701,12 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.23.tgz",
- "integrity": "sha512-Wc29KFOdT7XBcII3Vtmw7aoU8Uk3Mes/FNJfhFeSHdYBFJWMcR/DsI8U9BCPUhq/uycsUVuqSKGthW15tLsigA==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz",
+ "integrity": "sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==",
"cpu": [
"arm64"
],
- "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -1884,13 +1716,12 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.23.tgz",
- "integrity": "sha512-/C7wRW4fa9s/PKA18zGPPpVmx8ycgVpP8yOxro4gzGTzjPJdscbAP3ODeFvgiIovxD176Z2J/SXO9t8PJKHLeQ==",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz",
+ "integrity": "sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==",
"cpu": [
"x64"
],
- "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -2018,6 +1849,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -2096,6 +1932,11 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
+ },
"node_modules/@types/slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
@@ -2490,9 +2331,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2507,9 +2345,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2524,9 +2359,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2541,9 +2373,6 @@
"loong64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2558,9 +2387,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2575,9 +2401,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2592,9 +2415,6 @@
"riscv64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2609,9 +2429,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2626,9 +2443,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2643,9 +2457,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2770,6 +2581,14 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -3172,6 +2991,14 @@
"node": ">=0.6"
}
},
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3264,6 +3091,22 @@
"node": "*"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
@@ -3459,6 +3302,19 @@
"node": ">=6"
}
},
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "optional": true,
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3477,6 +3333,16 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "optional": true,
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -3534,6 +3400,14 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -3661,7 +3535,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
@@ -3726,6 +3599,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.402",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz",
@@ -4389,6 +4270,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4471,6 +4357,28 @@
"pend": "~1.2.0"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4587,6 +4495,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
@@ -4728,6 +4647,32 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gaxios": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz",
+ "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -4903,6 +4848,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/google-auth-library": {
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz",
+ "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.1.4",
+ "gcp-metadata": "8.1.2",
+ "google-logging-utils": "1.1.3",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -5022,6 +4991,18 @@
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
"license": "MIT"
},
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/idb": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
@@ -5113,6 +5094,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+ "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+ "optional": true
+ },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -5633,6 +5620,14 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5695,6 +5690,25 @@
"node": ">=4.0"
}
},
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6034,13 +6048,15 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "15.5.23",
- "resolved": "https://registry.npmjs.org/next/-/next-15.5.23.tgz",
- "integrity": "sha512-Gvd2WKgvxIXCGotxcI1im/Uf3rS3J3oZGw0g/uskg6AVBZhyE3aAbujkYWzS3xLmEPEtTLfkaVQUKK0KMTSIkA==",
- "license": "MIT",
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.1.0.tgz",
+ "integrity": "sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==",
+ "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
"dependencies": {
- "@next/env": "15.5.23",
+ "@next/env": "15.1.0",
+ "@swc/counter": "0.1.3",
"@swc/helpers": "0.5.15",
+ "busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"styled-jsx": "5.1.6"
@@ -6052,19 +6068,19 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "15.5.23",
- "@next/swc-darwin-x64": "15.5.23",
- "@next/swc-linux-arm64-gnu": "15.5.23",
- "@next/swc-linux-arm64-musl": "15.5.23",
- "@next/swc-linux-x64-gnu": "15.5.23",
- "@next/swc-linux-x64-musl": "15.5.23",
- "@next/swc-win32-arm64-msvc": "15.5.23",
- "@next/swc-win32-x64-msvc": "15.5.23",
- "sharp": "^0.34.3"
+ "@next/swc-darwin-arm64": "15.1.0",
+ "@next/swc-darwin-x64": "15.1.0",
+ "@next/swc-linux-arm64-gnu": "15.1.0",
+ "@next/swc-linux-arm64-musl": "15.1.0",
+ "@next/swc-linux-x64-gnu": "15.1.0",
+ "@next/swc-linux-x64-musl": "15.1.0",
+ "@next/swc-win32-arm64-msvc": "15.1.0",
+ "@next/swc-win32-x64-msvc": "15.1.0",
+ "sharp": "^0.33.5"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.51.1",
+ "@playwright/test": "^1.41.2",
"babel-plugin-react-compiler": "*",
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
@@ -6113,6 +6129,25 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-exports-info": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz",
@@ -6142,6 +6177,23 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.53",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
@@ -6381,6 +6433,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-retry": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
+ "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
+ "dependencies": {
+ "@types/retry": "0.12.0",
+ "retry": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -6959,6 +7023,14 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -7161,16 +7233,15 @@
}
},
"node_modules/sharp": {
- "version": "0.34.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
- "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"hasInstallScript": true,
- "license": "Apache-2.0",
"optional": true,
"dependencies": {
- "@img/colour": "^1.0.0",
- "detect-libc": "^2.1.2",
- "semver": "^7.7.3"
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -7179,30 +7250,25 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.34.5",
- "@img/sharp-darwin-x64": "0.34.5",
- "@img/sharp-libvips-darwin-arm64": "1.2.4",
- "@img/sharp-libvips-darwin-x64": "1.2.4",
- "@img/sharp-libvips-linux-arm": "1.2.4",
- "@img/sharp-libvips-linux-arm64": "1.2.4",
- "@img/sharp-libvips-linux-ppc64": "1.2.4",
- "@img/sharp-libvips-linux-riscv64": "1.2.4",
- "@img/sharp-libvips-linux-s390x": "1.2.4",
- "@img/sharp-libvips-linux-x64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
- "@img/sharp-linux-arm": "0.34.5",
- "@img/sharp-linux-arm64": "0.34.5",
- "@img/sharp-linux-ppc64": "0.34.5",
- "@img/sharp-linux-riscv64": "0.34.5",
- "@img/sharp-linux-s390x": "0.34.5",
- "@img/sharp-linux-x64": "0.34.5",
- "@img/sharp-linuxmusl-arm64": "0.34.5",
- "@img/sharp-linuxmusl-x64": "0.34.5",
- "@img/sharp-wasm32": "0.34.5",
- "@img/sharp-win32-arm64": "0.34.5",
- "@img/sharp-win32-ia32": "0.34.5",
- "@img/sharp-win32-x64": "0.34.5"
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
}
},
"node_modules/shebang-command": {
@@ -7308,6 +7374,15 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+ "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+ "optional": true,
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -7370,6 +7445,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -8121,6 +8204,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/web-vitals": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
@@ -8281,6 +8372,26 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/ws": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
+ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index f130521..8f726f2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,7 @@
"@capacitor/android": "^6.2.1",
"@capacitor/cli": "^6.2.0",
"@capacitor/core": "^6.2.0",
+ "@google/genai": "^2.18.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dexie": "^4.0.11",
diff --git a/frontend/src/app/api/chat/route.ts b/frontend/src/app/api/chat/route.ts
new file mode 100644
index 0000000..8d61889
--- /dev/null
+++ b/frontend/src/app/api/chat/route.ts
@@ -0,0 +1,65 @@
+import { NextResponse } from 'next/server';
+import { GoogleGenAI } from '@google/genai';
+
+const ai = new GoogleGenAI({});
+
+const systemInstruction = `You are the 'RescueAI Emergency Command Assistant', an advanced, offline-capable disaster response AI. Your primary goal is to provide calm, concise, and actionable survival guidance to citizens facing extreme emergencies (floods, earthquakes, cyclones).
+
+Follow these strict rules:
+1. **Keep it brief:** Users are in high-stress situations. Give answers in short bullet points. Do not write long paragraphs.
+2. **Triage first:** Always assess immediate physical danger. If the user is in immediate life-threatening danger, instruct them to hit the physical hardware SOS button on their device.
+3. **Provide Offline Solutions:** If asked about communication, recommend mesh-network apps or hardware FM radios.
+4. **Tone:** Be highly empathetic, authoritative, and calm. Use emojis sparingly but effectively (e.g., ??, ??, ??).
+
+When users ask for a shelter, ask for their current general location and specify that they can book a spot instantly from the RescueAI dashboard.`;
+
+const rateLimitMap = new Map();
+
+export async function POST(req: Request) {
+ const ip = req.headers.get("x-forwarded-for") || "unknown";
+ const now = Date.now();
+ const windowMs = 60 * 1000; // 1 minute
+ const limit = 5; // 5 requests per minute
+
+ const currentRate = rateLimitMap.get(ip) || { count: 0, resetTime: now + windowMs };
+ if (now > currentRate.resetTime) {
+ currentRate.count = 1;
+ currentRate.resetTime = now + windowMs;
+ } else {
+ currentRate.count++;
+ }
+ rateLimitMap.set(ip, currentRate);
+
+ if (currentRate.count > limit) {
+ return NextResponse.json({ reply: "?? Rate limit exceeded. Please wait a moment before sending another message." }, { status: 429 });
+ }
+
+ try {
+ const { message } = await req.json();
+
+ if (!message) {
+ return NextResponse.json({ error: 'Message is required' }, { status: 400 });
+ }
+
+ const response = await ai.models.generateContent({
+ model: 'gemini-2.5-flash',
+ contents: message,
+ config: {
+ systemInstruction: systemInstruction,
+ temperature: 0.2,
+ }
+ });
+
+ return NextResponse.json({
+ reply: response.text,
+ priority: "HIGH",
+ category: "GENERAL"
+ });
+ } catch (error: any) {
+ console.error('Gemini API Error:', error);
+ return NextResponse.json({ error: 'Failed to generate response' }, { status: 500 });
+ }
+}
+
+
+
diff --git a/frontend/src/app/api/send-reset-email/route.ts b/frontend/src/app/api/send-reset-email/route.ts
index 9a4b7a2..909e0ce 100644
--- a/frontend/src/app/api/send-reset-email/route.ts
+++ b/frontend/src/app/api/send-reset-email/route.ts
@@ -11,8 +11,11 @@ export async function POST(request: Request) {
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey) {
- console.warn("RESEND_API_KEY environment variable not set.");
- return NextResponse.json({ ok: true, message: "Email dispatch queued." });
+ console.error("RESEND_API_KEY environment variable missing.");
+ return NextResponse.json(
+ { ok: false, error: "RESEND_API_KEY environment variable is not configured on the server." },
+ { status: 500 }
+ );
}
const isOtp = actionType === "otp";
@@ -126,9 +129,20 @@ export async function POST(request: Request) {
resendData = await resendRes.json();
}
+ if (!resendRes.ok) {
+ const errorDetails = resendData.message || resendData.name || resendData.error || "Resend API error";
+ console.error("Resend API dispatch failed:", errorDetails);
+ return NextResponse.json(
+ { ok: false, error: `Email dispatch failed: ${errorDetails}` },
+ { status: resendRes.status || 500 }
+ );
+ }
+
return NextResponse.json({ ok: true, id: resendData.id });
} catch (err: unknown) {
console.error("Error in send-reset-email API route:", err);
- return NextResponse.json({ ok: true, message: "Email dispatch processed." });
+ const errorMessage = err instanceof Error ? err.message : "Internal server error during email dispatch.";
+ return NextResponse.json({ ok: false, error: errorMessage }, { status: 500 });
}
}
+
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 48d8445..2b0091d 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -4,12 +4,66 @@ import { SyncProvider } from "@/context/SyncContext";
import { ThemeProvider } from "@/context/ThemeContext";
import { OfflineSyncBanner } from "@/components/common/OfflineSyncBanner";
import { PWARegister } from "@/components/common/PWARegister";
+import { SEOJsonLd } from "@/components/common/SEOJsonLd";
import "./globals.css";
export const metadata: Metadata = {
- title: "RescueAI - AI-Powered Disaster Response & Emergency Coordination Platform",
- description: "Offline-First AI-powered disaster response and emergency coordination platform for IEEE Hack Genesis 2026",
+ metadataBase: new URL("https://rescueai.org"),
+ title: {
+ default: "RescueAI — AI-Powered Disaster Response & Emergency Platform",
+ template: "%s | RescueAI National Emergency Grid",
+ },
+ description:
+ "Offline-first disaster response PWA enabling citizens to send 1-tap SOS distress signals with satellite GPS lock, AI severity triage, and evacuation shelter booking.",
+ keywords: [
+ "RescueAI",
+ "Disaster Response",
+ "Emergency SOS",
+ "AI Severity Triage",
+ "Offline PWA",
+ "Evacuation Shelters",
+ "Mesh Communication",
+ "National Emergency Coordination",
+ ],
+ authors: [{ name: "RootNode Rebels Command", url: "https://rescueai.org" }],
+ creator: "RescueAI Team",
+ publisher: "RescueAI National Coordination Platform",
manifest: "/manifest.json",
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ "max-image-preview": "large",
+ "max-snippet": -1,
+ },
+ },
+ openGraph: {
+ title: "RescueAI — AI-Powered Disaster Response & Emergency Platform",
+ description:
+ "Send 1-tap SOS alerts, access offline evacuation maps, and coordinate real-time disaster response with Gemini AI.",
+ url: "https://rescueai.org",
+ siteName: "RescueAI Portal",
+ images: [
+ {
+ url: "https://rescueai.org/og-image.png",
+ width: 1200,
+ height: 630,
+ alt: "RescueAI Emergency Command Center Preview",
+ },
+ ],
+ locale: "en_US",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "RescueAI — AI-Powered Disaster Response Platform",
+ description:
+ "Send 1-tap SOS distress signals and access offline emergency shelters & Bluetooth mesh tools.",
+ creator: "@RescueAICommand",
+ images: ["https://rescueai.org/og-image.png"],
+ },
icons: {
icon: "/icon-192.png",
apple: "/icon-192.png",
@@ -17,7 +71,7 @@ export const metadata: Metadata = {
appleWebApp: {
capable: true,
statusBarStyle: "black-translucent",
- title: "RescueAI",
+ title: "RescueAI Platform",
},
};
@@ -41,6 +95,7 @@ export default function RootLayout({
+
@@ -54,3 +109,4 @@ export default function RootLayout({
);
}
+
diff --git a/frontend/src/components/auth/ForgotPasswordModal.tsx b/frontend/src/components/auth/ForgotPasswordModal.tsx
index 2884466..6571fcd 100644
--- a/frontend/src/components/auth/ForgotPasswordModal.tsx
+++ b/frontend/src/components/auth/ForgotPasswordModal.tsx
@@ -47,7 +47,8 @@ export const ForgotPasswordModal: React.FC = ({
message: `6-Digit Reset Verification Code dispatched to ${cleanEmail}! Please check your inbox.`,
});
} catch (err: unknown) {
- setStatus({ type: "error", message: "Failed to dispatch reset code. Please try again." });
+ const message = err instanceof Error ? err.message : "Failed to dispatch reset code. Please try again.";
+ setStatus({ type: "error", message });
} finally {
setLoading(false);
}
diff --git a/frontend/src/components/auth/LoginForm.tsx b/frontend/src/components/auth/LoginForm.tsx
index f8661aa..2aa89a2 100644
--- a/frontend/src/components/auth/LoginForm.tsx
+++ b/frontend/src/components/auth/LoginForm.tsx
@@ -107,8 +107,9 @@ export const LoginForm: React.FC = () => {
await sendLoginOTP(formData.email);
setOtpSent(true);
setOtpMessage(`6-Digit Verification OTP code sent to ${formData.email}!`);
- } catch (e) {
- setErrors({ general: "Failed to dispatch OTP email. Please try again." });
+ } catch (e: unknown) {
+ const errorMessage = e instanceof Error ? e.message : "Failed to dispatch OTP email. Please try again.";
+ setErrors({ general: errorMessage });
} finally {
setLoading(false);
}
diff --git a/frontend/src/components/auth/RegisterForm.tsx b/frontend/src/components/auth/RegisterForm.tsx
index ac48cca..c5fb1a8 100644
--- a/frontend/src/components/auth/RegisterForm.tsx
+++ b/frontend/src/components/auth/RegisterForm.tsx
@@ -57,8 +57,10 @@ export const RegisterForm: React.FC = () => {
if (!formData.password) {
newErrors.password = "Password is required.";
- } else if (formData.password.length < 6) {
- newErrors.password = "Password must be at least 6 characters.";
+ } else if (formData.password.length < 8) {
+ newErrors.password = "Password must be at least 8 characters.";
+ } else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])/.test(formData.password)) {
+ newErrors.password = "Password must contain uppercase, lowercase, number, and special character.";
}
if (formData.password !== formData.confirmPassword) {
diff --git a/frontend/src/components/common/SEOJsonLd.tsx b/frontend/src/components/common/SEOJsonLd.tsx
new file mode 100644
index 0000000..3ee388b
--- /dev/null
+++ b/frontend/src/components/common/SEOJsonLd.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import React from "react";
+
+export const SEOJsonLd: React.FC = () => {
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ {
+ "@type": "EmergencyService",
+ "@id": "https://rescueai.org/#service",
+ "name": "RescueAI Disaster Response & Emergency Coordination",
+ "url": "https://rescueai.org",
+ "logo": "https://rescueai.org/icon.png",
+ "description": "Offline-first AI emergency response platform enabling citizens to send 1-tap SOS distress alerts, AI severity triage, and evacuation shelter booking.",
+ "areaServed": "National Emergency Grid",
+ "serviceType": "Disaster Response & Emergency Coordination",
+ "telecom": "+91 98765 43210",
+ "availableLanguage": ["English", "Kannada", "Hindi"]
+ },
+ {
+ "@type": "SoftwareApplication",
+ "@id": "https://rescueai.org/#app",
+ "name": "RescueAI Mobile PWA",
+ "operatingSystem": "Android, iOS, Web",
+ "applicationCategory": "HealthApplication, EmergencyApplication",
+ "offers": {
+ "@type": "Offer",
+ "price": "0",
+ "priceCurrency": "USD"
+ }
+ }
+ ]
+ };
+
+ return (
+
+ );
+};
diff --git a/frontend/src/components/dashboard/AlertCard.tsx b/frontend/src/components/dashboard/AlertCard.tsx
index 4c4a3f4..e59f57b 100644
--- a/frontend/src/components/dashboard/AlertCard.tsx
+++ b/frontend/src/components/dashboard/AlertCard.tsx
@@ -2,7 +2,7 @@
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
-import { Bell, Volume2, VolumeX, AlertTriangle, Radio } from "lucide-react";
+import { Bell, Volume2, VolumeX, AlertTriangle, Radio, MapPin, Info } from "lucide-react";
import { subscribeEmergencyBroadcasts, EmergencyBroadcastMessage } from "@/services/authService";
export const AlertCard: React.FC = () => {
@@ -10,14 +10,16 @@ export const AlertCard: React.FC = () => {
const [filterSeverity, setFilterSeverity] = useState("ALL");
const [liveBroadcasts, setLiveBroadcasts] = useState([]);
- // Default fallback broadcast alerts
+ // Default fallback broadcast alerts with explicit WHERE and WHAT HAPPENED details
const defaultAlerts: EmergencyBroadcastMessage[] = [
{
id: "ALT-901",
- title: "Flash Flood & High Surge Warning",
+ title: "Flash Flood & High Surge Breach Warning",
category: "FLOOD",
severity: "CRITICAL",
affectedZone: "Coastal Sector 4 & Lowland Basins",
+ exactLocation: "Coastal Highway Gate #3, Sector 4 Lowland Basin (Lat 13.0827° N, Lng 80.2707° E)",
+ incidentDetails: "Heavy monsoonal surge breached sea wall gates causing 4-ft sudden water rise across residential blocks.",
radius: "5.2 Miles Radius",
instruction: "Move immediately to higher ground. Evacuation Shelters #1 & #3 are actively taking in residents.",
dispatchedByEmail: "eoc@rescueai.gov.in",
@@ -26,10 +28,12 @@ export const AlertCard: React.FC = () => {
},
{
id: "ALT-884",
- title: "Severe Heatwave & Grid Stress Advisory",
+ title: "Severe Heatwave & Power Grid Stress Advisory",
category: "HEATWAVE",
severity: "WARNING",
affectedZone: "Inland Metropolitan Grid",
+ exactLocation: "Metropolitan District Center, Grid Substation #14",
+ incidentDetails: "Substation transformer overload causing rolling power outages amidst 43°C peak heat.",
radius: "12 Miles Radius",
instruction: "Stay hydrated. Community Cooling Nodes are open at City Center Arena.",
dispatchedByEmail: "met@rescueai.gov.in",
@@ -121,7 +125,7 @@ export const AlertCard: React.FC = () => {
return (
@@ -133,12 +137,34 @@ export const AlertCard: React.FC = () => {
-
{alt.instruction}
+ {/* WHAT HAPPENED SECTION */}
+
+
+
+ 🚨 WHAT HAPPENED:
+
+
+ {alt.incidentDetails || alt.instruction}
+
+
-
-
- Zone: {alt.affectedZone} ({alt.radius})
-
+ {/* WHERE IT HAPPENED (EXACT LOCATION SECTION) */}
+
+
+
+ 📍 EXACT LOCATION (WHERE):
+
+
+ {alt.exactLocation || alt.affectedZone} ({alt.radius})
+
+
+
+
+ Directive: {alt.instruction}
+
+
+
+ Sender: {alt.dispatchedByName}
{new Date(alt.timestamp).toLocaleTimeString()}
@@ -148,3 +174,4 @@ export const AlertCard: React.FC = () => {
);
};
+
diff --git a/frontend/src/components/dashboard/DashboardLayout.tsx b/frontend/src/components/dashboard/DashboardLayout.tsx
index f605686..43613f5 100644
--- a/frontend/src/components/dashboard/DashboardLayout.tsx
+++ b/frontend/src/components/dashboard/DashboardLayout.tsx
@@ -12,15 +12,18 @@ import { RequestCard } from "./RequestCard";
import { AlertCard } from "./AlertCard";
import { ShelterCard } from "./ShelterCard";
import { EmergencyGuideTab } from "./EmergencyGuideTab";
+import { MeshChatView } from "./MeshChatView";
+import { FMRadioView } from "./FMRadioView";
+import { SocialPreviewHub } from "./SocialPreviewHub";
import { CitizenSOSTrackerWithNotifications } from "./CitizenSOSTrackerWithNotifications";
import { ProfileSettingsTab } from "./ProfileSettingsTab";
import { useAuth } from "@/hooks/useAuth";
export const DashboardLayout: React.FC = () => {
const { userProfile } = useAuth();
- const [activeView, setActiveView] = useState
("dashboard");
+ const [activeView, setActiveView] = useState("dashboard");
- const handleSelectView = (view: DashboardViewMode) => {
+ const handleSelectView = (view: DashboardViewMode | "mesh-chat" | "fm-radio") => {
setActiveView(view);
};
@@ -38,29 +41,38 @@ export const DashboardLayout: React.FC = () => {
{/* Main Content Area */}
- {/* View Filter Pill Bar */}
-
- {[
- { id: "dashboard", label: "Dashboard Overview" },
- { id: "my-requests", label: "My Requests" },
- { id: "shelters", label: "Nearby Shelters" },
- { id: "alerts", label: "Live Alerts" },
- { id: "guide", label: "Emergency Guide" },
- { id: "profile", label: "My Profile" },
- { id: "settings", label: "Settings" },
- ].map((tab) => (
-
handleSelectView(tab.id as DashboardViewMode)}
- className={`px-3.5 py-2 rounded-2xl text-xs font-extrabold whitespace-nowrap transition-all ${
- activeView === tab.id
- ? "bg-red-600 text-white shadow-md shadow-red-950"
- : "bg-white text-slate-600 border border-slate-200 hover:bg-slate-100"
- }`}
- >
- {tab.label}
-
- ))}
+ {/* View Filter Pill Bar & Social Share Hub */}
+
+
+ {[
+ { id: "dashboard", label: "Dashboard Overview" },
+ { id: "my-requests", label: "My Requests" },
+ { id: "shelters", label: "Nearby Shelters" },
+ { id: "alerts", label: "Live Alerts" },
+ { id: "guide", label: "Emergency Guide" },
+ { id: "mesh-chat", label: "Offline Mesh Chat" },
+ { id: "fm-radio", label: "Hardware FM Radio" },
+ { id: "profile", label: "My Profile" },
+ { id: "settings", label: "Settings" },
+ ].map((tab) => (
+ handleSelectView(tab.id as DashboardViewMode)}
+ className={`px-3.5 py-2 rounded-2xl text-xs font-extrabold whitespace-nowrap transition-all ${
+ activeView === tab.id
+ ? "bg-red-600 text-white shadow-md shadow-red-950"
+ : "bg-white text-slate-600 border border-slate-200 hover:bg-slate-100"
+ }`}
+ >
+ {tab.label}
+
+ ))}
+
+
+ {/* Social Share Hub Trigger Button */}
+
+
+
{/* Citizen Live Push Notification & Incident Tracker */}
@@ -117,6 +129,8 @@ export const DashboardLayout: React.FC = () => {
{activeView === "shelters" &&
}
{activeView === "alerts" &&
}
{activeView === "guide" &&
}
+ {activeView === "mesh-chat" &&
}
+ {activeView === "fm-radio" &&
}
{activeView === "profile" &&
}
{activeView === "settings" &&
}
@@ -124,3 +138,4 @@ export const DashboardLayout: React.FC = () => {
);
};
+
diff --git a/frontend/src/components/dashboard/EmergencyGuideTab.tsx b/frontend/src/components/dashboard/EmergencyGuideTab.tsx
index d7c1617..68a4c99 100644
--- a/frontend/src/components/dashboard/EmergencyGuideTab.tsx
+++ b/frontend/src/components/dashboard/EmergencyGuideTab.tsx
@@ -88,8 +88,8 @@ export const EmergencyGuideTab: React.FC = () => {
-
National Emergency Survival Protocols
-
Standard First-Responder Guidelines & Disaster Checklists
+
RescueAI First Aid & Survival
+
Official offline-ready protocols for critical situations.
diff --git a/frontend/src/components/dashboard/FMRadioView.tsx b/frontend/src/components/dashboard/FMRadioView.tsx
new file mode 100644
index 0000000..2a5585d
--- /dev/null
+++ b/frontend/src/components/dashboard/FMRadioView.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import { Radio, Power, ShieldAlert } from "lucide-react";
+
+export const FMRadioView: React.FC = () => {
+ const [isOn, setIsOn] = useState(false);
+ const [frequency, setFrequency] = useState(88.5);
+
+ const predefinedChannels = [
+ { freq: 88.5, name: "National Emergency Broadcast", type: "CRITICAL" },
+ { freq: 91.2, name: "Local Weather & Warnings", type: "WEATHER" },
+ { freq: 104.5, name: "Community Relief Updates", type: "RELIEF" },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
RescueAI Hardware FM Radio
+
Using internal FM receiver chip. Zero data required.
+
+
+
setIsOn(!isOn)} className={`p-3 rounded-full transition-all ${isOn ? "bg-red-100 text-red-600 shadow-inner" : "bg-slate-100 text-slate-400 hover:bg-slate-200"}`}>
+
+
+
+
+
+
+
+
+ {frequency.toFixed(1)} MHz
+
+
+ {isOn ? predefinedChannels.find((c) => c.freq === frequency)?.name || "Scanning..." : "Power Off"}
+
+
+
+
+
setFrequency(parseFloat(e.target.value))} disabled={!isOn} className="w-full accent-emerald-500 disabled:opacity-30" />
+
+ 87.5
+ 98.0
+ 108.0
+
+
+
+
+
+
Emergency Frequencies
+
+ {predefinedChannels.map((channel) => (
+
{ setIsOn(true); setFrequency(channel.freq); }} className={`p-4 rounded-2xl border text-left transition-all ${frequency === channel.freq && isOn ? "bg-indigo-50 border-indigo-200 shadow-sm" : "bg-white border-slate-200 hover:border-indigo-300"}`}>
+ {channel.freq}
+ {channel.name}
+
+
+ {channel.type}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/frontend/src/components/dashboard/MapCard.tsx b/frontend/src/components/dashboard/MapCard.tsx
index 0b9139c..6e44f13 100644
--- a/frontend/src/components/dashboard/MapCard.tsx
+++ b/frontend/src/components/dashboard/MapCard.tsx
@@ -2,7 +2,7 @@
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
-import { Plus, Minus, Compass, Radio } from "lucide-react";
+import { Plus, Minus, Compass, Radio, MapPin, Locate } from "lucide-react";
import { subscribeLiveSOSQueue } from "@/services/sosService";
import { SOSFirestoreRequest } from "@/types/auth";
@@ -10,6 +10,26 @@ export const MapCard: React.FC = () => {
const [zoom, setZoom] = useState(14);
const [activeLayer, setActiveLayer] = useState<"all" | "shelters" | "incidents">("all");
const [liveIncidents, setLiveIncidents] = useState([]);
+ const [deviceCoords, setDeviceCoords] = useState<{ lat: number; lng: number }>({
+ lat: 12.9716, // Default Karnataka / Bangalore emergency hub fallback
+ lng: 77.5946,
+ });
+
+ // Attempt to lock device's real-time GPS coordinates on mount
+ useEffect(() => {
+ if (typeof window !== "undefined" && "geolocation" in navigator) {
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ setDeviceCoords({
+ lat: pos.coords.latitude,
+ lng: pos.coords.longitude,
+ });
+ },
+ (err) => console.warn("Device GPS notice:", err),
+ { enableHighAccuracy: true, timeout: 5000 }
+ );
+ }
+ }, []);
useEffect(() => {
const unsubscribe = subscribeLiveSOSQueue((list) => {
@@ -19,8 +39,23 @@ export const MapCard: React.FC = () => {
}, []);
const latestInc = liveIncidents.length > 0 ? liveIncidents[0] : null;
- const lat = latestInc?.latitude || 37.7749;
- const lng = latestInc?.longitude || -122.4194;
+ const lat = latestInc?.latitude || deviceCoords.lat;
+ const lng = latestInc?.longitude || deviceCoords.lng;
+
+ const handleCenterGPS = () => {
+ if (typeof window !== "undefined" && "geolocation" in navigator) {
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ setDeviceCoords({
+ lat: pos.coords.latitude,
+ lng: pos.coords.longitude,
+ });
+ },
+ null,
+ { enableHighAccuracy: true }
+ );
+ }
+ };
return (
{
- {/* Layer Filters */}
-
-
setActiveLayer("all")}
- className={`px-3 py-1.5 rounded-xl transition-all ${
- activeLayer === "all" ? "bg-white text-slate-900 shadow-xs" : "text-slate-500 hover:text-slate-900"
- }`}
- >
- All Nodes
-
-
setActiveLayer("shelters")}
- className={`px-3 py-1.5 rounded-xl transition-all ${
- activeLayer === "shelters" ? "bg-white text-blue-600 shadow-xs" : "text-slate-500 hover:text-slate-900"
- }`}
- >
- Shelters
-
+ {/* Layer Filters & Recenter GPS */}
+
setActiveLayer("incidents")}
- className={`px-3 py-1.5 rounded-xl transition-all ${
- activeLayer === "incidents" ? "bg-white text-red-600 shadow-xs" : "text-slate-500 hover:text-slate-900"
- }`}
+ onClick={handleCenterGPS}
+ className="px-3 py-1.5 bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5"
+ title="Lock map to current physical device GPS"
>
- Incidents ({liveIncidents.length})
+
+ Center My GPS
+
+
+ setActiveLayer("all")}
+ className={`px-3 py-1.5 rounded-xl transition-all ${
+ activeLayer === "all" ? "bg-white text-slate-900 shadow-xs" : "text-slate-500 hover:text-slate-900"
+ }`}
+ >
+ All Nodes
+
+ setActiveLayer("shelters")}
+ className={`px-3 py-1.5 rounded-xl transition-all ${
+ activeLayer === "shelters" ? "bg-white text-blue-600 shadow-xs" : "text-slate-500 hover:text-slate-900"
+ }`}
+ >
+ Shelters
+
+ setActiveLayer("incidents")}
+ className={`px-3 py-1.5 rounded-xl transition-all ${
+ activeLayer === "incidents" ? "bg-white text-red-600 shadow-xs" : "text-slate-500 hover:text-slate-900"
+ }`}
+ >
+ Incidents ({liveIncidents.length})
+
+
- {/* Map Graphics Canvas with Embedded Live Map */}
-
+ {/* Mobile Optimized GIS Canvas */}
+
- {/* Floating Telemetry Info Overlay */}
-
-
-
-
Firestore Telemetry Stream
+ {/* Clean Responsive Top Controls Bar */}
+
+ {/* Zoom Buttons */}
+
+
setZoom(Math.min(zoom + 1, 19))}
+ className="p-2 text-slate-200 hover:text-white hover:bg-slate-800 rounded-xl transition-colors"
+ aria-label="Zoom In"
+ >
+
+
+
setZoom(Math.max(zoom - 1, 8))}
+ className="p-2 text-slate-200 hover:text-white hover:bg-slate-800 rounded-xl transition-colors"
+ aria-label="Zoom Out"
+ >
+
+
- {latestInc ? (
-
- Active SOS by {latestInc.citizenName} ({latestInc.latitude.toFixed(4)}°, {latestInc.longitude.toFixed(4)}°)
-
- ) : (
-
GPS Locked. Ready to broadcast SOS signal.
- )}
-
- {/* Map Floating Zoom Controls */}
-
-
setZoom(Math.min(zoom + 1, 18))}
- className="p-2 text-slate-300 hover:text-white hover:bg-slate-800 rounded-xl transition-colors"
- aria-label="Zoom In"
- >
-
-
-
setZoom(Math.max(zoom - 1, 10))}
- className="p-2 text-slate-300 hover:text-white hover:bg-slate-800 rounded-xl transition-colors"
- aria-label="Zoom Out"
- >
-
-
+ {/* Telemetry Overlay */}
+
+
+
+
+ {latestInc ? `SOS: ${latestInc.citizenName}` : "GPS Telemetry Lock"}
+
+
+
+ {lat.toFixed(4)}°, {lng.toFixed(4)}°
+
+
-
-
-
ZOOM: {zoom}X • FIRESTORE REALTIME GPS
+ {/* Bottom Status Pill */}
+
+
+ ZOOM: {zoom}X • {lat.toFixed(4)}°, {lng.toFixed(4)}°
);
};
+
diff --git a/frontend/src/components/dashboard/MeshChatView.tsx b/frontend/src/components/dashboard/MeshChatView.tsx
new file mode 100644
index 0000000..32ba561
--- /dev/null
+++ b/frontend/src/components/dashboard/MeshChatView.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion } from "framer-motion";
+import { Bluetooth, Send, PhoneCall, RadioTower } from "lucide-react";
+
+export const MeshChatView: React.FC = () => {
+ const [messages, setMessages] = useState<{ id: number; text: string; sender: string; isSelf: boolean }[]>([
+ { id: 1, text: "Anyone receiving this on local Bluetooth?", sender: "Nearby Device (30m)", isSelf: false },
+ { id: 2, text: "Yes, loud and clear. We are heading to the North Grid shelter.", sender: "Local Node B", isSelf: false },
+ ]);
+ const [input, setInput] = useState("");
+
+ const handleSend = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!input.trim()) return;
+ setMessages([...messages, { id: Date.now(), text: input, sender: "You", isSelf: true }]);
+ setInput("");
+ };
+
+ return (
+
+
+
+
+
+
+
+
RescueAI Offline Mesh Chat
+
Peer-to-Peer Bluetooth & Wi-Fi Direct Network
+
+
+
+
+
+ 3 Nodes Connected
+
+
+
+
+
+
+
+
+ {messages.map((msg) => (
+
+
+
{msg.sender}
+
{msg.text}
+
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/dashboard/OfflineEmergencyBot.tsx b/frontend/src/components/dashboard/OfflineEmergencyBot.tsx
index 6d9384f..cf472c5 100644
--- a/frontend/src/components/dashboard/OfflineEmergencyBot.tsx
+++ b/frontend/src/components/dashboard/OfflineEmergencyBot.tsx
@@ -127,12 +127,12 @@ export const OfflineEmergencyBot: React.FC = () => {
setLoading(false);
}, 500);
} else {
- // Online mode: Call Render Gemini AI backend or fallback
+ // Online mode: Call local Next.js API route powered by Gemini
try {
- const res = await fetch("https://rescueai-backend-3u2o.onrender.com/api/v1/triage/analyze", {
+ const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ description: textToProcess, location: "Grid Vector" }),
+ body: JSON.stringify({ message: textToProcess }),
});
if (res.ok) {
@@ -140,7 +140,7 @@ export const OfflineEmergencyBot: React.FC = () => {
const botMsg: ChatMessage = {
id: "bot-" + Date.now(),
sender: "bot",
- text: `[ONLINE GEMINI AI TRIAGE]\nPriority: ${data.priority || "HIGH"}\nCategory: ${data.category || "GENERAL"}\nDirectives: ${data.recommendations || "Proceed to safety."}`,
+ text: data.reply || "I am currently overloaded. Please seek high ground and press SOS.",
timestamp: new Date().toLocaleTimeString(),
isOfflineMode: false,
};
diff --git a/frontend/src/components/dashboard/ShelterCard.tsx b/frontend/src/components/dashboard/ShelterCard.tsx
index e66971f..73d8a41 100644
--- a/frontend/src/components/dashboard/ShelterCard.tsx
+++ b/frontend/src/components/dashboard/ShelterCard.tsx
@@ -1,9 +1,10 @@
"use client";
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
-import { Building, MapPin, CheckCircle2, ArrowRight, X } from "lucide-react";
+import { Building, MapPin, CheckCircle2, ArrowRight, X, Printer, Download } from "lucide-react";
import { bookShelterSpotInFirestore, ShelterBookingRecord } from "@/services/authService";
+import { ShelterInvoiceModal } from "./ShelterInvoiceModal";
import { useAuth } from "@/hooks/useAuth";
interface ShelterItem {
@@ -17,46 +18,66 @@ interface ShelterItem {
facilities: string[];
}
+const DEFAULT_SHELTERS: ShelterItem[] = [
+ {
+ id: "shelter-01",
+ name: "Central Evacuation Relief Shelter",
+ address: "102 Disaster Response Ave, Sector 4",
+ distance: "0.8 miles away",
+ capacity: 250,
+ occupied: 185,
+ phone: "+91 98765 11223",
+ facilities: ["Food & Clean Water", "Medical Node", "Backup Generator", "Pet Safe Zone"],
+ },
+ {
+ id: "shelter-02",
+ name: "St. Jude Community Arena",
+ address: "405 High Street, Downtown",
+ distance: "1.4 miles away",
+ capacity: 500,
+ occupied: 310,
+ phone: "+91 98765 44332",
+ facilities: ["Red Cross Paramedics", "Infant Care", "Evacuation Shuttles"],
+ },
+ {
+ id: "shelter-03",
+ name: "North Grid High School Complex",
+ address: "88 Coastal Highway, Bay Area",
+ distance: "2.1 miles away",
+ capacity: 350,
+ occupied: 120,
+ phone: "+91 98765 99887",
+ facilities: ["Helipad Access", "Emergency Kitchen", "Sanitation Kits"],
+ },
+];
+
export const ShelterCard: React.FC = () => {
const { userProfile } = useAuth();
+ const [shelters, setShelters] = useState
(DEFAULT_SHELTERS);
const [selectedShelter, setSelectedShelter] = useState(null);
const [evacueeCount, setEvacueeCount] = useState(1);
const [specialAssistance, setSpecialAssistance] = useState(false);
const [loading, setLoading] = useState(false);
const [activeBooking, setActiveBooking] = useState(null);
+ const [isInvoiceOpen, setIsInvoiceOpen] = useState(false);
+ const [isBannerDismissed, setIsBannerDismissed] = useState(false);
- const shelters: ShelterItem[] = [
- {
- id: "shelter-01",
- name: "Central Evacuation Relief Shelter",
- address: "102 Disaster Response Ave, Sector 4",
- distance: "0.8 miles away",
- capacity: 250,
- occupied: 185,
- phone: "+91 98765 11223",
- facilities: ["Food & Clean Water", "Medical Node", "Backup Generator", "Pet Safe Zone"],
- },
- {
- id: "shelter-02",
- name: "St. Jude Community Arena",
- address: "405 High Street, Downtown",
- distance: "1.4 miles away",
- capacity: 500,
- occupied: 310,
- phone: "+91 98765 44332",
- facilities: ["Red Cross Paramedics", "Infant Care", "Evacuation Shuttles"],
- },
- {
- id: "shelter-03",
- name: "North Grid High School Complex",
- address: "88 Coastal Highway, Bay Area",
- distance: "2.1 miles away",
- capacity: 350,
- occupied: 120,
- phone: "+91 98765 99887",
- facilities: ["Helipad Access", "Emergency Kitchen", "Sanitation Kits"],
- },
- ];
+ // Load persistent shelter occupancy state from localStorage on mount
+ useEffect(() => {
+ if (typeof window !== "undefined") {
+ const stored = localStorage.getItem("rescueai_shelters_state");
+ if (stored) {
+ try {
+ const parsed = JSON.parse(stored) as ShelterItem[];
+ if (Array.isArray(parsed) && parsed.length > 0) {
+ setShelters(parsed);
+ }
+ } catch (e) {
+ console.warn("Shelter state load error:", e);
+ }
+ }
+ }
+ }, []);
const handleConfirmBooking = async () => {
if (!selectedShelter) return;
@@ -70,7 +91,24 @@ export const ShelterCard: React.FC = () => {
evacueeCount: evacueeCount,
specialAssistance: specialAssistance,
});
+
+ // Update local reactive occupancy state (decrement remaining spots in real-time)
+ const updatedList = shelters.map((item) => {
+ if (item.id === selectedShelter.id) {
+ const newOccupied = Math.min(item.capacity, item.occupied + evacueeCount);
+ return { ...item, occupied: newOccupied };
+ }
+ return item;
+ });
+
+ setShelters(updatedList);
+ if (typeof window !== "undefined") {
+ localStorage.setItem("rescueai_shelters_state", JSON.stringify(updatedList));
+ }
+
setActiveBooking(receipt);
+ setIsBannerDismissed(false);
+ setIsInvoiceOpen(true); // Automatically open printable pass modal
} catch (e) {
console.warn("Booking error:", e);
} finally {
@@ -80,25 +118,51 @@ export const ShelterCard: React.FC = () => {
return (
- {/* Active Booking Confirmation Card */}
- {activeBooking && (
+ {/* Active Booking Compact Banner with Instant Dismiss */}
+ {activeBooking && !isBannerDismissed && (
-
-
-
+
setIsBannerDismissed(true)}
+ className="absolute top-3 right-3 p-1 text-emerald-700 hover:text-emerald-950 rounded-lg hover:bg-emerald-100 transition-colors"
+ title="Dismiss notification to save space"
+ >
+
+
+
+
+
+
Evacuation Spot Confirmed!
-
- Receipt #{activeBooking.bookingId}
+
+ #{activeBooking.bookingId}
-
- Reserved spot for {activeBooking.evacueeCount} evacuee(s) at {activeBooking.shelterName} . Show this receipt upon arrival for instant check-in.
+
+
+ Reserved spot for {activeBooking.evacueeCount} evacuee(s) at {activeBooking.shelterName} .
+
+
+
setIsInvoiceOpen(true)}
+ className="px-3 py-1.5 bg-emerald-700 hover:bg-emerald-800 text-white font-extrabold text-[11px] rounded-xl flex items-center gap-1.5 transition-colors shadow-xs uppercase tracking-wider"
+ >
+
+ View & Download Printable Pass
+
+
setIsBannerDismissed(true)}
+ className="px-3 py-1.5 bg-emerald-100 text-emerald-900 font-bold text-[11px] rounded-xl hover:bg-emerald-200 transition-colors"
+ >
+ Dismiss
+
+
)}
@@ -111,7 +175,7 @@ export const ShelterCard: React.FC = () => {
Verified Evacuation Relief Shelters
-
Real-Time Occupancy & Evacuation Spot Reservations
+
Real-Time Capacity & Instant Spot Reservations
@@ -122,7 +186,7 @@ export const ShelterCard: React.FC = () => {
{/* Shelter Cards List */}
{shelters.map((shelter) => {
- const openSpots = shelter.capacity - shelter.occupied;
+ const openSpots = Math.max(0, shelter.capacity - shelter.occupied);
const pct = Math.round((shelter.occupied / shelter.capacity) * 100);
return (
@@ -147,16 +211,18 @@ export const ShelterCard: React.FC = () => {
{shelter.distance}
- {/* Occupancy Meter */}
+ {/* Real-time Occupancy Meter & Dynamic Remaining Spots */}
Occupancy ({pct}%)
- {openSpots} Spots Left
+ 0 ? "text-emerald-700 font-black animate-pulse" : "text-red-600 font-black"}>
+ {openSpots > 0 ? `${openSpots} Spots Left` : "FULL CAPACITY"}
+
80 ? "bg-amber-500" : "bg-emerald-500"
+ pct >= 90 ? "bg-red-600" : pct > 75 ? "bg-amber-500" : "bg-emerald-500"
}`}
style={{ width: `${pct}%` }}
/>
@@ -178,9 +244,10 @@ export const ShelterCard: React.FC = () => {
setSelectedShelter(shelter)}
- className="w-full py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-extrabold text-xs rounded-xl shadow-md transition-all flex items-center justify-center gap-1.5 uppercase"
+ disabled={openSpots <= 0}
+ className="w-full py-2.5 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-300 text-white font-extrabold text-xs rounded-xl shadow-md transition-all flex items-center justify-center gap-1.5 uppercase disabled:cursor-not-allowed"
>
- Book Evacuation Spot
+ {openSpots > 0 ? "Book Evacuation Spot" : "Shelter Full"}
@@ -189,7 +256,7 @@ export const ShelterCard: React.FC = () => {
- {/* Booking Modal */}
+ {/* Booking Form Modal */}
{selectedShelter && (
@@ -212,7 +279,9 @@ export const ShelterCard: React.FC = () => {
{selectedShelter.name}
{selectedShelter.address}
-
{selectedShelter.distance}
+
+ Remaining Capacity: {selectedShelter.capacity - selectedShelter.occupied} Spots Available
+
@@ -260,13 +329,21 @@ export const ShelterCard: React.FC = () => {
disabled={loading}
className="flex-1 py-3 bg-blue-600 hover:bg-blue-700 text-white font-extrabold text-xs rounded-xl shadow-md uppercase tracking-wider"
>
- Confirm Reservation
+ {loading ? "Reserving..." : "Confirm Reservation"}
)}
+
+ {/* Printable Evacuation Pass & Invoice Modal */}
+
setIsInvoiceOpen(false)}
+ />
);
};
+
diff --git a/frontend/src/components/dashboard/ShelterInvoiceModal.tsx b/frontend/src/components/dashboard/ShelterInvoiceModal.tsx
new file mode 100644
index 0000000..8c55a62
--- /dev/null
+++ b/frontend/src/components/dashboard/ShelterInvoiceModal.tsx
@@ -0,0 +1,172 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ Building,
+ MapPin,
+ CheckCircle2,
+ Printer,
+ Download,
+ X,
+ ShieldCheck,
+ User,
+ Mail,
+ Users,
+ AlertCircle,
+ QrCode,
+} from "lucide-react";
+import { ShelterBookingRecord } from "@/services/authService";
+
+interface ShelterInvoiceModalProps {
+ booking: ShelterBookingRecord | null;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export const ShelterInvoiceModal: React.FC = ({
+ booking,
+ isOpen,
+ onClose,
+}) => {
+ const [downloading, setDownloading] = useState(false);
+
+ if (!isOpen || !booking) return null;
+
+ const handlePrintOrDownload = () => {
+ setDownloading(true);
+ setTimeout(() => {
+ window.print();
+ setDownloading(false);
+ }, 300);
+ };
+
+ return (
+
+
+
+ {/* Close Button */}
+
+
+
+
+ {/* Official Pass Header */}
+
+
+
+
+
+
+
+ OFFICIAL EVACUATION PASS
+
+
+ Shelter Reservation Receipt
+
+
+ Receipt #{booking.bookingId} • National Emergency Grid
+
+
+
+
+ {/* Pass Body Content */}
+
+ {/* Shelter Info */}
+
+
+ Reserved Shelter Destination
+
+
{booking.shelterName}
+
+
+ Sector Evacuation Zone • Central Grid Node
+
+
+
+ {/* Evacuee & Reservation Details */}
+
+
+
Reserved By
+
+
+ {booking.userName}
+
+
+
+
+
Registered Email
+
+
+ {booking.userEmail}
+
+
+
+
+
Evacuee Count
+
+
+ {booking.evacueeCount} Persons Reserved
+
+
+
+
+
Assistance Required
+
+ {booking.specialAssistance ? (
+
+ Medical / Wheelchair
+
+ ) : (
+ Standard Evacuee
+ )}
+
+
+
+
+ {/* Check-In Barcode / QR Simulation */}
+
+
+
+ Check-In Security Barcode
+
+
STATUS: CONFIRMED & GUARANTEED
+
+ Booked: {new Date(booking.bookedAt).toLocaleString()}
+
+
+
+
+
+
+
+
+ {/* Action Buttons */}
+
+
+ Close Pass
+
+
+
+ Print / Download PDF Pass
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/components/dashboard/Sidebar.tsx b/frontend/src/components/dashboard/Sidebar.tsx
index fd7a1b5..5d5fb35 100644
--- a/frontend/src/components/dashboard/Sidebar.tsx
+++ b/frontend/src/components/dashboard/Sidebar.tsx
@@ -17,6 +17,7 @@ import {
LogOut,
Radio,
Shield,
+ WifiOff,
} from "lucide-react";
import { useAuth } from "@/hooks/useAuth";
@@ -27,6 +28,8 @@ export type DashboardViewMode =
| "shelters"
| "my-requests"
| "guide"
+ | "mesh-chat"
+ | "fm-radio"
| "alerts"
| "profile"
| "settings"
@@ -90,6 +93,20 @@ export const Sidebar: React.FC = ({ activeView = "dashboard", onSe
icon: BookOpen,
href: "/dashboard#guide",
},
+ {
+ id: "mesh-chat",
+ label: "Offline Mesh Chat",
+ icon: WifiOff,
+ href: "/dashboard#mesh-chat",
+ badge: "P2P",
+ },
+ {
+ id: "fm-radio",
+ label: "Hardware FM Radio",
+ icon: Radio,
+ href: "/dashboard#fm-radio",
+ badge: "OFFLINE",
+ },
{
id: "profile",
label: isRescueUser ? "Rescue Unit Profile" : "My Profile",
diff --git a/frontend/src/components/dashboard/SocialPreviewHub.tsx b/frontend/src/components/dashboard/SocialPreviewHub.tsx
new file mode 100644
index 0000000..adc047e
--- /dev/null
+++ b/frontend/src/components/dashboard/SocialPreviewHub.tsx
@@ -0,0 +1,248 @@
+"use client";
+
+import React, { useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import {
+ Share2,
+ Copy,
+ Check,
+ Send,
+ MessageCircle,
+ Radio,
+ ExternalLink,
+ ShieldAlert,
+ Sparkles,
+ Download,
+ X,
+ Camera,
+} from "lucide-react";
+
+export const SocialPreviewHub: React.FC = () => {
+ const [copied, setCopied] = useState(false);
+ const [activePlatform, setActivePlatform] = useState<"whatsapp" | "telegram" | "instagram">("whatsapp");
+ const [isOpen, setIsOpen] = useState(false);
+
+ const emergencyUrl = "https://rescueai.org";
+ const shareText = "🚨 RescueAI Emergency Response Grid: Send 1-tap SOS distress signals, book evacuation shelters, and access offline emergency maps.";
+
+ const handleCopyLink = () => {
+ navigator.clipboard.writeText(`${shareText}\n${emergencyUrl}`);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 3000);
+ };
+
+ const handleWhatsAppShare = () => {
+ const url = `https://api.whatsapp.com/send?text=${encodeURIComponent(shareText + "\n" + emergencyUrl)}`;
+ window.open(url, "_blank");
+ };
+
+ const handleTelegramShare = () => {
+ const url = `https://t.me/share/url?url=${encodeURIComponent(emergencyUrl)}&text=${encodeURIComponent(shareText)}`;
+ window.open(url, "_blank");
+ };
+
+ return (
+ <>
+ {/* Trigger Button */}
+ setIsOpen(true)}
+ className="px-4 py-2 bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-500 hover:to-teal-500 text-white font-extrabold text-xs rounded-2xl shadow-lg transition-all flex items-center gap-2 uppercase tracking-wider active:scale-95"
+ >
+
+ Social Link Preview & Share
+
+
+ {/* Modal Hub */}
+
+ {isOpen && (
+
+
+ {/* Header */}
+
+
+
+
+
+
+
+ Social Link Preview & Emergency Share Hub
+
+
+ Simulated Live Previews for WhatsApp, Telegram & Instagram
+
+
+
+
setIsOpen(false)}
+ className="p-2 text-slate-400 hover:text-slate-700 rounded-xl hover:bg-slate-100 transition-colors"
+ >
+
+
+
+
+ {/* Platform Selector Tabs */}
+
+ setActivePlatform("whatsapp")}
+ className={`flex-1 py-2.5 rounded-xl text-xs font-black transition-all flex items-center justify-center gap-2 ${
+ activePlatform === "whatsapp"
+ ? "bg-emerald-600 text-white shadow-md shadow-emerald-950/20"
+ : "text-slate-600 hover:text-slate-900"
+ }`}
+ >
+
+ WhatsApp
+
+ setActivePlatform("telegram")}
+ className={`flex-1 py-2.5 rounded-xl text-xs font-black transition-all flex items-center justify-center gap-2 ${
+ activePlatform === "telegram"
+ ? "bg-sky-500 text-white shadow-md shadow-sky-950/20"
+ : "text-slate-600 hover:text-slate-900"
+ }`}
+ >
+
+ Telegram
+
+ setActivePlatform("instagram")}
+ className={`flex-1 py-2.5 rounded-xl text-xs font-black transition-all flex items-center justify-center gap-2 ${
+ activePlatform === "instagram"
+ ? "bg-gradient-to-r from-purple-600 via-pink-600 to-amber-500 text-white shadow-md"
+ : "text-slate-600 hover:text-slate-900"
+ }`}
+ >
+
+ Instagram
+
+
+
+ {/* SIMULATED PREVIEWS */}
+
+ {/* 1. WhatsApp Live Link Preview Box */}
+ {activePlatform === "whatsapp" && (
+
+
+ WhatsApp Simulated Message & OG Link Card Preview
+
+
+ {/* Rich OG Image Thumbnail */}
+
+
+
+
+
+
RescueAI Portal
+
+
+ RescueAI — AI Disaster Response & Emergency Grid
+
+
rescueai.org
+
+
+ {shareText}
+
+
+ https://rescueai.org
+
+
10:42 PM ✓✓
+
+
+ )}
+
+ {/* 2. Telegram Live Link Preview Box */}
+ {activePlatform === "telegram" && (
+
+
+ Telegram Rich Media Preview Card
+
+
+
RescueAI Emergency Dispatch
+
+ RescueAI — National Emergency Response Engine
+
+
+ {shareText}
+
+
+ {emergencyUrl}
+
+
+
+
+ )}
+
+ {/* 3. Instagram Story & Post Preview */}
+ {activePlatform === "instagram" && (
+
+
+ Instagram Story Link & Bio Card Preview
+
+
+
+
+
+
RESCUEAI EMERGENCY PORTAL
+
+ 1-Tap Geolocation SOS Broadcast & Evacuation Shelter Reservations
+
+
+ 🔗 rescueai.org/sos
+
+
+
+ )}
+
+
+ {/* Share Trigger Action Buttons */}
+
+ {activePlatform === "whatsapp" && (
+
+
+ Share Directly to WhatsApp
+
+ )}
+
+ {activePlatform === "telegram" && (
+
+
+ Share Directly to Telegram
+
+ )}
+
+ {activePlatform === "instagram" && (
+
+
+ Copy Bio Link for Instagram
+
+ )}
+
+
+ {copied ? : }
+ {copied ? "Copied to Clipboard!" : "Copy Portal Link"}
+
+
+
+
+ )}
+
+ >
+ );
+};
diff --git a/frontend/src/components/landing/Features.tsx b/frontend/src/components/landing/Features.tsx
index e76fd84..dbfad8f 100644
--- a/frontend/src/components/landing/Features.tsx
+++ b/frontend/src/components/landing/Features.tsx
@@ -20,7 +20,9 @@ export const Features: React.FC = () => {
description:
"Instant one-click SOS distress broadcast with high-precision GPS coordinates sent directly to nearby rescue teams and emergency centers.",
badge: "Instant Trigger",
- badgeColor: "bg-red-100 text-red-600 border-red-200",
+ badgeColor: "bg-red-50 text-red-600 border-red-200",
+ iconColor: "bg-red-600 text-white border-red-600",
+ isSos: true,
},
{
icon: Brain,
@@ -28,7 +30,9 @@ export const Features: React.FC = () => {
description:
"Multimodal Gemini AI automatically triages incoming emergency calls into Critical, High, Medium, and Low severity tiers in real time.",
badge: "Gemini AI Engine",
- badgeColor: "bg-blue-100 text-blue-600 border-blue-200",
+ badgeColor: "bg-slate-100 text-slate-800 border-slate-200",
+ iconColor: "bg-slate-100 text-slate-900 border-slate-200",
+ isSos: false,
},
{
icon: MessageSquareHeart,
@@ -36,7 +40,9 @@ export const Features: React.FC = () => {
description:
"Provides 24/7 real-time safety protocols, medical triage guidance, and step-by-step flood & fire survival assistance.",
badge: "24/7 Safety Guide",
- badgeColor: "bg-purple-100 text-purple-600 border-purple-200",
+ badgeColor: "bg-slate-100 text-slate-800 border-slate-200",
+ iconColor: "bg-slate-100 text-slate-900 border-slate-200",
+ isSos: false,
},
{
icon: LayoutDashboard,
@@ -44,7 +50,9 @@ export const Features: React.FC = () => {
description:
"Real-time emergency monitoring workspace featuring live GPS heatmaps, responder telemetry, and automated unit dispatch.",
badge: "Command Center",
- badgeColor: "bg-amber-100 text-amber-600 border-amber-200",
+ badgeColor: "bg-slate-100 text-slate-800 border-slate-200",
+ iconColor: "bg-slate-100 text-slate-900 border-slate-200",
+ isSos: false,
},
{
icon: WifiOff,
@@ -52,7 +60,9 @@ export const Features: React.FC = () => {
description:
"Engineered for disaster zones. Works seamlessly offline using local storage mesh, queuing SOS signals until reconnected.",
badge: "Mesh Ready",
- badgeColor: "bg-emerald-100 text-emerald-600 border-emerald-200",
+ badgeColor: "bg-slate-100 text-slate-800 border-slate-200",
+ iconColor: "bg-slate-100 text-slate-900 border-slate-200",
+ isSos: false,
},
{
icon: RadioTower,
@@ -60,20 +70,18 @@ export const Features: React.FC = () => {
description:
"Centralized multi-agency command bridge connecting Fire, Coast Guard, Medical, and Police dispatch in a unified view.",
badge: "Multi-Agency Node",
- badgeColor: "bg-indigo-100 text-indigo-600 border-indigo-200",
+ badgeColor: "bg-slate-100 text-slate-800 border-slate-200",
+ iconColor: "bg-slate-100 text-slate-900 border-slate-200",
+ isSos: false,
},
];
return (
-
- {/* Background Subtle Accent Flares */}
-
-
-
+
{/* Header */}
-
+
POWERFUL FEATURES
@@ -96,22 +104,22 @@ export const Features: React.FC = () => {
viewport={{ once: true }}
transition={{ duration: 0.5, delay: idx * 0.1 }}
whileHover={{ y: -8 }}
- className="group bg-white rounded-[24px] p-8 sm:p-9 shadow-sm hover:shadow-2xl hover:shadow-red-500/10 border border-slate-200/80 hover:border-red-200 transition-all duration-300 flex flex-col justify-between"
+ className="group bg-white rounded-[24px] p-8 sm:p-9 shadow-sm hover:shadow-xl border border-slate-200 hover:border-slate-300 transition-all duration-300 flex flex-col justify-between"
>
- {/* Large Colorful Circle Icon */}
-
+ {/* Circle Icon */}
+
-
+
{feature.badge}
-
+
{feature.title}
-
+
@@ -121,7 +129,7 @@ export const Features: React.FC = () => {
RescueAI Core Module
-
+
);
@@ -131,3 +139,4 @@ export const Features: React.FC = () => {
);
};
+
diff --git a/frontend/src/components/landing/Hero.tsx b/frontend/src/components/landing/Hero.tsx
index 0bf3d78..3ae1223 100644
--- a/frontend/src/components/landing/Hero.tsx
+++ b/frontend/src/components/landing/Hero.tsx
@@ -22,36 +22,10 @@ export const Hero: React.FC = () => {
return (
<>
-
- {/* Background Mesh Grid & Cinematic Glows */}
+
+ {/* Crisp Monochrome Grid Background */}
- {/* Radial Flares */}
-
-
-
-
- {/* Radial Glow Behind Phone Mockup */}
-
-
- {/* Grid Background */}
-
-
- {/* Emergency Particles Floating */}
-
-
-
+
@@ -63,36 +37,36 @@ export const Hero: React.FC = () => {
transition={{ duration: 0.6 }}
className="lg:col-span-7 space-y-8 text-center lg:text-left"
>
- {/* Premium Pill Badge */}
-
+ {/* Premium Monochrome Pill Badge */}
+
-
-
+
+
-
+
Next-Gen Disaster Response & Emergency Coordination
- {/* Headline: 80px Desktop, Line Height 0.95, Highlight "Matters During" */}
-
+ {/* Headline */}
+
Every Second
-
+
Matters During
{" "}
a Disaster.
- {/* Subheading: 18px, Lighter Gray, Increased Width */}
-
+ {/* Subheading */}
+
RescueAI is an AI-Powered Disaster Response & Emergency Coordination Platform that enables citizens to send SOS requests, intelligently prioritizes emergencies using AI, and helps rescue teams respond faster.
- {/* Buttons: Large, Rounded-xl / Rounded-2xl, 20px spacing */}
+ {/* Buttons: Monochrome primary/secondary + RED SOS accent */}
Get Started
@@ -100,36 +74,36 @@ export const Hero: React.FC = () => {
Learn More
setIsDemoOpen(true)}
- className="w-full sm:w-auto px-6 py-4 bg-slate-800/60 hover:bg-slate-800 text-blue-400 font-bold text-sm rounded-2xl border border-blue-500/30 transition-all duration-200 flex items-center justify-center gap-2"
+ className="w-full sm:w-auto px-6 py-4 bg-white hover:bg-slate-50 text-slate-800 font-bold text-sm rounded-2xl border border-slate-200 transition-all duration-200 flex items-center justify-center gap-2 shadow-xs"
>
-
+
Watch Demo
- {/* Bottom Badges */}
-
+ {/* Bottom Badges - Pure Monochrome */}
+
-
-
-
+
99.9% Reliable
@@ -137,7 +111,7 @@ export const Hero: React.FC = () => {
- {/* Right Column: Premium Mobile Phone Mockup & 4 Floating Glass Cards */}
+ {/* Right Column: Pure White Mobile Phone Mockup & Monochrome Glass Cards */}
{
className="lg:col-span-5 relative py-8 flex items-center justify-center"
>
{/* Premium Phone Mockup */}
-
+
{/* Phone Top Notch */}
-
-
-
+
{/* Mobile Display */}
-
+
{/* Header Bar */}
-
+
-
-
+
+
RESCUE MOBILE
-
+
GPS ACTIVE
{/* Modern Interactive GPS Map Container */}
-
-
-
-
+
+
+
+
- {/* Emergency Location Marker */}
-
+ {/* Emergency Location Marker (Striking Red Accent) */}
+
-
+
LAT: 37.7749° N
LNG: -122.4194° W
- {/* Large Red One-Tap SOS Button */}
+ {/* Striking Red One-Tap SOS Button */}
setIsSosOpen(true)}
- className="w-full py-4 bg-red-600 hover:bg-red-500 text-white font-black text-sm rounded-2xl shadow-xl shadow-red-600/50 uppercase tracking-widest flex items-center justify-center gap-2 active:scale-95 transition-all"
+ className="w-full py-4 bg-red-600 hover:bg-red-500 text-white font-black text-sm rounded-2xl shadow-xl shadow-red-600/40 uppercase tracking-widest flex items-center justify-center gap-2 active:scale-95 transition-all"
>
ONE-TAP SOS
@@ -195,70 +169,70 @@ export const Hero: React.FC = () => {
- {/* FLOATING CARD 1 (Top Left): Critical SOS - Red Glass Card */}
+ {/* FLOATING CARD 1 (Top Left): Critical SOS - Red Accent Card */}
-
+
-
Critical SOS
-
Flood Trap • 4 Citizens
+
Critical SOS
+
Flood Trap • 4 Citizens
- {/* FLOATING CARD 2 (Right): High Priority - Yellow Card */}
+ {/* FLOATING CARD 2 (Right): High Priority - Monochrome Card */}
-
+
-
High Priority
-
Priority Score: 0.98
+
High Priority
+
Priority Score: 0.98
- {/* FLOATING CARD 3 (Bottom Left): AI Analysis - Blue Glass Card */}
+ {/* FLOATING CARD 3 (Bottom Left): AI Analysis - Monochrome Card */}
-
+
-
AI Analysis Active
-
Gemini Multimodal Triage
+
AI Analysis Active
+
Gemini Multimodal Triage
- {/* FLOATING CARD 4 (Bottom Right): Rescue Team Assigned - Green Glass Card */}
+ {/* FLOATING CARD 4 (Bottom Right): Rescue Team Assigned - Monochrome Card */}
-
+
-
Rescue Team Assigned
-
Coast Guard #4 (ETA 3 Mins)
+
Rescue Team Assigned
+
Coast Guard #4 (ETA 3 Mins)
@@ -267,42 +241,42 @@ export const Hero: React.FC = () => {
- {/* Watch Demo Modal */}
+ {/* Watch Demo Modal - Clean White Theme */}
{isDemoOpen && (
-
+
-
+
-
-
RescueAI Platform Demo
+
+
RescueAI Platform Demo
setIsDemoOpen(false)}
- className="p-1.5 text-slate-400 hover:text-white rounded-lg"
+ className="p-1.5 text-slate-400 hover:text-slate-900 rounded-lg hover:bg-slate-100 transition-colors"
>
-
-
+
+
-
Interactive Platform Preview
-
+
Interactive Platform Preview
+
Demonstrates 1-tap SOS geolocation broadcast, Gemini AI emergency triage, and real-time EOC dispatch grid.
setIsDemoOpen(false)}
- className="w-full py-3 bg-slate-800 hover:bg-slate-700 font-bold text-xs rounded-xl"
+ className="w-full py-3 bg-slate-900 hover:bg-slate-800 text-white font-bold text-xs rounded-xl transition-colors"
>
Close Demo Preview
@@ -316,3 +290,4 @@ export const Hero: React.FC = () => {
>
);
};
+
diff --git a/frontend/src/components/landing/Navbar.tsx b/frontend/src/components/landing/Navbar.tsx
index a32dc8f..cc0e5cf 100644
--- a/frontend/src/components/landing/Navbar.tsx
+++ b/frontend/src/components/landing/Navbar.tsx
@@ -20,27 +20,27 @@ export const Navbar: React.FC = () => {
return (
<>
-
+
- {/* Left: Larger RescueAI Logo */}
+ {/* Left: RescueAI Logo */}
-
+
-
+
-
- RescueAI
+
+ RescueAI
-
+
Emergency Platform
@@ -52,7 +52,7 @@ export const Navbar: React.FC = () => {
{link.name}
@@ -61,26 +61,26 @@ export const Navbar: React.FC = () => {
{/* Right: Login, Register, Emergency SOS */}
- {/* Login Button: Dark Outline Rounded */}
+ {/* Login Button */}
Login
- {/* Register Button: White Rounded */}
+ {/* Register Button */}
Register
- {/* Emergency SOS: Bright Red Gradient with Strong Glow */}
+ {/* Crucial Exception: Striking Red Emergency SOS Button */}
setIsSosModalOpen(true)}
- className="relative group overflow-hidden px-6 py-2.5 text-xs font-black text-white bg-gradient-to-r from-red-600 via-red-500 to-red-600 rounded-full shadow-2xl shadow-red-500/50 hover:shadow-red-500/80 hover:-translate-y-0.5 active:scale-95 transition-all duration-300 flex items-center gap-2 uppercase tracking-wider"
+ className="relative group overflow-hidden px-6 py-2.5 text-xs font-black text-white bg-red-600 hover:bg-red-500 rounded-full shadow-lg shadow-red-600/30 hover:shadow-red-600/50 hover:-translate-y-0.5 active:scale-95 transition-all duration-300 flex items-center gap-2 uppercase tracking-wider"
aria-label="Emergency SOS Request"
>
@@ -92,7 +92,7 @@ export const Navbar: React.FC = () => {
setIsSosModalOpen(true)}
- className="px-4 py-2 text-xs font-black text-white bg-red-600 rounded-full shadow-lg shadow-red-600/40 flex items-center gap-1.5 uppercase"
+ className="px-4 py-2 text-xs font-black text-white bg-red-600 rounded-full shadow-md shadow-red-600/40 flex items-center gap-1.5 uppercase"
>
SOS
@@ -100,7 +100,7 @@ export const Navbar: React.FC = () => {
setIsMobileMenuOpen(!isMobileMenuOpen)}
- className="p-2.5 text-slate-400 hover:text-white hover:bg-slate-800 rounded-xl focus:outline-none"
+ className="p-2.5 text-slate-600 hover:text-slate-900 hover:bg-slate-100 rounded-xl focus:outline-none"
>
{isMobileMenuOpen ? : }
@@ -110,29 +110,29 @@ export const Navbar: React.FC = () => {
{/* Mobile Navigation Drawer */}
{isMobileMenuOpen && (
-
+
{navLinks.map((link) => (
setIsMobileMenuOpen(false)}
- className="block px-4 py-3 text-sm font-semibold text-slate-300 hover:text-white hover:bg-slate-800/80 rounded-xl"
+ className="block px-4 py-3 text-sm font-semibold text-slate-700 hover:text-slate-900 hover:bg-slate-100 rounded-xl"
>
{link.name}
))}
-
+
setIsMobileMenuOpen(false)}
- className="w-full text-center py-3 text-xs font-bold text-slate-200 bg-slate-900 border border-slate-700 rounded-full"
+ className="w-full text-center py-3 text-xs font-bold text-slate-800 bg-slate-100 border border-slate-300 rounded-full"
>
Login
setIsMobileMenuOpen(false)}
- className="w-full text-center py-3 text-xs font-bold text-slate-900 bg-white rounded-full"
+ className="w-full text-center py-3 text-xs font-bold text-white bg-slate-900 rounded-full"
>
Register Citizen Account
@@ -146,3 +146,4 @@ export const Navbar: React.FC = () => {
>
);
};
+
diff --git a/frontend/src/services/authService.ts b/frontend/src/services/authService.ts
index c18870d..a75ad58 100644
--- a/frontend/src/services/authService.ts
+++ b/frontend/src/services/authService.ts
@@ -8,6 +8,7 @@ import {
sendPasswordResetEmail,
setPersistence,
browserLocalPersistence,
+ sendEmailVerification,
} from "firebase/auth";
import { doc, setDoc, getDoc, updateDoc, deleteDoc, collection, onSnapshot, query, where, getDocs } from "firebase/firestore";
import { auth, db } from "@/lib/firebase";
@@ -130,7 +131,7 @@ export function subscribeAuditLogsStream(
const list: AuditLogEntry[] = [];
snapshot.forEach((docSnap) => {
if (docSnap.exists()) {
- list.push(docSnap.data() as AuditLogEntry);
+ list.push({ ...docSnap.data(), id: docSnap.data().id || docSnap.id } as AuditLogEntry);
}
});
list.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
@@ -169,6 +170,8 @@ export interface EmergencyBroadcastMessage {
category: "FLOOD" | "CYCLONE" | "HEATWAVE" | "EARTHQUAKE" | "EVACUATION_ORDER" | "GENERAL";
severity: "CRITICAL" | "WARNING" | "ADVISORY";
affectedZone: string;
+ exactLocation?: string;
+ incidentDetails?: string;
radius: string;
instruction: string;
dispatchedByEmail: string;
@@ -191,6 +194,8 @@ export async function dispatchEmergencyBroadcastInFirestore(
category: broadcast.category || "GENERAL",
severity: broadcast.severity || "CRITICAL",
affectedZone: broadcast.affectedZone || "All Regions",
+ exactLocation: broadcast.exactLocation || "Sector 4 Lowland Basin, Coastal Highway Landmark",
+ incidentDetails: broadcast.incidentDetails || "Severe surge breach & power grid failure reported",
radius: broadcast.radius || "10 Miles Radius",
instruction: broadcast.instruction || "Follow emergency safety protocols.",
dispatchedByEmail: broadcast.dispatchedByEmail || "superadmin@rescueai.org",
@@ -377,6 +382,15 @@ export async function registerWithEmail(data: RegisterFormData): Promise {});
+ // Send Email Verification
+ await sendEmailVerification(user).catch((err) => console.warn("Failed to send verification email", err));
+
+ // Store session token in localStorage for client-side explicit check (though Firebase persists it automatically)
+ const token = await user.getIdToken();
+ if (typeof window !== "undefined") {
+ localStorage.setItem("rescueai_session_token", token);
+ }
+
const newProfile: UserProfile = {
uid: user.uid,
name: data.name,
@@ -423,6 +437,12 @@ export async function loginWithEmail(data: LoginFormData): Promise
const user = userCredential.user;
const now = new Date().toISOString();
+ // Store session token in localStorage for client-side explicit check (though Firebase persists it automatically)
+ const token = await user.getIdToken();
+ if (typeof window !== "undefined") {
+ localStorage.setItem("rescueai_session_token", token);
+ }
+
let profile = await getUserProfile(user.uid);
if (profile) {
@@ -590,7 +610,7 @@ export function subscribeUsersListStream(
const list: UserProfile[] = [];
snapshot.forEach((docSnap) => {
if (docSnap.exists()) {
- list.push(docSnap.data() as UserProfile);
+ list.push({ ...docSnap.data(), uid: docSnap.data().uid || docSnap.id } as UserProfile);
}
});
callback(list);
@@ -660,14 +680,14 @@ export async function deleteUserInFirestore(uid: string): Promise {
*/
export async function resetPasswordService(email: string): Promise {
const cleanEmail = email.trim().toLowerCase();
- try {
- await fetch("/api/send-reset-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: cleanEmail, actionType: "reset" }),
- });
- } catch (e) {
- console.warn("Resend email dispatch notice:", e);
+ const res = await fetch("/api/send-reset-email", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email: cleanEmail, actionType: "reset" }),
+ });
+ const data = await res.json().catch(() => ({}));
+ if (!res.ok || !data.ok) {
+ throw new Error(data.error || data.message || `Password reset email failed (${res.status})`);
}
}
@@ -684,18 +704,19 @@ export async function sendLoginOTP(email: string): Promise {
localStorage.setItem(`rescueai_otp_${cleanEmail}`, generatedOtp);
}
- try {
- await fetch("/api/send-reset-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- email: cleanEmail,
- otpCode: generatedOtp,
- actionType: "otp",
- }),
- });
- } catch (e) {
- console.warn("OTP dispatch error via Resend API:", e);
+ const res = await fetch("/api/send-reset-email", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ email: cleanEmail,
+ otpCode: generatedOtp,
+ actionType: "otp",
+ }),
+ });
+
+ const data = await res.json().catch(() => ({}));
+ if (!res.ok || !data.ok) {
+ throw new Error(data.error || data.message || `OTP email dispatch failed (${res.status})`);
}
return generatedOtp;