From 0741b4e03c17923ec5608ccf9943770ba43e45e0 Mon Sep 17 00:00:00 2001 From: Marcio Bevervanso Date: Tue, 17 Feb 2026 17:49:42 -0300 Subject: [PATCH] feat: complete dashboard redesign, payment history, and backend limits --- .gitignore | 24 + README.md | 20 + foodsnap-main.rar | Bin 0 -> 165196 bytes foodsnap.rar | Bin 0 -> 169437 bytes index.html | 143 + metadata.json | 5 + package-lock.json | 3286 +++++++++++++++++ package.json | 26 + src/App.tsx | 327 ++ src/components/coach/AnalysisSection.tsx | 59 + src/components/coach/CoachResult.tsx | 255 ++ src/components/coach/CoachWizard.tsx | 405 ++ src/components/coach/DietSection.tsx | 115 + src/components/coach/Shared.tsx | 62 + src/components/coach/WorkoutSection.tsx | 100 + .../coach/pdf/PdfAnalysisCompact.tsx | 102 + src/components/coach/pdf/PdfDietCompact.tsx | 181 + src/components/coach/pdf/PdfShared.tsx | 34 + .../coach/pdf/PdfWorkoutCompact.tsx | 119 + src/components/common/HistoryCard.tsx | 67 + src/components/common/MacroBadge.tsx | 16 + src/components/common/StatCard.tsx | 27 + src/components/dashboard/DashboardCoach.tsx | 149 + src/components/dashboard/DashboardHistory.tsx | 81 + .../dashboard/DashboardOverview.tsx | 189 + .../dashboard/DashboardSubscription.tsx | 181 + src/components/landing/CoachHighlight.tsx | 110 + src/components/landing/FAQ.tsx | 58 + src/components/landing/Features.tsx | 213 ++ src/components/landing/Footer.tsx | 117 + src/components/landing/Header.tsx | 218 ++ src/components/landing/Hero.tsx | 360 ++ src/components/landing/HowItWorks.tsx | 64 + src/components/landing/Pricing.tsx | 184 + src/components/landing/Testimonials.tsx | 67 + src/components/layout/MobileNav.tsx | 45 + src/components/layout/Sidebar.tsx | 172 + src/components/modals/CalculatorsModal.tsx | 659 ++++ src/components/modals/RegistrationModal.tsx | 409 ++ .../professional/ProfessionalModule.tsx | 195 + .../professional/common/PlaceholderModule.tsx | 14 + .../professional/common/StatsCard.tsx | 9 + .../professional/dashboard/Overview.tsx | 27 + .../professional/dashboard/StudentsList.tsx | 231 ++ .../professional/dashboard/Workouts.tsx | 27 + src/contexts/LanguageContext.tsx | 1377 +++++++ src/hooks/useCoachPlan.ts | 52 + src/hooks/useDashboardHistory.ts | 73 + src/hooks/useDashboardStats.ts | 57 + src/index.css | 113 + src/lib/database.types.ts | 651 ++++ src/lib/gemini.ts | 112 + src/lib/supabase.ts | 8 + src/main.tsx | 17 + src/n8n-coach-whatsapp.json | 572 +++ src/n8n-daily-report.json | 199 + src/n8n-foodsnap-branched.json | 911 +++++ src/n8n-foodsnap-unified.json | 713 ++++ src/n8n-stripe-webhook.json | 134 + src/pages/AdminPanel.tsx | 798 ++++ src/pages/Dashboard.tsx | 234 ++ src/pages/FAQPage.tsx | 121 + src/pages/ProfessionalDashboard.tsx | 176 + src/types/index.ts | 12 + supabase/.temp/cli-latest | 1 + supabase/.temp/gotrue-version | 1 + supabase/.temp/pooler-url | 1 + supabase/.temp/postgres-version | 1 + supabase/.temp/project-ref | 1 + supabase/.temp/rest-version | 1 + supabase/.temp/storage-migration | 1 + supabase/.temp/storage-version | 1 + supabase/functions/coach-generator/index.ts | 115 + supabase/functions/coach-generator/prompt.ts | 100 + supabase/functions/stripe-checkout/index.ts | 125 + supabase/functions/stripe-webhook/index.ts | 299 ++ supabase/functions/validate-access/index.ts | 117 + supabase/functions/whatsapp-webhook/index.ts | 954 +++++ .../whatsapp-webhook/pdf-template.ts | 249 ++ supabase/functions/whatsapp-webhook/prompt.ts | 257 ++ .../20240105000001_whatsapp_state.sql | 31 + .../20260120_create_coach_analyses.sql | 39 + .../20260120_professional_schema.sql | 190 + .../migrations/20260217_create_payments.sql | 15 + supabase_introspection.sql | 208 ++ tsconfig.json | 29 + vite.config.ts | 23 + 87 files changed, 18241 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 foodsnap-main.rar create mode 100644 foodsnap.rar create mode 100644 index.html create mode 100644 metadata.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.tsx create mode 100644 src/components/coach/AnalysisSection.tsx create mode 100644 src/components/coach/CoachResult.tsx create mode 100644 src/components/coach/CoachWizard.tsx create mode 100644 src/components/coach/DietSection.tsx create mode 100644 src/components/coach/Shared.tsx create mode 100644 src/components/coach/WorkoutSection.tsx create mode 100644 src/components/coach/pdf/PdfAnalysisCompact.tsx create mode 100644 src/components/coach/pdf/PdfDietCompact.tsx create mode 100644 src/components/coach/pdf/PdfShared.tsx create mode 100644 src/components/coach/pdf/PdfWorkoutCompact.tsx create mode 100644 src/components/common/HistoryCard.tsx create mode 100644 src/components/common/MacroBadge.tsx create mode 100644 src/components/common/StatCard.tsx create mode 100644 src/components/dashboard/DashboardCoach.tsx create mode 100644 src/components/dashboard/DashboardHistory.tsx create mode 100644 src/components/dashboard/DashboardOverview.tsx create mode 100644 src/components/dashboard/DashboardSubscription.tsx create mode 100644 src/components/landing/CoachHighlight.tsx create mode 100644 src/components/landing/FAQ.tsx create mode 100644 src/components/landing/Features.tsx create mode 100644 src/components/landing/Footer.tsx create mode 100644 src/components/landing/Header.tsx create mode 100644 src/components/landing/Hero.tsx create mode 100644 src/components/landing/HowItWorks.tsx create mode 100644 src/components/landing/Pricing.tsx create mode 100644 src/components/landing/Testimonials.tsx create mode 100644 src/components/layout/MobileNav.tsx create mode 100644 src/components/layout/Sidebar.tsx create mode 100644 src/components/modals/CalculatorsModal.tsx create mode 100644 src/components/modals/RegistrationModal.tsx create mode 100644 src/components/professional/ProfessionalModule.tsx create mode 100644 src/components/professional/common/PlaceholderModule.tsx create mode 100644 src/components/professional/common/StatsCard.tsx create mode 100644 src/components/professional/dashboard/Overview.tsx create mode 100644 src/components/professional/dashboard/StudentsList.tsx create mode 100644 src/components/professional/dashboard/Workouts.tsx create mode 100644 src/contexts/LanguageContext.tsx create mode 100644 src/hooks/useCoachPlan.ts create mode 100644 src/hooks/useDashboardHistory.ts create mode 100644 src/hooks/useDashboardStats.ts create mode 100644 src/index.css create mode 100644 src/lib/database.types.ts create mode 100644 src/lib/gemini.ts create mode 100644 src/lib/supabase.ts create mode 100644 src/main.tsx create mode 100644 src/n8n-coach-whatsapp.json create mode 100644 src/n8n-daily-report.json create mode 100644 src/n8n-foodsnap-branched.json create mode 100644 src/n8n-foodsnap-unified.json create mode 100644 src/n8n-stripe-webhook.json create mode 100644 src/pages/AdminPanel.tsx create mode 100644 src/pages/Dashboard.tsx create mode 100644 src/pages/FAQPage.tsx create mode 100644 src/pages/ProfessionalDashboard.tsx create mode 100644 src/types/index.ts create mode 100644 supabase/.temp/cli-latest create mode 100644 supabase/.temp/gotrue-version create mode 100644 supabase/.temp/pooler-url create mode 100644 supabase/.temp/postgres-version create mode 100644 supabase/.temp/project-ref create mode 100644 supabase/.temp/rest-version create mode 100644 supabase/.temp/storage-migration create mode 100644 supabase/.temp/storage-version create mode 100644 supabase/functions/coach-generator/index.ts create mode 100644 supabase/functions/coach-generator/prompt.ts create mode 100644 supabase/functions/stripe-checkout/index.ts create mode 100644 supabase/functions/stripe-webhook/index.ts create mode 100644 supabase/functions/validate-access/index.ts create mode 100644 supabase/functions/whatsapp-webhook/index.ts create mode 100644 supabase/functions/whatsapp-webhook/pdf-template.ts create mode 100644 supabase/functions/whatsapp-webhook/prompt.ts create mode 100644 supabase/migrations/20240105000001_whatsapp_state.sql create mode 100644 supabase/migrations/20260120_create_coach_analyses.sql create mode 100644 supabase/migrations/20260120_professional_schema.sql create mode 100644 supabase/migrations/20260217_create_payments.sql create mode 100644 supabase_introspection.sql create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba133bf --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1ieKyFsxWOnR3ACt_oJ5Hpidgm9Wny9At + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/foodsnap-main.rar b/foodsnap-main.rar new file mode 100644 index 0000000000000000000000000000000000000000..33df37be570f99ce99229f813f02cb6dfc0eb126 GIT binary patch literal 165196 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%0D7eCnL*6?lHTKgebhuryr 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..e44b7c3 --- /dev/null +++ b/index.html @@ -0,0 +1,143 @@ + + + + + + + + FoodSnap - Nutritional Intelligence + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..97fde74 --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "FoodSnap", + "description": "Instant nutritional analysis from a simple photo.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ba8f55b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3286 @@ +{ + "name": "foodsnap", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "foodsnap", + "version": "0.0.0", + "dependencies": { + "@google/genai": "^1.33.0", + "@supabase/supabase-js": "2.39.7", + "framer-motion": "11.0.8", + "html2pdf.js": "^0.12.1", + "lucide-react": "0.344.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT", + "optional": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.34.0.tgz", + "integrity": "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.24.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@supabase/functions-js": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz", + "integrity": "sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/gotrue-js": { + "version": "2.62.2", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.62.2.tgz", + "integrity": "sha512-AP6e6W9rQXFTEJ7sTTNYQrNf0LCcnt1hUW+RIgUK+Uh3jbWvcIST7wAlYyNZiMlS9+PYyymWQ+Ykz/rOYSO0+A==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.9.2.tgz", + "integrity": "sha512-I6yHo8CC9cxhOo6DouDMy9uOfW7hjdsnCxZiaJuIVZm1dBGTFiQPgfMa9zXCamEWzNyWRjZvupAUuX+tqcl5Sw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz", + "integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", + "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.39.7", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.39.7.tgz", + "integrity": "sha512-1vxsX10Uhc2b+Dv9pRjBjHfqmw2N2h1PyTg9LEfICR3x2xwE24By1MGCjDZuzDKH5OeHCsf4it6K8KRluAAEXA==", + "license": "MIT", + "dependencies": { + "@supabase/functions-js": "2.1.5", + "@supabase/gotrue-js": "2.62.2", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.9.2", + "@supabase/realtime-js": "2.9.3", + "@supabase/storage-js": "2.5.5" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.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==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/framer-motion": { + "version": "11.0.8", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.8.tgz", + "integrity": "sha512-1KSGNuqe1qZkS/SWQlDnqK2VCVzRVEoval379j0FiUBJAZoqgwyvqFkfvJbgW2IPFo4wX16K+M0k5jO23lCIjA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2pdf.js": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.12.1.tgz", + "integrity": "sha512-3rBWQ96H5oOU9jtoz3MnE/epGi27ig9h8aonBk4JTpvUERM3lMRxhIRckhJZEi4wE0YfRINoYOIDY0hLY0CHgQ==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.0.0", + "jspdf": "^3.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jspdf": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz", + "integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.344.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz", + "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "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==", + "license": "MIT", + "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.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..505e02c --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "foodsnap", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@google/genai": "^1.33.0", + "@supabase/supabase-js": "2.39.7", + "framer-motion": "11.0.8", + "html2pdf.js": "^0.12.1", + "lucide-react": "0.344.0", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..4927a8f --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,327 @@ +import React, { useState, useEffect } from 'react'; +import Header from './components/landing/Header'; +import Hero from './components/landing/Hero'; +import CoachHighlight from './components/landing/CoachHighlight'; +import HowItWorks from './components/landing/HowItWorks'; +import Features from './components/landing/Features'; +import Testimonials from './components/landing/Testimonials'; +import Pricing from './components/landing/Pricing'; +import FAQ from './components/landing/FAQ'; +import Footer from './components/landing/Footer'; +import RegistrationModal from './components/modals/RegistrationModal'; +import CalculatorsModal from './components/modals/CalculatorsModal'; +import Dashboard from './pages/Dashboard'; +import AdminPanel from './pages/AdminPanel'; +import ProfessionalDashboard from './pages/ProfessionalDashboard'; +import FAQPage from './pages/FAQPage'; +import { LanguageProvider } from './contexts/LanguageContext'; +import { supabase } from './lib/supabase'; +import { Loader2 } from 'lucide-react'; + +import { User } from './types'; + +// removed User interface definition + +const AppContent: React.FC = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [isToolsOpen, setIsToolsOpen] = useState(false); + const [authMode, setAuthMode] = useState<'login' | 'register'>('register'); + const [selectedPlan, setSelectedPlan] = useState('starter'); + const [currentView, setCurrentView] = useState<'home' | 'faq'>('home'); // Estado de navegação + const [isCompletingProfile, setIsCompletingProfile] = useState(false); // Novo estado para controle de perfil incompleto + + const [user, setUser] = useState(null); + const [isAdminView, setIsAdminView] = useState(false); + const [isProfessionalView, setIsProfessionalView] = useState(false); + const [isLoadingSession, setIsLoadingSession] = useState(true); + + // Check active session on load + // Check active session on load + useEffect(() => { + let mounted = true; + + const initializeAuth = async () => { + try { + // Obter sessão inicial sem race conditions complexas + const { data: { session }, error } = await supabase.auth.getSession(); + + if (error) { + console.error("Erro ao obter sessão inicial:", error); + if (mounted) setIsLoadingSession(false); + return; + } + + if (session?.user) { + console.log("App: Sessão encontrada, carregando perfil..."); + if (mounted) { + await fetchUserProfile(session.user.id, session.user.email); + } + } else { + console.log("App: Nenhuma sessão ativa."); + if (mounted) setIsLoadingSession(false); + } + } catch (err) { + console.error("Erro inesperado na autenticação:", err); + if (mounted) setIsLoadingSession(false); + } + }; + + initializeAuth(); + + const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => { + console.log(`Auth event: ${event}`); + if (!mounted) return; + + if (event === 'SIGNED_OUT') { + setUser(null); + setIsAdminView(false); + setIsProfessionalView(false); + setIsLoadingSession(false); + setCurrentView('home'); + setIsCompletingProfile(false); + } else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') { + if (session?.user) { + // Apenas recarrega se o usuário ainda não estiver setado ou se mudou + // Mas para garantir atualização de claims/perfil, recarregamos. + await fetchUserProfile(session.user.id, session.user.email); + } + } + }); + + return () => { + mounted = false; + subscription.unsubscribe(); + }; + }, []); + + const fetchUserProfile = async (userId: string, email?: string) => { + try { + let profile = null; + + // Tentativa única de buscar perfil. O cliente Supabase já trata retries de rede. + const { data, error } = await supabase + .from('profiles') + .select('*') + .eq('id', userId) + .maybeSingle(); + + if (error) throw error; + + profile = data; + + // Se não tem perfil ou falta telefone, solicitamos completar cadastro + if (!profile || !profile.phone_e164) { + console.warn("Perfil incompleto. Solicitando dados."); + // Não fazemos signOut, apenas abrimos o modal para completar + setIsCompletingProfile(true); + setAuthMode('register'); // Visualmente irrelevante pois isCompletingProfile domina + setIsModalOpen(true); + // User fica null, então cai na Landing Page com Modal aberto. Perfeito. + return; + } + + // Se perfil ok, garante que flag de completar está false + setIsCompletingProfile(false); + + const { data: entitlement } = await supabase + .from('user_entitlements') + .select('*') + .eq('user_id', userId) + .maybeSingle(); + + let plan: 'free' | 'pro' | 'trial' = 'free'; + if (entitlement) { + if (entitlement.entitlement_code === 'pro' && entitlement.is_active) plan = 'pro'; + else if (entitlement.entitlement_code === 'trial' && entitlement.is_active) plan = 'trial'; + } + + setUser({ + id: userId, + name: profile.full_name || 'Usuário', + email: email || profile.email || '', + phone: profile.phone_e164, + public_id: profile.public_id, + avatar: undefined, // Column does not exist in DB, using undefined to trigger UI fallback + is_admin: profile.is_admin, + is_professional: profile.is_professional, + plan: plan, + plan_valid_until: entitlement?.valid_until + }); + + // Auto-switch logic: + // If user is professional, default to Professional View. + // If user is Admin, they usually see Admin View but we might default to user dashboard for them unless they toggle. + // Logic requested: "System verifies and logs him in correct panel". + + const loginIntent = localStorage.getItem('login_intent'); + + if (profile.is_professional) { + setIsProfessionalView(true); + } else { + setIsProfessionalView(false); + } + + // Override if explicit intent was set (though we removed the button, old intents might linger, safe to ignore or keep) + if (loginIntent === 'user') { + // If they explicitly wanted user view but are pro, maybe respect it? + // For now, let's stick to the requested "System verifies" rule above. + } + + } catch (error) { + console.error('Error fetching profile:', error); + setUser(null); + } finally { + setIsLoadingSession(false); + } + + }; + + const handleOpenRegister = (plan: string = 'starter') => { + setSelectedPlan(plan); + setAuthMode('register'); + setIsModalOpen(true); + setIsCompletingProfile(false); + }; + + const handleOpenLogin = (context?: 'user' | 'professional') => { + // If context is professional, we can store this intent to redirect after login + // For now, we'll use a simple localStorage flag or state + if (context === 'professional') { + localStorage.setItem('login_intent', 'professional'); + } else { + localStorage.setItem('login_intent', 'user'); + } + + setAuthMode('login'); + setIsModalOpen(true); + setIsCompletingProfile(false); + }; + + const handleAuthSuccess = async () => { + setIsModalOpen(false); + setIsCompletingProfile(false); + // Force refresh profile to ensure we have latest data + const { data: { session } } = await supabase.auth.getSession(); + if (session?.user) { + await fetchUserProfile(session.user.id, session.user.email); + + // Check intent + const intent = localStorage.getItem('login_intent'); + if (intent === 'professional') { + // We can't know for sure if they are pro yet inside this function scope easily unless we use the user state which might be stale + // But fetchUserProfile updates 'user'. + // Ideally we wait for user state to update. + // For simplicity, let's rely on the useEffect that watches 'user' or just check 'isProfessionalView' toggle inside fetchUserProfile? + // Lets keep it simple: We just set the view if the profile allows it. + // Actually, fetchUserProfile runs, updates User. + // We can check localstorage IN fetchUserProfile. + } + localStorage.removeItem('login_intent'); + } + }; + + const handleLogout = async () => { + await supabase.auth.signOut(); + setUser(null); + setIsAdminView(false); + }; + + const toggleAdminView = () => { + if (user?.is_admin) { + setIsAdminView(!isAdminView); + } + }; + + // Helper function for navigating + const handleNavigate = (view: 'home' | 'faq') => { + setCurrentView(view); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + if (isLoadingSession) { + return ( +
+ +
+ ); + } + + // Rota Admin + if (user && isAdminView && user.is_admin) { + return ; + } + + // Rota Profissional + if (user && isProfessionalView) { + return setIsProfessionalView(false)} onLogout={handleLogout} />; + } + + // Rota Dashboard Usuário + if (user) { + return ( + setIsProfessionalView(true)} + /> + ); + } + + // Rota Pública (Landing Page ou FAQ Page) + return ( +
+
handleOpenRegister('starter')} + onLogin={handleOpenLogin} + onOpenTools={() => setIsToolsOpen(true)} + onNavigate={handleNavigate} // Passa navegação + /> + +
+ {currentView === 'home' ? ( + <> + handleOpenRegister('starter')} /> + handleOpenRegister('starter')} /> + + + + + + + ) : ( + handleNavigate('home')} /> + )} +
+ +
handleOpenRegister('starter')} + onNavigate={handleNavigate} // Passa navegação + /> + + setIsModalOpen(false)} + plan={selectedPlan} + mode={authMode} + isCompletingProfile={isCompletingProfile} // Passa o estado de completar perfil + onSuccess={handleAuthSuccess} + /> + + setIsToolsOpen(false)} + /> +
+ ); +}; + +const App: React.FC = () => { + return ( + + + + ); +}; + +export default App; \ No newline at end of file diff --git a/src/components/coach/AnalysisSection.tsx b/src/components/coach/AnalysisSection.tsx new file mode 100644 index 0000000..b8c6ff8 --- /dev/null +++ b/src/components/coach/AnalysisSection.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Activity, Trophy, CheckCircle2, AlertCircle } from 'lucide-react'; +import { Card, Badge } from './Shared'; + +interface AnalysisSectionProps { + analysis: any; +} + +const AnalysisSection: React.FC = ({ analysis }) => { + return ( +
+ }> +
+
+

Gordura Estimada

+

{analysis?.body_fat_percentage}%

+
+
+

Massa Muscular

+

{analysis?.muscle_mass_level}

+
+
+
+

Avaliação Postural

+

+ {analysis?.posture_analysis || "Nenhum desvio significativo detectado."} +

+
+
+ + }> +
+
+

+ Pontos Fortes +

+
+ {analysis?.strengths?.map((s: string, i: number) => ( + + ))} +
+
+
+

+ Foco Total +

+
+ {analysis?.weaknesses?.map((s: string, i: number) => ( + + ))} +
+
+
+
+
+ ); +}; + +export default AnalysisSection; diff --git a/src/components/coach/CoachResult.tsx b/src/components/coach/CoachResult.tsx new file mode 100644 index 0000000..59598a2 --- /dev/null +++ b/src/components/coach/CoachResult.tsx @@ -0,0 +1,255 @@ +import React, { useState } from 'react'; +import { Dumbbell, Utensils, Activity, Loader2 } from 'lucide-react'; +import { motion } from 'framer-motion'; + +import { KPI, Tab } from './Shared'; +import AnalysisSection from './AnalysisSection'; +import DietSection from './DietSection'; +import WorkoutSection from './WorkoutSection'; + +// PDF pages +import { PdfAnalysisCompact } from './pdf/PdfAnalysisCompact'; +import { PdfDietCompact } from './pdf/PdfDietCompact'; +import { PdfWorkoutCompact } from './pdf/PdfWorkoutCompact'; + +// @ts-ignore +import { renderToStaticMarkup } from 'react-dom/server'; + +interface CoachResultProps { + data: any; + onReset: () => void; +} + +const N8N_WEBHOOK_URL = 'https://n8n.seureview.com.br/webhook/pdf-coach'; + +const CoachResult: React.FC = ({ data, onReset }) => { + const [activeTab, setActiveTab] = useState<'analysis' | 'diet' | 'workout'>('analysis'); + const [isGeneratingPdf, setIsGeneratingPdf] = useState(false); + + if (!data) return null; + + const { analysis, diet, workout, motivation_quote } = data; + + const handleSavePDF = async () => { + setIsGeneratingPdf(true); + + try { + // 1) Render 2 pages (Diet & Workout only - requested by user) + const pdfPages = ( +
+ {/* REMOVED ANALYSIS PAGE AS REQUESTED */} + +
+ +
+ +
+ +
+
+ ); + + const pagesHtml = renderToStaticMarkup(pdfPages); + + // 2) Full HTML + print-lock CSS (Optimized for Gotenberg) + const fullHtml = ` + + + + + + + + + + + + + ${pagesHtml} + +`; + + // 3) Send to n8n + const fileName = `FoodSnap_Titan_${new Date().toISOString().split('T')[0]}`; + + const response = await fetch(N8N_WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ html: fullHtml, file_name: fileName }), + }); + + if (!response.ok) throw new Error(`Erro n8n: ${response.status} ${response.statusText}`); + + // 4) Download PDF + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${fileName}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (err) { + console.error('PDF Generation Server Error:', err); + alert('Erro ao gerar PDF no servidor. Verifique se o Webhook do n8n está configurado.'); + } finally { + setIsGeneratingPdf(false); + } + }; + + return ( +
+ {/* Premium Header */} +
+
+
+ +
+
+
+ + Protocolo Titan + + {new Date().toLocaleDateString()} +
+ +

+ Seu Blueprint
+ + De Transformação + +

+ +

+ "{motivation_quote || 'Disciplina é a ponte entre metas e conquistas.'}" +

+
+ +
+ + + +
+
+ +
+ + + + +
+
+ + {/* Tabs */} +
+
+ setActiveTab('analysis')} + icon={} + label="Diagnóstico" + /> + setActiveTab('diet')} + icon={} + label="Nutrição" + /> + setActiveTab('workout')} + icon={} + label="Treinamento" + /> +
+
+ + {/* Rich UI */} +
+ {activeTab === 'analysis' && ( + + + + )} + + {activeTab === 'diet' && ( + + + + )} + + {activeTab === 'workout' && ( + + + + )} +
+
+ ); +}; + +export default CoachResult; diff --git a/src/components/coach/CoachWizard.tsx b/src/components/coach/CoachWizard.tsx new file mode 100644 index 0000000..8f5245c --- /dev/null +++ b/src/components/coach/CoachWizard.tsx @@ -0,0 +1,405 @@ + +import React, { useState, useRef, useEffect } from 'react'; +import { Camera, Upload, X, ChevronRight, Check, AlertCircle, Loader2, Dumbbell, Apple, Activity, Image as ImageIcon } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { supabase } from '@/lib/supabase'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface CoachWizardProps { + isOpen: boolean; + onClose: () => void; + onComplete: (data: any) => void; +} + +type Step = 'photos' | 'goal' | 'processing'; + +const CoachWizard: React.FC = ({ isOpen, onClose, onComplete }) => { + const { t } = useLanguage(); + const [step, setStep] = useState('photos'); + const [photos, setPhotos] = useState<{ front?: string, side?: string, back?: string }>({}); + const [goal, setGoal] = useState(''); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [loadingMsgIndex, setLoadingMsgIndex] = useState(0); + + const loadingMessages = t.coach.processing.steps; + + useEffect(() => { + let interval: any; + if (step === 'processing' && !errorMessage) { + interval = setInterval(() => { + setLoadingMsgIndex(prev => (prev + 1) % loadingMessages.length); + }, 3000); + } + return () => clearInterval(interval); + }, [step, errorMessage]); + + // Refs for different input types + const fileInputRef = useRef(null); + const cameraInputRef = useRef(null); + const [activePhotoField, setActivePhotoField] = useState<'front' | 'side' | 'back' | null>(null); + + if (!isOpen) return null; + + // --- Image Processing Helper (Resize & Compress) --- + const processImage = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = (event) => { + const img = new Image(); + img.src = event.target?.result as string; + img.onload = () => { + const canvas = document.createElement('canvas'); + const MAX_WIDTH = 1024; // Resize to max 1024px width for AI/Backend limit + const scaleSize = MAX_WIDTH / img.width; + const width = (scaleSize < 1) ? MAX_WIDTH : img.width; + const height = (scaleSize < 1) ? img.height * scaleSize : img.height; + + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0, width, height); + + // Compress to JPEG 0.7 quality + const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.7); + resolve(compressedDataUrl); + }; + img.onerror = (err) => reject(err); + }; + reader.onerror = (err) => reject(err); + }); + }; + + const handleFileChange = async (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0] && activePhotoField) { + const file = e.target.files[0]; + try { + setLoading(true); // Show momentary loading for compression + const compressedImage = await processImage(file); + setPhotos(prev => ({ ...prev, [activePhotoField]: compressedImage })); + setActivePhotoField(null); + } catch (error) { + console.error("Error processing image:", error); + alert("Erro ao processar a imagem. Tente outra."); + } finally { + setLoading(false); + // Reset inputs to allow selecting same file again if needed + if (fileInputRef.current) fileInputRef.current.value = ""; + if (cameraInputRef.current) cameraInputRef.current.value = ""; + } + } + }; + + const triggerUpload = (field: 'front' | 'side' | 'back', source: 'gallery' | 'camera') => { + setActivePhotoField(field); + if (source === 'gallery') { + setTimeout(() => fileInputRef.current?.click(), 0); + } else { + setTimeout(() => cameraInputRef.current?.click(), 0); + } + }; + + const handleNext = () => { + if (step === 'photos') { + if (photos.front && photos.side && photos.back) setStep('goal'); + else alert("Por favor, adicione as 3 fotos (Frente, Perfil, Costas) para garantir a precisão da análise."); + } else if (step === 'goal') { + if (goal) startProcessing(); + } + }; + + const startProcessing = async () => { + setStep('processing'); + setLoading(true); + setErrorMessage(null); + + + try { + // Create a timeout promise that rejects after 55 seconds + const timeoutPromise = new Promise((_, reject) => { + const id = setTimeout(() => { + clearTimeout(id); + reject(new Error(t.coach.processing.wait)); + }, 55000); // 55s strict timeout + }); + + // Race between the API call and the timeout + const response: any = await Promise.race([ + supabase.functions.invoke('coach-generator', { + body: { photos, goal, intent: 'coach' } + }), + timeoutPromise + ]); + + // If we get here, it means the API responded before timeout + const { data, error } = response; + + + if (error) { + console.error("Supabase Invoke Error:", error); + // Tenta extrair a mensagem de erro real do backend se existir + let errorMsg = "Falha na comunicação com a IA."; + if (error && typeof error === 'object') { + // Supabase functions usually return { context: ..., error: { message: "..." } } or just error + if ('message' in error) errorMsg = (error as any).message; + else errorMsg = JSON.stringify(error); + } + throw new Error(errorMsg); + } + + if (!data) { + throw new Error("Nenhuma resposta recebida da IA."); + } + + console.log("Coach Result:", data); + + // Validate essential data presence + if (!data.analysis || !data.diet || !data.workout) { + throw new Error("A resposta da IA veio incompleta. Tente com fotos mais claras."); + } + + onComplete(data); + onClose(); + + } catch (err: any) { + console.error("Coach Logic Error:", err); + + let message = "Erro ao gerar protocolo. Verifique sua conexão e tente novamente."; + if (err.name === 'AbortError') message = "O servidor demorou muito para responder. Tente fotos menores."; + if (err.message) message = err.message; + + setErrorMessage(message); + // Don't auto-close, let user see error and retry + } finally { + setLoading(false); + // If error, stay on processing step or go back? + // Better to show error on processing screen with a "Retry" button or "Back" + } + }; + + return ( +
+ + {/* Header */} +
+
+

+ + {t.coach.title} +

+

{t.coach.subtitle}

+
+ +
+ + {/* Content */} +
+ + + {step === 'photos' && ( + +
+ +

+ {t.coach.photosStep.alert.split(':')[0]}: {t.coach.photosStep.alert.split(':')[1]} +

+
+ +
+ {['front', 'side', 'back'].map((side) => ( +
+

+ {side === 'front' ? t.coach.photosStep.front : side === 'side' ? t.coach.photosStep.side : t.coach.photosStep.back} +

+
+ {photos[side as keyof typeof photos] ? ( + <> + +
+ + +
+
+ +
+ + ) : ( +
+ +
+ + +
+ {/* Helper text removed as buttons are visible */} +
+ )} +
+
+ ))} +
+
+ )} + + {step === 'goal' && ( + +

{t.coach.goalStep.title}

+ +
+ {[ + { id: 'hypertrophy', icon: , title: t.coach.goalStep.hypertrophy.title, desc: t.coach.goalStep.hypertrophy.desc }, + { id: 'definition', icon: , title: t.coach.goalStep.definition.title, desc: t.coach.goalStep.definition.desc }, + { id: 'maintenance', icon: , title: t.coach.goalStep.maintenance.title, desc: t.coach.goalStep.maintenance.desc }, + { id: 'strength', icon: , title: t.coach.goalStep.strength.title, desc: t.coach.goalStep.strength.desc } + ].map((opt) => ( + + ))} +
+
+ )} + + {step === 'processing' && ( + + {errorMessage ? ( +
+
+ +
+

{t.coach.processing.errorTitle}

+

{errorMessage}

+ +
+ ) : ( + <> +
+
+
+ +
+

+ {loadingMessages[loadingMsgIndex]} +

+

+ {t.coach.processing.wait} +

+ + )} +
+ )} +
+ +
+ + {/* Footer */} + {step !== 'processing' && ( +
+ {step === 'goal' && ( + + )} + +
+ )} +
+ + {/* Hidden Inputs */} + + +
+ ); +}; + +export default CoachWizard; diff --git a/src/components/coach/DietSection.tsx b/src/components/coach/DietSection.tsx new file mode 100644 index 0000000..ac94980 --- /dev/null +++ b/src/components/coach/DietSection.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { Plus, Trash2, Edit2, CheckCircle2, AlertCircle, Droplets, Apple, Clock, Pill } from 'lucide-react'; +import { MacroCard } from './Shared'; + +interface DietSectionProps { + diet: any; +} + +const DietSection: React.FC = ({ diet }) => { + return ( +
+ {/* Macros & Hydration */} +
+ + + +
+
+ + {diet?.hydration_liters}L + Água/Dia +
+
+ +
+ {/* Meal Plan List */} +
+

Plano Alimentar

+
+ {diet?.meal_plan_example?.map((meal: any, i: number) => ( +
+
+
+ {i + 1} +
+
+

{meal.name}

+ {meal.time_range && ( +

+ {meal.time_range} +

+ )} +
+
+ +
+ {/* New Format: Multiple Options */} + {meal.options && Array.isArray(meal.options) ? ( +
+ {meal.options.map((opt: string, idx: number) => ( +
+

{opt}

+
+ ))} +
+ ) : ( + // Legacy Fallback +
+

+ {meal.main_option || (meal.options && meal.options[0]) || "Opção Padrão"} +

+
+ )} + + {/* Substitution Suggestion */} + {(meal.substitution_suggestion || meal.substitution) && ( +
+
+ +
+
+

Dica de Substituição

+

+ {meal.substitution_suggestion || meal.substitution} +

+
+
+ )} +
+
+ ))} +
+
+ + {/* Supplements */} +
+

Suplementação

+
+
+
+ {diet?.supplements?.map((sup: any, i: number) => { + // Handle complex object or simple string + const name = typeof sup === 'string' ? sup : sup.name; + const dosage = typeof sup === 'string' ? '' : sup.dosage; + const reason = typeof sup === 'string' ? '' : sup.reason; + + return ( +
+
+ {name} +
+ {dosage &&

{dosage}

} + {reason &&

{reason}

} +
+ ); + })} +
+
+
+
+
+ ); +}; + +export default DietSection; diff --git a/src/components/coach/Shared.tsx b/src/components/coach/Shared.tsx new file mode 100644 index 0000000..d2efa9f --- /dev/null +++ b/src/components/coach/Shared.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +export const KPI = ({ label, value }: any) => ( +
+

{label}

+

{value || '-'}

+
+); + +export const Tab = ({ active, onClick, icon, label }: any) => ( + +); + +export const Card = ({ title, icon, children, className = "" }: any) => ( +
+
+
+ {icon} +
+

{title}

+
+ {children} +
+); + +export const Badge = ({ text, color }: any) => { + const styles = color === 'green' + ? 'bg-emerald-50 text-emerald-700 border-emerald-100' + : 'bg-orange-50 text-orange-700 border-orange-100'; + + return ( + + {text} + + ); +}; + +export const MacroCard = ({ label, value, color }: any) => { + const colors: any = { + brand: 'bg-brand-50 text-brand-900 border-brand-100', + blue: 'bg-blue-50 text-blue-900 border-blue-100', + yellow: 'bg-yellow-50 text-yellow-900 border-yellow-100' + }; + + return ( +
+ {label} + {value} +
+ ); +}; diff --git a/src/components/coach/WorkoutSection.tsx b/src/components/coach/WorkoutSection.tsx new file mode 100644 index 0000000..7654081 --- /dev/null +++ b/src/components/coach/WorkoutSection.tsx @@ -0,0 +1,100 @@ +import React, { useState } from 'react'; +import { Calendar, Activity, ChevronUp, ChevronDown, Dumbbell } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; + +interface WorkoutSectionProps { + workout: any; +} + +const WorkoutSection: React.FC = ({ workout }) => { + const [openInjury, setOpenInjury] = useState(false); + + return ( +
+ {/* Workout Header */} +
+
+
+

Estrutura de Treino

+

{workout?.split}

+

{workout?.frequency_days} dias na semana

+
+
+ +
+
+ + {/* Injury Adaptations Accordion - Only show if data exists */} + {workout?.injury_adaptations && ( +
setOpenInjury(!openInjury)}> +
+

+ Adaptações para Dores? +

+ {openInjury ? : } +
+

Clique para ver exercícios alternativos.

+ + + {openInjury && ( + +
+ {Object.entries(workout.injury_adaptations).map(([key, val]: any) => ( +
+ + {key === 'knee_pain' ? 'Dor no Joelho' : key === 'shoulder_pain' ? 'Dor no Ombro' : 'Dor nas Costas'} + +

{val}

+
+ ))} +
+
+ )} +
+
+ )} +
+ + {/* Routine Grid */} +
+ {workout?.routine?.map((day: any, i: number) => ( +
+
+
+ + {day.day} + +

{day.muscle_group}

+
+
+ +
+
+ +
+ {day.exercises?.map((ex: any, idx: number) => ( +
+
+

{ex.name}

+
+ {ex.sets}x + {ex.reps} +
+
+ {ex.technique &&

{ex.technique}

} +
+ ))} +
+
+ ))} +
+
+ ); +}; + +export default WorkoutSection; diff --git a/src/components/coach/pdf/PdfAnalysisCompact.tsx b/src/components/coach/pdf/PdfAnalysisCompact.tsx new file mode 100644 index 0000000..10c3f14 --- /dev/null +++ b/src/components/coach/pdf/PdfAnalysisCompact.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { Activity } from 'lucide-react'; +import { PdfHeaderRow, safeStr, asArray } from './PdfShared'; + +export const PdfAnalysisCompact: React.FC<{ data: any }> = ({ data }) => { + const a = data?.analysis || {}; + const d = data?.diet || {}; + const w = data?.workout || {}; + + const bullets = asArray( + a?.improvements || + a?.what_to_improve || + a?.improve || + a?.recommendations || + a?.tips || + a?.notes || + a?.observations || + [] + ) + .map((x: any) => (typeof x === 'string' ? x : safeStr(x?.text, ''))) + .filter(Boolean) + .slice(0, 8); + + const positives = asArray(a?.strengths || a?.positives || a?.good_points || a?.pontos_fortes || []) + .map((x: any) => (typeof x === 'string' ? x : safeStr(x?.text, ''))) + .filter(Boolean) + .slice(0, 6); + + return ( +
+ } + /> + +
+
+
Biótipo
+
{safeStr(a?.somatotype)}
+
+
+
Objetivo
+
{safeStr(w?.focus)}
+
+
+
Calorias
+
+ {Math.round(d?.total_calories || 0)} kcal +
+
+
+
Estrutura
+
{safeStr(w?.split)}
+
+
+ +
+
+
Pontos fortes
+ {positives.length ? ( +
    + {positives.map((t: string, i: number) => ( +
  • {t}
  • + ))} +
+ ) : ( +

+ {safeStr(a?.summary || a?.overview || a?.diagnosis || a?.diagnostico, 'Sem detalhes extras.')} +

+ )} +
+ +
+
O que melhorar
+ {bullets.length ? ( +
    + {bullets.map((t: string, i: number) => ( +
  • {t}
  • + ))} +
+ ) : ( +

+ {safeStr(a?.improvement_summary || a?.next_steps || a?.proximos_passos, 'Sem detalhes extras.')} +

+ )} +
+
+ +
+
Notas rápidas
+

+ {safeStr( + a?.final_note || a?.note || a?.observacao_final || a?.closing, + 'Consistência diária > perfeição. Foque em execução e acompanhamento.' + )} +

+
+
+ ); +}; diff --git a/src/components/coach/pdf/PdfDietCompact.tsx b/src/components/coach/pdf/PdfDietCompact.tsx new file mode 100644 index 0000000..ac124d4 --- /dev/null +++ b/src/components/coach/pdf/PdfDietCompact.tsx @@ -0,0 +1,181 @@ +import React, { useMemo } from 'react'; +import { Utensils, Droplets, Pill } from 'lucide-react'; +import { PdfHeaderRow, safeStr, asArray } from './PdfShared'; + +function truncate(text: string, max = 140) { + const t = (text || '').trim(); + if (!t) return '-'; + return t.length > max ? t.slice(0, max - 1) + '…' : t; +} + +function pickMeals(diet: any): any[] { + // ✅ match do frontend + if (Array.isArray(diet?.meal_plan_example) && diet.meal_plan_example.length) return diet.meal_plan_example; + + // fallback antigos + const candidates = [ + diet?.meals, + diet?.meal_plan, + diet?.plan, + diet?.daily_plan, + diet?.diet_plan, + diet?.meals_plan, + diet?.mealsPlan, + diet?.refeicoes, + diet?.refeicoes_plano, + ]; + for (const c of candidates) if (Array.isArray(c) && c.length) return c; + + return []; +} + +export const PdfDietCompact: React.FC<{ diet: any }> = ({ diet }) => { + const meals = useMemo(() => pickMeals(diet).slice(0, 6), [diet]); // 6 max pra caber 1 página + const supplements = asArray(diet?.supplements).slice(0, 6); + + const protein = diet?.macros?.protein_g ?? diet?.protein_g ?? diet?.protein ?? diet?.protein_grams; + const carbs = diet?.macros?.carbs_g ?? diet?.carbs_g ?? diet?.carbs ?? diet?.carb_grams; + const fats = diet?.macros?.fats_g ?? diet?.fat_g ?? diet?.fat ?? diet?.fat_grams; + const water = diet?.hydration_liters ?? diet?.water_liters ?? diet?.hydration; + + return ( +
+ } + /> + + {/* Summary row */} +
+
+
+
Calorias/dia
+
{Math.round(diet?.total_calories || 0)} kcal
+
+ +
+
Proteína
+
{safeStr(protein)}
+
+ +
+
Carbo
+
{safeStr(carbs)}
+
+ +
+
+
Gordura
+
{safeStr(fats)}
+
+
+ +
{safeStr(water, '-')}{String(water || '').includes('L') ? '' : 'L'}
+
+
+
+
+ + {/* Main grid: meals + supplements */} +
+ {/* Meals (2 cols) */} +
+
Plano Alimentar
+ +
+ {meals.length ? ( + meals.map((meal: any, i: number) => { + const options = Array.isArray(meal?.options) ? meal.options : []; + const opt1 = options?.[0] || meal?.main_option || ''; + const opt2 = options?.[1] || ''; + + return ( +
+
+
+
+ {safeStr(meal?.name, `Refeição ${i + 1}`)} +
+ {meal?.time_range && ( +
+ {safeStr(meal.time_range)} +
+ )} +
+
#{i + 1}
+
+ +
+ {opt1 ? ( +
+ Opção 1: + {truncate(String(opt1), 160)} +
+ ) : null} + + {opt2 ? ( +
+ Opção 2: + {truncate(String(opt2), 160)} +
+ ) : null} + + {(meal?.substitution_suggestion || meal?.substitution) ? ( +
+ Dica de substituição:{' '} + {truncate(String(meal?.substitution_suggestion || meal?.substitution), 180)} +
+ ) : null} +
+
+ ); + }) + ) : ( +
+ Não achei diet.meal_plan_example. Se teu JSON mudou, me manda 1 exemplo do diet. +
+ )} +
+
+ + {/* Supplements (1 col) */} +
+
Suplementação
+ +
+
+ {supplements.length ? ( + supplements.map((sup: any, i: number) => { + const name = typeof sup === 'string' ? sup : sup?.name; + const dosage = typeof sup === 'string' ? '' : sup?.dosage; + const reason = typeof sup === 'string' ? '' : sup?.reason; + + return ( +
+
+ +
{truncate(String(name || 'Suplemento'), 40)}
+
+ {dosage ?
{truncate(String(dosage), 60)}
: null} + {reason ?
{truncate(String(reason), 80)}
: null} +
+ ); + }) + ) : ( +
+ Sem suplementos informados. +
+ )} +
+
+ +
+ Dica: água + consistência diária. Ajustes finos semanais. +
+
+
+
+ ); +}; diff --git a/src/components/coach/pdf/PdfShared.tsx b/src/components/coach/pdf/PdfShared.tsx new file mode 100644 index 0000000..a646dda --- /dev/null +++ b/src/components/coach/pdf/PdfShared.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +export function safeStr(v: any, fallback = '-'): string { + if (v === null || v === undefined) return fallback; + if (typeof v === 'string') return v.trim() || fallback; + if (typeof v === 'number') return Number.isFinite(v) ? String(v) : fallback; + return fallback; +} + +export function asArray(x: any): any[] { + return Array.isArray(x) ? x : []; +} + +export const PdfHeaderRow: React.FC<{ + index: string; + title: string; + subtitle: string; + icon: React.ReactNode; +}> = ({ index, title, subtitle, icon }) => { + return ( +
+
+
+ Protocolo Titan • FoodSnap Coach +
+

+ {index}. {title} +

+

{subtitle}

+
+
{icon}
+
+ ); +}; diff --git a/src/components/coach/pdf/PdfWorkoutCompact.tsx b/src/components/coach/pdf/PdfWorkoutCompact.tsx new file mode 100644 index 0000000..334ec3b --- /dev/null +++ b/src/components/coach/pdf/PdfWorkoutCompact.tsx @@ -0,0 +1,119 @@ +import React, { useMemo } from 'react'; +import { Dumbbell, Quote } from 'lucide-react'; +import { PdfHeaderRow, safeStr, asArray } from './PdfShared'; + +function pickRoutine(workout: any) { + // ✅ SHAPE REAL DO FRONTEND (WorkoutSection usa workout.routine) + const r = workout?.routine ?? workout?.days ?? workout?.plan ?? []; + return Array.isArray(r) ? r : []; +} + +function pickExercises(day: any) { + const ex = day?.exercises ?? day?.items ?? day?.workout ?? []; + return Array.isArray(ex) ? ex : []; +} + +function exLine(ex: any) { + if (typeof ex === 'string') return ex; + + const name = safeStr(ex?.name || ex?.exercise || ex?.movimento, ''); + const sets = ex?.sets ?? ex?.series; + const reps = ex?.reps ?? ex?.repetitions; + const technique = safeStr(ex?.technique || ex?.notes || ex?.cue, ''); + + const sr: string[] = []; + if (sets !== undefined && sets !== null && String(sets).trim() !== '') sr.push(`${sets}x`); + if (reps !== undefined && reps !== null && String(reps).trim() !== '') sr.push(`${reps}`); + + const left = [name, sr.length ? sr.join(' ') : ''].filter(Boolean).join(' — '); + return [left, technique].filter(Boolean).join(' • ') || '-'; +} + +export const PdfWorkoutCompact: React.FC<{ workout: any; quote?: string }> = ({ workout, quote }) => { + const days = useMemo(() => pickRoutine(workout).slice(0, 5), [workout]); + + return ( +
+ } + /> + + {/* Top summary */} +
+
+
+
Split
+
{safeStr(workout?.split)}
+
+
+
Frequência
+
{safeStr(workout?.frequency_days, '-')} dias
+
+
+
Objetivo
+
{safeStr(workout?.focus)}
+
+
+
Duração
+
{safeStr(workout?.duration || '4–8 semanas')}
+
+
+
+ + {/* Day cards (muito mais bonito que tabela) */} +
+ {days.length ? ( + days.map((day: any, idx: number) => { + const exs = pickExercises(day).slice(0, 5); + const dayName = safeStr(day?.day || day?.name || day?.title || `Dia ${idx + 1}`, `Dia ${idx + 1}`); + const muscle = safeStr(day?.muscle_group || day?.focus || day?.grupo, ''); + + return ( +
+
+
+
{dayName}
+
{muscle}
+
+
{safeStr(workout?.split, '')}
+
+ +
+ {exs.length ? ( +
    + {exs.map((ex: any, i: number) => ( +
  • + {exLine(ex)} +
  • + ))} +
+ ) : ( +
Treino do dia não detalhado.
+ )} +
+ + {day?.technique_focus ? ( +
+ Técnica: {safeStr(day?.technique_focus, '-')} +
+ ) : null} +
+ ); + }) + ) : ( +
+ Rotina não detalhada neste relatório. +
+ )} +
+ +
+ + "{quote || 'Disciplina é a ponte entre metas e conquistas.'}" +
+
+ ); +}; diff --git a/src/components/common/HistoryCard.tsx b/src/components/common/HistoryCard.tsx new file mode 100644 index 0000000..e732576 --- /dev/null +++ b/src/components/common/HistoryCard.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import MacroBadge from './MacroBadge'; + +interface HistoryCardProps { + item: { + id: string; + img: string; + category: string; + details?: string; + cals: number; + score: number; + date: string; + protein: string; + carbs: string; + fat: string; + }; + fallback: string; +} + +const HistoryCard: React.FC = ({ item, fallback }) => ( +
+
+ {item.category} { + const target = e.currentTarget; + // Proteção contra Loop Infinito de Erros + if (target.src !== fallback) { + target.src = fallback; + } + }} + /> +
+
+ {item.cals} kcal +
+ {item.score > 0 && ( +
= 80 ? 'bg-green-500' : 'bg-yellow-500'}`}> + Score {item.score} +
+ )} +
+
+
{item.category}
+ {item.details &&

{item.details}

} + +
+ {item.date} +
+ +
+
+ Prot + {item.protein} +
+
+ Carb + {item.carbs} +
+
+
+
+); + +export default HistoryCard; diff --git a/src/components/common/MacroBadge.tsx b/src/components/common/MacroBadge.tsx new file mode 100644 index 0000000..75c56e5 --- /dev/null +++ b/src/components/common/MacroBadge.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +interface MacroBadgeProps { + label: string; + value: string | number; + color: string; +} + +const MacroBadge: React.FC = ({ label, value, color }) => ( +
+ {label}: + {value} +
+); + +export default MacroBadge; diff --git a/src/components/common/StatCard.tsx b/src/components/common/StatCard.tsx new file mode 100644 index 0000000..d75ce39 --- /dev/null +++ b/src/components/common/StatCard.tsx @@ -0,0 +1,27 @@ +import React, { ReactNode } from 'react'; + +interface StatCardProps { + title: string; + value: string; + sub: string; + icon: ReactNode; + highlight?: boolean; +} + +const StatCard: React.FC = ({ title, value, sub, icon, highlight }) => ( +
+
+
+ {icon} +
+ {highlight && } +
+
+

{value}

+

{title}

+

{sub}

+
+
+); + +export default StatCard; diff --git a/src/components/dashboard/DashboardCoach.tsx b/src/components/dashboard/DashboardCoach.tsx new file mode 100644 index 0000000..d29e3d1 --- /dev/null +++ b/src/components/dashboard/DashboardCoach.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { Sparkles, Zap, ScanLine, ScanEye, BrainCircuit, TrendingUp, History, Plus, Activity } from 'lucide-react'; +import CoachResult from '@/components/coach/CoachResult'; + +interface DashboardCoachProps { + coachPlan: any; + setCoachPlan: (plan: any) => void; + coachHistory?: any[]; // Array of coach_analyses records + setIsCoachWizardOpen: (open: boolean) => void; +} + +const DashboardCoach: React.FC = ({ coachPlan, setCoachPlan, coachHistory = [], setIsCoachWizardOpen }) => { + + // ───────────────────────────────────────────────────────────────────────────── + // STATE 1: NO HISTORY (HERO / ONBOARDING) + // ───────────────────────────────────────────────────────────────────────────── + if (!coachHistory || coachHistory.length === 0) { + return ( +
+ + + {/* Sleek Modern Header */} +
+ {/* Abstract Premium Background */} +
+
+ +
+
+
+ + AI Personal Trainer +
+ +

+ Seu Corpo,
+ + Sua Melhor Versão. + +

+

+ Chega de treinos genéricos. Nossa IA analisa seu biótipo e cria um protocolo 100% científico e adaptado para você. +

+ +
+ +
+
+ + {/* Visual Stats / Tech Feel */} +
+
+
+
+ +
+ {/* Fake Data Lines */} +
+ + +
+
+
+
+
+
+
+
+ ACCURACY + 98.5% +
+
+
+
+
+
+ + {/* Features Grid - Darker/Cleaner */} +
+ {[ + { title: 'Visão Computacional', desc: 'Identifica gordura e desvios posturais.', icon: }, + { title: 'Hiper-Personalização', desc: 'Cada grama de carbo calculada para VOCÊ.', icon: }, + { title: 'Evolução Constante', desc: 'Refaça a análise a cada 30 dias.', icon: }, + ].map((feat, i) => ( +
+
+ {feat.icon} +
+

{feat.title}

+

{feat.desc}

+
+ ))} +
+ + {/* Social Proof / Trust Strip */} +
+ {[ + { label: "Protocolos Gerados", value: "10k+" }, + { label: "Precisão da IA", value: "98%" }, + { label: "Tempo Médio", value: "30 seg" }, + { label: "Avaliação", value: "4.9/5" }, + ].map((stat, i) => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+ +
+ ); + } + + // ───────────────────────────────────────────────────────────────────────────── + // STATE 2: COACH RESULT (CONTENT ONLY, HISTORY IS IN MAIN SIDEBAR) + // ───────────────────────────────────────────────────────────────────────────── + return ( +
+ {coachPlan ? ( + setCoachPlan(null)} /> + ) : ( +
+
+ +
+

Selecione uma análise

+

+ Escolha um protocolo no menu lateral ("Coach AI → Histórico") ou gere um novo. +

+ +
+ )} +
+ ); +}; + +export default DashboardCoach; diff --git a/src/components/dashboard/DashboardHistory.tsx b/src/components/dashboard/DashboardHistory.tsx new file mode 100644 index 0000000..9aa925a --- /dev/null +++ b/src/components/dashboard/DashboardHistory.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Search, Loader2 } from 'lucide-react'; +import HistoryCard from '@/components/common/HistoryCard'; +import MacroBadge from '@/components/common/MacroBadge'; + +interface DashboardHistoryProps { + history: any[]; + loadingHistory: boolean; + t: any; + fallbackImage: string; +} + +const DashboardHistory: React.FC = ({ history, loadingHistory, t, fallbackImage }) => { + return ( +
+
+

{t.dashboard.historyTitle}

+

{t.dashboard.historySubtitle}

+
+ +
+
+ + +
+
+ + {loadingHistory ? ( +
+ ) : history.length === 0 ? ( +
+

{t.dashboard.emptyHistory}

+
+ ) : ( +
+ {history.map(item => ( +
+
+ {item.category} { + const target = e.currentTarget; + if (target.src !== fallbackImage) { + target.src = fallbackImage; + } + }} + className="w-full h-full object-cover" + /> + {item.score > 0 && ( +
= 80 ? 'bg-green-500' : (item.score >= 50 ? 'bg-yellow-500' : 'bg-red-500')}`}> + {item.score} +
+ )} +
+ +
+
+
+

{item.category}

+ {item.details &&

{item.details}

} +
+ {item.date} +
+ +
+ + + + +
+
+
+ ))} +
+ )} +
+ ); +}; + +export default DashboardHistory; diff --git a/src/components/dashboard/DashboardOverview.tsx b/src/components/dashboard/DashboardOverview.tsx new file mode 100644 index 0000000..41037f7 --- /dev/null +++ b/src/components/dashboard/DashboardOverview.tsx @@ -0,0 +1,189 @@ +import React from 'react'; +import { Search, Zap, CreditCard, MessageCircle, Smartphone, QrCode, ChevronRight, Loader2, CheckCircle2, TrendingUp, Calendar, ArrowRight } from 'lucide-react'; +import HistoryCard from '@/components/common/HistoryCard'; + +interface DashboardOverviewProps { + user: { + name: string; + public_id: string; + plan: string; + plan_valid_until?: string; + }; + stats: { + totalCount: number; + avgCals: number; + }; + loadingStats: boolean; + history: any[]; + loadingHistory: boolean; + planName: string; + t: any; + whatsappUrl: string; + qrCodeUrl: string; + whatsappNumber: string; + setActiveTab: (tab: string) => void; + fallbackImage: string; +} + +const DashboardOverview: React.FC = ({ + user, + stats, + loadingStats, + history, + loadingHistory, + planName, + t, + whatsappUrl, + qrCodeUrl, + whatsappNumber, + setActiveTab, + fallbackImage +}) => { + return ( +
+ + {/* 1. Hero Section (Glassmorphism / Dark Mode Concept) */} +
+ {/* Background Blobs */} +
+
+ +
+
+
+ {planName} Member +
+

+ Olá, {user.name.split(' ')[0]} 👋 +

+

+ Vamos transformar sua saúde hoje? Acompanhe seu progresso e mantenha o foco nas suas metas. +

+ +
+ + +
+
+ + {/* Quick Stats in Hero */} +
+
+

{t.dashboard.statDishes}

+

{loadingStats ? '...' : stats.totalCount}

+
+ +12% vs mês +
+
+
+

{t.dashboard.statCals}

+

{loadingStats ? '...' : Math.round(stats.avgCals)}

+
+ Média Diária +
+
+
+
+
+ +
+ {/* 2. Recent History (Main Column) */} +
+
+

+ + {t.dashboard.recentTitle} +

+ +
+ + {loadingHistory ? ( +
+ ) : history.length === 0 ? ( +
+
+ +
+

{t.dashboard.emptyRecent}

+ +
+ ) : ( +
+ {history.slice(0, 4).map(item => ( + + ))} +
+ )} +
+ + {/* 3. Side Widget (WhatsApp Connect) */} +
+
+
+ +
+
+ QR Code +
+

Conectar WhatsApp

+

Escaneie para enviar fotos e receber análises instantâneas.

+ + +
+
+ + {/* Mini Plan Status */} +
+
+

Status do Plano

+
+
+ {planName} +
+ {user.plan_valid_until && ( +

Válido até {new Date(user.plan_valid_until).toLocaleDateString('pt-BR')}

+ )} +
+
+ +
+
+
+
+
+ ); +}; + +export default DashboardOverview; diff --git a/src/components/dashboard/DashboardSubscription.tsx b/src/components/dashboard/DashboardSubscription.tsx new file mode 100644 index 0000000..4b7bac0 --- /dev/null +++ b/src/components/dashboard/DashboardSubscription.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from 'react'; +import { CreditCard, ExternalLink, Calendar, CheckCircle2, History, AlertCircle, Loader2 } from 'lucide-react'; +import { User } from '@/types'; +import { supabase } from '@/lib/supabase'; + +interface DashboardSubscriptionProps { + user: User; + planName: string; + t: any; + handleStripePortal: () => void; +} + +const DashboardSubscription: React.FC = ({ user, planName, t, handleStripePortal }) => { + const [payments, setPayments] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchPayments = async () => { + try { + const { data, error } = await supabase + .from('payments') + .select('*') + .order('created_at', { ascending: false }); + + if (data) setPayments(data); + } catch (error) { + console.error("Error fetching payments:", error); + } finally { + setLoading(false); + } + }; + + if (user.id) { + fetchPayments(); + } + }, [user.id]); + + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': return 'bg-green-100 text-green-700'; + case 'pending': return 'bg-yellow-100 text-yellow-700'; + case 'failed': return 'bg-red-100 text-red-700'; + default: return 'bg-gray-100 text-gray-700'; + } + }; + + return ( +
+
+

{t.dashboard.subTitle}

+

{t.dashboard.subDesc}

+
+ + {/* Current Plan Card */} +
+
+ +
+ +
+
+
+
+ {t.dashboard.currentPlan} + {(user.plan === 'pro' || user.plan === 'trial') && ( + + Ativo + + )} +
+

+ {planName} +

+
+
+ + + {user.plan_valid_until + ? `${t.dashboard.validUntil} ${new Date(user.plan_valid_until).toLocaleDateString('pt-BR')}` + : t.dashboard.limitedAccess} + +
+
+
+
+
+ +
+

+ Gerencie sua assinatura e métodos de pagamento através do portal seguro. +

+ +
+
+ + {/* Payment History */} +
+
+

+ + Histórico de Pagamentos +

+
+ + {loading ? ( +
+ +
+ ) : payments.length === 0 ? ( +
+ +

Nenhum pagamento registrado ainda.

+
+ ) : ( +
+ + + + + + + + + + + {payments.map((payment) => ( + + + + + + + ))} + +
DataValorPlanoStatus
+ {new Date(payment.created_at).toLocaleDateString('pt-BR')} + + R$ {payment.amount.toFixed(2)} + + {payment.plan_type} + + + {payment.status === 'completed' ? 'Pago' : payment.status} + +
+
+ )} +
+ + {/* Upgrade Banner (Conditional) */} + {user.plan === 'free' && ( +
+
+
+

+ + Desbloqueie todo o potencial +

+

+ Obtenha análises ilimitadas, histórico completo e acesso às funcionalidades profissionais. +

+
+ +
+ {/* Decorative BG */} +
+
+ )} +
+ ); +}; + +export default DashboardSubscription; diff --git a/src/components/landing/CoachHighlight.tsx b/src/components/landing/CoachHighlight.tsx new file mode 100644 index 0000000..ef846ec --- /dev/null +++ b/src/components/landing/CoachHighlight.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { ScanEye, Dumbbell, Utensils, CheckCircle2 } from 'lucide-react'; + +interface CoachHighlightProps { + onRegister: () => void; +} + +const CoachHighlight: React.FC = ({ onRegister }) => { + return ( +
+ + {/* Background Effects */} +
+
+
+
+ +
+
+ + {/* Left: Text Content */} +
+
+ + Nova Tecnologia +
+ +

+ Seu corpo analisado
+ + pela Inteligência Artificial. + +

+ +

+ Esqueça planilhas genéricas. Nossa IA escaneia seu biótipo através de fotos e cria, em segundos, o protocolo exato de treino e dieta para sua estrutura. +

+ +
+ } title="Visão Computacional" desc="Identifica massa muscular, gordura e postura." /> + } title="Dieta Milimétrica" desc="Macros calculados para o seu metabolismo basal." /> + } title="Treino Adaptativo" desc="Periodização baseada no seu nível e objetivo." /> +
+ + +
+ + {/* Right: Visual Demo (Mockup) */} +
+
+ Trainer reviewing data + + {/* Floating Elements duplicating the 'Scanner' feel */} +
+
+ +
+
+
Scanning...
+
Ectomorfo Identificado
+
+
+ +
+
+ + Protocolo Gerado +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+ ); +}; + +const FeatureRow = ({ icon, title, desc }: any) => ( +
+
+ {icon} +
+
+

{title}

+

{desc}

+
+
+); + +export default CoachHighlight; diff --git a/src/components/landing/FAQ.tsx b/src/components/landing/FAQ.tsx new file mode 100644 index 0000000..2567806 --- /dev/null +++ b/src/components/landing/FAQ.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { ChevronDown, ChevronUp } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +const FAQ: React.FC = () => { + const { t } = useLanguage(); + + const faqs = [ + { question: t.faq.q1, answer: t.faq.a1 }, + { question: t.faq.q2, answer: t.faq.a2 }, + { question: t.faq.q3, answer: t.faq.a3 }, + { question: t.faq.q4, answer: t.faq.a4 } + ]; + + const [openIndex, setOpenIndex] = useState(null); + + return ( +
+
+
+

{t.faq.title}

+
+ +
+ {faqs.map((faq, index) => ( +
+ + +
+

+ {faq.answer} +

+
+
+ ))} +
+
+
+ ); +}; + +export default FAQ; \ No newline at end of file diff --git a/src/components/landing/Features.tsx b/src/components/landing/Features.tsx new file mode 100644 index 0000000..bf6b67a --- /dev/null +++ b/src/components/landing/Features.tsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { Flame, Scale, MessageSquare, Sparkles, ArrowLeftRight, UtensilsCrossed, CheckCircle2, Dumbbell } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { useLanguage } from '@/contexts/LanguageContext'; + +const Features: React.FC = () => { + const { t } = useLanguage(); + + const features = [ + { + icon: , + bg: "bg-emerald-50 border-emerald-100 ring-2 ring-emerald-500/10 shadow-lg shadow-emerald-500/10", + title: "Coach AI", + description: "NOVO! Seu personal trainer e nutricionista via IA. Envie fotos, descubra seu biótipo e receba treinos e dietas 100% adaptados.", + novelty: true + }, + { + icon: , + bg: "bg-red-50 border-red-100", + title: "Startup para Profissionais", + description: "Personal Trainer ou Nutricionista? Tenha seu próprio app/dashboard para gerenciar alunos, vender planos e acompanhar a evolução deles.", + novelty: true + }, + { + icon: , + bg: "bg-orange-50 border-orange-100", + title: t.features.f1Title, + description: t.features.f1Desc + }, + { + icon: , + bg: "bg-brand-50 border-brand-100", + title: t.features.f2Title, + description: t.features.f2Desc + }, + { + icon: , + bg: "bg-blue-50 border-blue-100", + title: t.features.f3Title, + description: t.features.f3Desc + }, + { + icon: , + bg: "bg-indigo-50 border-indigo-100", + title: t.features.f4Title, + description: t.features.f4Desc + }, + { + icon: , + bg: "bg-purple-50 border-purple-100", + title: t.features.f5Title, + description: t.features.f5Desc + } + ]; + + return ( +
+
+
+ +
+ + + + {t.features.guruTitle} + +

+ {t.features.mainTitle} +

+

+ {t.features.subtitle} +

+
+ +
+ {features.map((feature, index) => ( + +
+ {feature.icon} +
+
+

+ {feature.title} + {/* @ts-ignore */} + {feature.novelty && ( + Novo + )} +

+

{feature.description}

+
+
+ ))} +
+
+ + +
+ + {/* Abstract Background Blob */} +
+ + {/* Main Card Image Container */} +
+ Healthy Bowl + + {/* Gradient Overlay for Text Readability */} +
+
+ + {/* Floating UI Elements */} + + {/* Top Right: Score Badge */} +
+
+
+ 94 + SCORE +
+
+

Qualidade

+
+ + Excelente +
+
+
+
+ + {/* Bottom Left: Visual Tip (Chat Bubble style) */} +
+
+
+
+ +
+
+ {t.features.visualTipTitle} +

+ {t.features.visualTipDesc} +

+
+
+
+
+ + {/* Bottom Center: Macro Analysis Card (Simulating App UI) */} +
+
+
+
+

Salada Caesar & Frango

+

Análise em tempo real • 12:42

+
+
+ 340 kcal +
+
+ +
+
+
+ Proteína + 28g +
+
+
+
+
+
+
+ Carboidratos + 12g +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+ ); +}; + +export default Features; \ No newline at end of file diff --git a/src/components/landing/Footer.tsx b/src/components/landing/Footer.tsx new file mode 100644 index 0000000..bd4e38b --- /dev/null +++ b/src/components/landing/Footer.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { Scan, Zap, MessageCircle, Instagram, Twitter, Linkedin } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface FooterProps { + onRegister: () => void; + onNavigate?: (view: 'home' | 'faq') => void; // Optional prop to support navigation +} + +const Footer: React.FC = ({ onRegister, onNavigate }) => { + const { t } = useLanguage(); + + const handleFaqClick = (e: React.MouseEvent) => { + if (onNavigate) { + e.preventDefault(); + onNavigate('faq'); + } + }; + + const handleHomeClick = (e: React.MouseEvent, id?: string) => { + if (onNavigate) { + // Se tiver navegação, garante que estamos na home primeiro + if (!id) { + e.preventDefault(); + onNavigate('home'); + } + } + }; + + return ( +
+ ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/components/landing/Header.tsx b/src/components/landing/Header.tsx new file mode 100644 index 0000000..c651cc5 --- /dev/null +++ b/src/components/landing/Header.tsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect } from 'react'; +import { Scan, Menu, X, Zap, ArrowRight, Globe, Calculator } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface HeaderProps { + onRegister: () => void; + onLogin: (context?: 'user' | 'professional') => void; + onOpenTools: () => void; + onNavigate?: (view: 'home' | 'faq') => void; +} + +const Header: React.FC = ({ onRegister, onLogin, onOpenTools, onNavigate }) => { + const [isScrolled, setIsScrolled] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [langMenuOpen, setLangMenuOpen] = useState(false); + const { language, setLanguage, t } = useLanguage(); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 20); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const navLinks = [ + { name: t.header.howItWorks, id: 'how-it-works' }, + { name: t.header.features, id: 'features' }, + { name: t.header.pricing, id: 'pricing' }, + ]; + + const toggleLang = (lang: 'pt' | 'en' | 'es') => { + setLanguage(lang); + setLangMenuOpen(false); + }; + + const handleScrollTo = (id: string) => { + // Se a função de navegação for fornecida, garante que vamos para a home primeiro + if (onNavigate) { + onNavigate('home'); + // Pequeno delay para permitir a renderização da home antes de scrollar + setTimeout(() => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + }, 100); + } else { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + } + setMobileMenuOpen(false); + }; + + const handleLogoClick = (e: React.MouseEvent) => { + e.preventDefault(); + if (onNavigate) onNavigate('home'); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + return ( +
+
+ {/* Logo Professional */} + +
+ + +
+
+ + FoodSnap.ai + + + {t.header.slogan} + +
+
+ + {/* Desktop Nav */} + + + {/* Mobile Menu Toggle */} + +
+ + {/* Mobile Menu */} + {mobileMenuOpen && ( +
+ {navLinks.map((link) => ( + + ))} + + + +
+ + + +
+ +
+ + +
+
+ )} +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/src/components/landing/Hero.tsx b/src/components/landing/Hero.tsx new file mode 100644 index 0000000..0f4c1d5 --- /dev/null +++ b/src/components/landing/Hero.tsx @@ -0,0 +1,360 @@ +import React, { useState, useRef } from 'react'; +import { ArrowRight, MessageCircle, Scan, Zap, Camera, Lightbulb, Sparkles, Upload, X } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface HeroProps { + onRegister: () => void; +} + +const Hero: React.FC = ({ onRegister }) => { + const [demoState, setDemoState] = useState<'initial' | 'analyzing' | 'result'>('initial'); + const [userImage, setUserImage] = useState(null); + const [showDemoInstruction, setShowDemoInstruction] = useState(false); + const fileInputRef = useRef(null); + const { t } = useLanguage(); + + const handleDemoClick = () => { + setShowDemoInstruction(true); + }; + + const handleTriggerUpload = () => { + fileInputRef.current?.click(); + }; + + const scrollToPricing = (e: React.MouseEvent) => { + e.preventDefault(); + const element = document.getElementById('pricing'); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + }; + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + const imageUrl = URL.createObjectURL(file); + setUserImage(imageUrl); + setShowDemoInstruction(false); // Fecha o modal + setDemoState('analyzing'); + + // Simulate network delay and processing + setTimeout(() => { + setDemoState('result'); + }, 3500); // Um pouco mais de tempo para ver o "robô pensando" + } + }; + + return ( +
+ {/* Hidden Input for Demo */} + + + {/* Modern Background */} +
+
+
+ +
+
+ + {/* Text Content */} + +
+ + + + + NOVO: Coach AI 2.0 - Treino & Dieta +
+ +

+ {t.hero.titleStart}
+ + {t.hero.titleHighlight} + +

+ +

+ {t.hero.subtitle} +

+ +
+ + +
+ +
+
+
+ {[1, 2, 3].map((i) => ( +
+ user +
+ ))} +
+ {t.hero.stats} +
+ +
+ + {t.hero.analysis} +
+
+
+ + {/* Visual Element - Modern Mockup Interactive */} + +
+ {/* Notch */} +
+ + {/* Screen */} +
+ {/* Header Mockup */} +
+
+
+ +
+
+

FoodSnap

+
+ +

Online

+
+
+
+
+ + {/* Chat Area */} +
+ {/* User Message (Image) */} + + +
+ Meal +
+

12:30

+
+
+ + {/* Loading State / Robot Message */} + {demoState === 'analyzing' && ( + +
+
+
+ + + +
+ {t.hero.demoProcessing} +
+
+
+ )} + + {/* AI Response */} + {(demoState === 'initial' || demoState === 'result') && ( + +
+ {/* Header Analysis */} +
+
+
+ +
+ {t.hero.analysis} +
+ + Score A + +
+ +
+ {/* Macros */} +
+
+ + {demoState === 'initial' ? '485' : '520'} kcal + + + {demoState === 'initial' ? 'High Protein' : 'Balanced'} + +
+ +
+
+

Prot

+

{demoState === 'initial' ? '32g' : '28g'}

+
+
+

Carb

+

{demoState === 'initial' ? '45g' : '55g'}

+
+
+

Gord

+

{demoState === 'initial' ? '12g' : '18g'}

+
+
+
+ + {/* Insights */} +
+
+ +

+ {t.hero.demoAdvice} {t.hero.demoAdviceText} +

+
+
+
+
+
+ +

Powered by FoodSnap

+
+
+ )} +
+ + {/* Input Area (Visual Only) */} +
+
+ +
+
+ ... +
+
+
+
+ + {/* Floating Elements */} + {demoState === 'initial' && ( +
+
+
+ +
+
+

Detected

+

Salmon Bowl

+
+
+
+ )} +
+ +
+
+ + {/* Demo Instruction Modal */} + + {showDemoInstruction && ( +
+ setShowDemoInstruction(false)} + className="absolute inset-0 bg-gray-950/70 backdrop-blur-sm" + /> + + + +
+ +
+ +

{t.hero.demoModalTitle}

+

+ {t.hero.demoModalDesc} +

+ + +
+
+ )} +
+
+ ); +}; + +export default Hero; \ No newline at end of file diff --git a/src/components/landing/HowItWorks.tsx b/src/components/landing/HowItWorks.tsx new file mode 100644 index 0000000..3c2e56b --- /dev/null +++ b/src/components/landing/HowItWorks.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Camera, Send, Activity, ChevronRight } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +const HowItWorks: React.FC = () => { + const { t } = useLanguage(); + + const steps = [ + { + icon: , + title: t.howItWorks.step1Title, + description: t.howItWorks.step1Desc + }, + { + icon: , + title: t.howItWorks.step2Title, + description: t.howItWorks.step2Desc + }, + { + icon: , + title: t.howItWorks.step3Title, + description: t.howItWorks.step3Desc + } + ]; + + return ( +
+ {/* Background Pattern */} +
+ +
+
+

{t.howItWorks.title}

+

+ {t.howItWorks.subtitle} +

+
+ +
+ + {steps.map((step, index) => ( +
+
+ {step.icon} +
+ + {/* Connector */} + {index < steps.length - 1 && ( +
+ +
+ )} + +

{step.title}

+

{step.description}

+
+ ))} +
+
+
+ ); +}; + +export default HowItWorks; \ No newline at end of file diff --git a/src/components/landing/Pricing.tsx b/src/components/landing/Pricing.tsx new file mode 100644 index 0000000..43d4ef1 --- /dev/null +++ b/src/components/landing/Pricing.tsx @@ -0,0 +1,184 @@ +import React from 'react'; +import { Check, ShieldCheck, Sparkles, Star, Gift } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface PricingProps { + onRegister: (plan: string) => void; +} + +const Pricing: React.FC = ({ onRegister }) => { + const { t } = useLanguage(); + + return ( +
+ {/* Background Decor */} +
+ +
+
+

{t.pricing.title}

+

{t.pricing.subtitle}

+
+ + {/* Free Plan Banner */} +
+
+
+
+ +
+
+

{t.pricing.freeTierTitle}

+

{t.pricing.freeTierDesc}

+
+
+ +
+
+ +
+ + {/* Plan: Monthly (Was Starter in structure, now Monthly) */} +
+

{t.pricing.plans.monthly.title}

+
+ {t.pricing.plans.monthly.price} + {t.pricing.plans.monthly.period} +
+

{t.pricing.plans.monthly.description}

+

{t.pricing.plans.monthly.billingInfo}

+ +
    + {t.pricing.plans.monthly.features.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+ + +
+ + {/* Plan: Annual (Highlighted) */} +
+
+
+
+

+ {t.pricing.plans.annual.title} + {t.pricing.plans.annual.savings} +

+

{t.pricing.plans.annual.description}

+
+
+ +
+
+ +
+ {t.pricing.plans.annual.price} + {t.pricing.plans.annual.period} +
+

{t.pricing.plans.annual.billingInfo}

+ +
+ +
    + {t.pricing.plans.annual.features.map((item, i) => ( +
  • +
    + +
    + {item} +
  • + ))} +
+ + +
+
+ + {/* Plan: Quarterly */} +
+

{t.pricing.plans.quarterly.title}

+
+ {t.pricing.plans.quarterly.price} + {t.pricing.plans.quarterly.period} +
+

{t.pricing.plans.quarterly.description}

+

{t.pricing.plans.quarterly.billingInfo}

+ +
    + {t.pricing.plans.quarterly.features.map((item, i) => ( +
  • + + {item} +
  • + ))} +
+ + +
+ +
+ + {/* --- PROFESSIONAL PLAN SECTION --- */} +
+
+ {/* Abstract Shapes */} +
+ +
+
+
+ Para Nutris e Personais +
+

Área Profissional

+

+ Sistema completo para gestão de alunos, treinos e dietas. +

+
+ +
+ + Em Breve + + +
+
+
+
+ +
+
+ ); +}; + +export default Pricing; \ No newline at end of file diff --git a/src/components/landing/Testimonials.tsx b/src/components/landing/Testimonials.tsx new file mode 100644 index 0000000..50e6e14 --- /dev/null +++ b/src/components/landing/Testimonials.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { Star, Quote } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +const Testimonials: React.FC = () => { + const { t } = useLanguage(); + + const reviews = [ + { + name: "Rafael Silva", + role: t.testimonials.r1Role, + image: "https://picsum.photos/100/100?random=10", + content: t.testimonials.r1Content + }, + { + name: "Dra. Mariana Costa", + role: t.testimonials.r2Role, + image: "https://picsum.photos/100/100?random=11", + content: t.testimonials.r2Content + }, + { + name: "Lucas Mendes", + role: t.testimonials.r3Role, + image: "https://picsum.photos/100/100?random=12", + content: t.testimonials.r3Content + } + ]; + + return ( +
+
+
+

{t.testimonials.title}

+

{t.testimonials.subtitle}

+
+ +
+ {reviews.map((review, index) => ( +
+ + +
+ {[...Array(5)].map((_, i) => )} +
+ +

"{review.content}"

+ +
+ {review.name} +
+

{review.name}

+

{review.role}

+
+
+
+ ))} +
+
+
+ ); +}; + +export default Testimonials; \ No newline at end of file diff --git a/src/components/layout/MobileNav.tsx b/src/components/layout/MobileNav.tsx new file mode 100644 index 0000000..b1254b5 --- /dev/null +++ b/src/components/layout/MobileNav.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { LayoutDashboard, History, CreditCard, Dumbbell } from 'lucide-react'; + +interface MobileNavProps { + activeTab: string; + setActiveTab: (tab: any) => void; + t: any; +} + +const MobileNav: React.FC = ({ activeTab, setActiveTab, t }) => { + return ( +
+ + + + +
+ ); +}; + +export default MobileNav; diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..4ee5ff4 --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; +import { LayoutDashboard, History, CreditCard, Dumbbell, ShieldAlert, BrainCircuit, LogOut, Zap, ChevronDown, ChevronRight, Calendar } from 'lucide-react'; +import { User } from '@/types'; + +interface SidebarProps { + user: User; + activeTab: string; + setActiveTab: (tab: string) => void; + onLogout: () => void; + onOpenAdmin?: () => void; + onOpenPro?: () => void; + t: any; // Translation object + coachHistory?: any[]; // Array of coach_analyses records + onSelectCoachPlan?: (plan: any) => void; +} + +const Sidebar: React.FC = ({ user, activeTab, setActiveTab, onLogout, onOpenAdmin, onOpenPro, t, coachHistory, onSelectCoachPlan }) => { + const [isCoachExpanded, setIsCoachExpanded] = useState(false); + + const handleCoachClick = () => { + // If has history, toggle submenu + if (coachHistory && coachHistory.length > 0) { + setIsCoachExpanded(!isCoachExpanded); + } + setActiveTab('coach'); + }; + + return ( + + ); +}; + +interface SidebarItemProps { + icon: React.ReactNode; + label: string; + active: boolean; + onClick: () => void; + hasSubmenu?: boolean; + isExpanded?: boolean; +} + +const SidebarItem = ({ icon, label, active, onClick, hasSubmenu, isExpanded }: SidebarItemProps) => ( + +); + +export default Sidebar; + diff --git a/src/components/modals/CalculatorsModal.tsx b/src/components/modals/CalculatorsModal.tsx new file mode 100644 index 0000000..b4c1250 --- /dev/null +++ b/src/components/modals/CalculatorsModal.tsx @@ -0,0 +1,659 @@ +import React, { useState } from 'react'; +import { X, Calculator, Droplets, Activity, Scale, ChevronRight, ArrowRight, Check, Dumbbell, Flame, Heart, Percent } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface CalculatorsModalProps { + isOpen: boolean; + onClose: () => void; +} + +type ToolType = 'bmi' | 'water' | 'bmr' | 'tdee' | 'orm' | 'bodyfat' | 'hr'; + +const CalculatorsModal: React.FC = ({ isOpen, onClose }) => { + const [activeTool, setActiveTool] = useState('bmi'); + const { t } = useLanguage(); + + if (!isOpen) return null; + + return ( + +
+ {/* Backdrop Overlay */} + + + {/* Modal Container */} + + {/* Close Button (Mobile) */} + + + {/* Sidebar Navigation */} + + + {/* Main Content Area */} +
+ {/* Close Button (Desktop) */} + + +
+ + + {activeTool === 'bmi' && } + {activeTool === 'water' && } + {activeTool === 'bmr' && } + {activeTool === 'tdee' && } + {activeTool === 'orm' && } + {activeTool === 'bodyfat' && } + {activeTool === 'hr' && } + + +
+
+
+
+
+ ); +}; + +// --- Components de Navegação --- + +const NavButton = ({ active, onClick, icon, label, desc }: any) => ( + +); + +// --- Calculadoras (Existentes + Novas) --- + +const BMICalculator = ({ t }: any) => { + const [weight, setWeight] = useState(''); + const [height, setHeight] = useState(''); + const [bmi, setBmi] = useState(null); + + const calculate = () => { + if (weight && height) { + const h = parseFloat(height) / 100; + const val = parseFloat(weight) / (h * h); + setBmi(parseFloat(val.toFixed(1))); + } + }; + + const getStatus = (val: number) => { + if (val < 18.5) return { label: 'Abaixo do peso', color: 'text-blue-500', bg: 'bg-blue-500', range: 0 }; + if (val < 25) return { label: 'Peso ideal', color: 'text-green-500', bg: 'bg-green-500', range: 33 }; + if (val < 30) return { label: 'Sobrepeso', color: 'text-yellow-500', bg: 'bg-yellow-500', range: 66 }; + return { label: 'Obesidade', color: 'text-red-500', bg: 'bg-red-500', range: 100 }; + }; + + const status = bmi ? getStatus(bmi) : null; + + return ( +
+
+

{t.tools.bmi.title}

+

{t.tools.bmi.desc}

+
+ +
+ + +
+ + + + {bmi && status && ( +
+
+

Seu Resultado

+
{bmi}
+ + {status.label} + +
+ + {/* Visual Bar */} +
+
+
+
+
+
+
+
+
+
+
+
+ 18.5 + 25.0 + 30.0 +
+
+ )} +
+ ); +}; + +const WaterCalculator = ({ t }: any) => { + const [weight, setWeight] = useState(''); + const [liters, setLiters] = useState(null); + + const calculate = () => { + if (weight) { + const val = parseFloat(weight) * 0.035; + setLiters(parseFloat(val.toFixed(1))); + } + }; + + return ( +
+
+

{t.tools.water.title}

+

{t.tools.water.desc}

+
+ +
+
+ + +
+ + {/* Visual Bottle */} +
+
+
+
+
+ + {liters && {liters}L} +
+
+
+ + {liters && ( +
+

Meta Diária

+

{liters} L

+
+ )} +
+ ); +}; + +const BMRCalculator = ({ t }: any) => { + const [gender, setGender] = useState<'male' | 'female'>('male'); + const [weight, setWeight] = useState(''); + const [height, setHeight] = useState(''); + const [age, setAge] = useState(''); + const [bmr, setBmr] = useState(null); + + const calculate = () => { + if (weight && height && age) { + let val = (10 * parseFloat(weight)) + (6.25 * parseFloat(height)) - (5 * parseFloat(age)); + val = gender === 'male' ? val + 5 : val - 161; + setBmr(Math.round(val)); + } + }; + + return ( +
+
+

{t.tools.bmr.title}

+

{t.tools.bmr.desc}

+
+ +
+ + +
+ +
+ + + +
+ + + + {bmr && ( +
+
+

Gasto em Repouso

+

Calorias que você queima parado.

+
+
+

{bmr}

+

kcal / dia

+
+
+ )} +
+ ); +}; + +const TDEECalculator = ({ t }: any) => { + const [gender, setGender] = useState<'male' | 'female'>('male'); + const [weight, setWeight] = useState(''); + const [height, setHeight] = useState(''); + const [age, setAge] = useState(''); + const [activity, setActivity] = useState(1.2); + const [tdee, setTdee] = useState(null); + + const calculate = () => { + if (weight && height && age) { + let bmr = (10 * parseFloat(weight)) + (6.25 * parseFloat(height)) - (5 * parseFloat(age)); + bmr = gender === 'male' ? bmr + 5 : bmr - 161; + setTdee(Math.round(bmr * activity)); + } + }; + + const activityLevels = [ + { val: 1.2, label: t.tools.tdee.sedentary }, + { val: 1.375, label: t.tools.tdee.light }, + { val: 1.55, label: t.tools.tdee.moderate }, + { val: 1.725, label: t.tools.tdee.active }, + { val: 1.9, label: t.tools.tdee.veryActive }, + ]; + + return ( +
+
+

{t.tools.tdee.title}

+

{t.tools.tdee.desc}

+
+ +
+ + +
+ +
+ + + +
+ +
+ +
+ {activityLevels.map((lvl) => ( + + ))} +
+
+ + + + {tdee && ( +
+
+

Gasto Calórico Total

+

Energia necessária para manter seu peso atual.

+
+
+

{tdee}

+

kcal / dia

+
+
+ )} +
+ ); +}; + +const ORMCalculator = ({ t }: any) => { + const [lift, setLift] = useState(''); + const [reps, setReps] = useState(''); + const [orm, setOrm] = useState(null); + + const calculate = () => { + if (lift && reps) { + // Epley Formula + const w = parseFloat(lift); + const r = parseFloat(reps); + if (r === 1) { + setOrm(w); + } else { + const val = w * (1 + r / 30); + setOrm(Math.round(val)); + } + } + }; + + return ( +
+
+

{t.tools.orm.title}

+

{t.tools.orm.desc}

+
+ +
+ + +
+ + + + {orm && ( +
+
+ Sua Força Máxima Estimada + +
+
+ {orm} + kg +
+
+
+ 90% + {Math.round(orm * 0.9)}kg +
+
+ 70% (Hipertrofia) + {Math.round(orm * 0.7)}kg +
+
+ 50% + {Math.round(orm * 0.5)}kg +
+
+
+ )} +
+ ); +}; + +const BodyFatCalculator = ({ t }: any) => { + const [gender, setGender] = useState<'male' | 'female'>('male'); + const [waist, setWaist] = useState(''); + const [neck, setNeck] = useState(''); + const [hip, setHip] = useState(''); + const [height, setHeight] = useState(''); + const [bf, setBf] = useState(null); + + const calculate = () => { + // US Navy Method + const h = parseFloat(height); + const w = parseFloat(waist); + const n = parseFloat(neck); + + if (gender === 'male' && h && w && n) { + const res = 495 / (1.0324 - 0.19077 * Math.log10(w - n) + 0.15456 * Math.log10(h)) - 450; + setBf(parseFloat(res.toFixed(1))); + } else if (gender === 'female' && h && w && n && hip) { + const hi = parseFloat(hip); + const res = 495 / (1.29579 - 0.35004 * Math.log10(w + hi - n) + 0.22100 * Math.log10(h)) - 450; + setBf(parseFloat(res.toFixed(1))); + } + }; + + return ( +
+
+

{t.tools.bodyfat.title}

+

{t.tools.bodyfat.desc}

+
+ +
+ + +
+ +
+ + + + {gender === 'female' && ( + + )} +
+ + + + {bf && ( +
+

Gordura Corporal Estimada

+

{bf}%

+
+ Método US Navy +
+
+ )} +
+ ); +}; + +const HeartRateCalculator = ({ t }: any) => { + const [age, setAge] = useState(''); + const [maxHr, setMaxHr] = useState(null); + + const calculate = () => { + if (age) { + const val = 220 - parseFloat(age); + setMaxHr(val); + } + }; + + return ( +
+
+

{t.tools.hr.title}

+

{t.tools.hr.desc}

+
+ +
+ +
+ + + + {maxHr && ( +
+
+ Frequência Máxima Teórica +
{maxHr} bpm
+
+ +
+ + + + +
+
+ )} +
+ ); +}; + +const ZoneBar = ({ zone, color, range, val, label }: any) => ( +
+
Z{zone}
+
+
+ {label} + {range} +
+
{val} bpm
+
+
+); + + +const BigInput = ({ label, value, onChange, placeholder, unit }: any) => ( +
+ +
+ onChange(e.target.value)} + className="w-full px-4 py-4 rounded-xl bg-gray-50 border border-gray-200 text-gray-900 font-bold text-lg focus:border-brand-500 focus:ring-2 focus:ring-brand-200 outline-none transition-all placeholder-gray-300" + placeholder={placeholder} + /> + {unit && {unit}} +
+
+); + +export default CalculatorsModal; \ No newline at end of file diff --git a/src/components/modals/RegistrationModal.tsx b/src/components/modals/RegistrationModal.tsx new file mode 100644 index 0000000..cfa95ac --- /dev/null +++ b/src/components/modals/RegistrationModal.tsx @@ -0,0 +1,409 @@ +import React, { useState, useEffect } from 'react'; +import { X, ArrowRight, Loader2, Lock, Mail, User as UserIcon, Eye, EyeOff, Phone, CheckCircle, AlertCircle } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { supabase } from '@/lib/supabase'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface RegistrationModalProps { + isOpen: boolean; + onClose: () => void; + plan: string; + mode: 'login' | 'register'; + isCompletingProfile?: boolean; + onSuccess: () => void; +} + +const onlyDigits = (s: string) => (s || '').replace(/\D/g, ''); + +const GoogleIcon = () => ( + + + + + + +); + +const RegistrationModal: React.FC = ({ + isOpen, + onClose, + plan, + mode, + isCompletingProfile = false, + onSuccess +}) => { + const { t } = useLanguage(); + const [activeMode, setActiveMode] = useState<'login' | 'register'>(mode); + const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + // Feedback states + const [errorMsg, setErrorMsg] = useState(null); + const [successMsg, setSuccessMsg] = useState(null); + + const [formData, setFormData] = useState({ + name: '', + email: '', + phone: '', + password: '' + }); + + useEffect(() => { + if (isOpen) { + if (isCompletingProfile) { + // Se estiver completando perfil, busca dados da sessão atual + supabase.auth.getUser().then(({ data }) => { + if (data.user) { + setFormData(prev => ({ + ...prev, + email: data.user?.email || '', + name: data.user?.user_metadata?.full_name || data.user?.user_metadata?.name || '', + })); + } + }); + } else { + setActiveMode(mode); + } + + setLoading(false); + setErrorMsg(null); + setSuccessMsg(null); + setShowPassword(false); + if (!isCompletingProfile) setFormData({ name: '', email: '', phone: '', password: '' }); + } + }, [isOpen, mode, isCompletingProfile]); + + const friendlyAuthError = (msg: string) => { + const m = (msg || '').toLowerCase(); + // Simplified error mapping, could be extended to dictionary if strict multi-lang errors needed + if (m.includes('database error')) return 'Server Error.'; + if (m.includes('already registered') || m.includes('user already registered')) return 'Email already registered.'; + if (m.includes('invalid login credentials')) return 'Invalid credentials.'; + if (m.includes('password should be at least')) return 'Password too short (min 6 chars).'; + if (m.includes('email not confirmed')) return 'Please confirm your email.'; + if (m.includes('duplicate key') || m.includes('already exists')) return 'Phone or Email already in use.'; + return msg || 'An error occurred.'; + }; + + const handleGoogleLogin = async () => { + setLoading(true); + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: window.location.origin + } + }); + + if (error) { + setErrorMsg(friendlyAuthError(error.message)); + setLoading(false); + } + // Se der certo, o usuário sai da página, então não precisamos setar loading false + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setErrorMsg(null); + setSuccessMsg(null); + + try { + // --- MODO: COMPLETAR PERFIL (Vindo do Google) --- + if (isCompletingProfile) { + const phoneDigits = onlyDigits(formData.phone); + const fullName = formData.name.trim(); + + if (!fullName) throw new Error(t.auth.errorRequired); + if (!phoneDigits || phoneDigits.length < 10) throw new Error(t.auth.errorPhone); + + // RPC para salvar profile + const { error: rpcError } = await supabase.rpc('register_user_profile', { + p_full_name: fullName, + p_phone: phoneDigits, + p_email: formData.email // Email já vem do Google/Sessão + }); + + if (rpcError) { + console.error(rpcError); + throw new Error('Error saving profile. Try again.'); + } + + setSuccessMsg(t.auth.successLogin); + setTimeout(() => onSuccess(), 1500); + return; + } + + // --- MODO: REGISTRO NORMAL --- + if (activeMode === 'register') { + const email = (formData.email || '').trim().toLowerCase(); + const fullName = (formData.name || '').trim(); + const phoneDigits = onlyDigits(formData.phone); + + if (!fullName) throw new Error(t.auth.errorRequired); + if (!email) throw new Error(t.auth.errorRequired); + if (!phoneDigits) throw new Error(t.auth.errorRequired); + if (phoneDigits.length < 10) throw new Error(t.auth.errorPhone); + + const { data: authData, error: authError } = await supabase.auth.signUp({ + email, + password: formData.password, + options: { emailRedirectTo: window.location.origin } + }); + + if (authError) throw authError; + + if (!authData.user) { + setSuccessMsg(t.auth.successRegister); + setTimeout(() => onSuccess(), 2000); + return; + } + + const { error: rpcError } = await supabase.rpc('register_user_profile', { + p_full_name: fullName, + p_phone: phoneDigits, + p_email: email + }); + + if (rpcError) throw new Error('Phone/Email already in use.'); + + setSuccessMsg(t.auth.successRegister); + setTimeout(() => onSuccess(), 1500); + return; + } + + // --- MODO: LOGIN NORMAL --- + const { error: loginError } = await supabase.auth.signInWithPassword({ + email: (formData.email || '').trim().toLowerCase(), + password: formData.password + }); + + if (loginError) throw loginError; + + setSuccessMsg(t.auth.successLogin); + setTimeout(() => onSuccess(), 1500); + + } catch (error: any) { + console.error('Auth Error:', error); + setLoading(false); + const rawMsg = error?.message || error?.error_description || 'Error'; + setErrorMsg(friendlyAuthError(rawMsg)); + } + }; + + const title = isCompletingProfile ? t.auth.completeProfile : (activeMode === 'login' ? t.auth.welcomeBack : t.auth.createAccount); + const subtitle = isCompletingProfile ? t.auth.confirmPhone : (activeMode === 'login' ? t.auth.accessPanel : t.auth.fillToAccess); + + return ( + + {isOpen && ( +
+ + + +
+
+
+

{title}

+

{subtitle}

+
+ {!isCompletingProfile && ( + + )} +
+ + {errorMsg && ( + + + {errorMsg} + + )} + + {successMsg && ( + + + {successMsg} + + )} + +
+ + {/* Campos para Registro ou Completar Perfil */} + {(activeMode === 'register' || isCompletingProfile) && ( +
+ +
+ + setFormData({ ...formData, name: e.target.value })} + /> +
+
+ )} + + {(activeMode === 'register' || isCompletingProfile) && ( +
+ +
+ + setFormData({ ...formData, phone: e.target.value })} + /> +
+

{t.auth.phoneHelper}

+
+ )} + + {!isCompletingProfile && ( + <> +
+ +
+ + setFormData({ ...formData, email: e.target.value })} + /> +
+
+ +
+ +
+ + setFormData({ ...formData, password: e.target.value })} + /> + +
+
+ + )} + +
+ +
+
+ + {/* Google Button & Toggle Mode */} + {!isCompletingProfile && ( +
+
+
+
{t.auth.or}
+
+ + + +
+

+ {activeMode === 'login' ? t.auth.noAccount : t.auth.hasAccount} + +

+
+
+ )} +
+ +
+ + {t.auth.security} +
+
+
+ )} +
+ ); +}; + +export default RegistrationModal; \ No newline at end of file diff --git a/src/components/professional/ProfessionalModule.tsx b/src/components/professional/ProfessionalModule.tsx new file mode 100644 index 0000000..275883d --- /dev/null +++ b/src/components/professional/ProfessionalModule.tsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import { + Briefcase, + Users, + DollarSign, + Settings, + Plus, + Edit2, + Trash2, + ChevronRight, + Award, + CheckCircle2, + Calendar, + MessageSquare +} from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +const ProfessionalModule: React.FC = () => { + // Mock Data for MVP + const [services, setServices] = useState([ + { id: 1, title: 'Consultoria Online Mensal', price: 15000, active: true, clients: 12 }, + { id: 2, title: 'Treino Hipertrofia Individual', price: 8990, active: true, clients: 5 }, + { id: 3, title: 'Avaliação Física Presencial', price: 12000, active: false, clients: 0 } + ]); + + const [clients] = useState([ + { id: 1, name: 'João Silva', plan: 'Consultoria Online', status: 'active', lastCheckin: 'Hoje' }, + { id: 2, name: 'Maria Oliveira', plan: 'Treino Hipertrofia', status: 'active', lastCheckin: 'Ontem' }, + { id: 3, name: 'Carlos Santos', plan: 'Consultoria Online', status: 'pending', lastCheckin: '3 dias atrás' } + ]); + + const { t } = useLanguage(); // Assuming we might add translations later, but sticking to PT for hardcoded MVP parts + + const formatCurrency = (val: number) => { + return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(val / 100); + }; + + return ( +
+ {/* Header Section */} +
+
+

+ Área Profissional BETA +

+

Gerencie seus serviços, alunos e faturamento em um só lugar.

+
+
+ + +
+
+ + {/* KPI Cards */} +
+
+
+ +
+
+

Alunos Ativos

+

17

+
+
+
+
+ +
+
+

Faturamento (Mês)

+

R$ 2.450,00

+
+
+
+
+ +
+
+

Serviços Ativos

+

3

+
+
+
+ +
+ + {/* Meus Serviços */} +
+
+
+

+ Meus Serviços +

+
+
+ {services.map(service => ( +
+
+
+

{service.title}

+ {service.active ? ( + Ativo + ) : ( + Inativo + )} +
+

+ {formatCurrency(service.price)} • {service.clients} alunos inscritos +

+
+
+ + +
+
+ ))} +
+
+ +
+
+ + {/* Quick Tips / Upsell */} +
+
+

Aumente suas vendas

+

+ Profissionais que detalham bem seus serviços e usam fotos profissionais vendem 3x mais. + Configure seu perfil público agora mesmo. +

+ +
+
+
+
+ + {/* Meus Alunos (Sidebar) */} +
+
+
+

+ Alunos Recentes +

+
+
+ {clients.map(client => ( +
+
+
+ {client.name.substring(0, 2).toUpperCase()} +
+
+

{client.name}

+

{client.plan}

+
+ {client.status === 'active' ? ( + + ) : ( +
+ )} +
+
+ {client.lastCheckin} + +
+
+ ))} +
+
+ +
+
+
+
+
+ ); +}; + +export default ProfessionalModule; diff --git a/src/components/professional/common/PlaceholderModule.tsx b/src/components/professional/common/PlaceholderModule.tsx new file mode 100644 index 0000000..3e89fc1 --- /dev/null +++ b/src/components/professional/common/PlaceholderModule.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export const PlaceholderModule = ({ title, desc, icon }: any) => ( +
+
+ {icon} +
+

{title}

+

{desc}

+ +
+); diff --git a/src/components/professional/common/StatsCard.tsx b/src/components/professional/common/StatsCard.tsx new file mode 100644 index 0000000..3237bba --- /dev/null +++ b/src/components/professional/common/StatsCard.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const StatsCard = ({ label, value, trend, alert }: any) => ( +
+

{label}

+

{value}

+

{trend}

+
+); diff --git a/src/components/professional/dashboard/Overview.tsx b/src/components/professional/dashboard/Overview.tsx new file mode 100644 index 0000000..5e6da9e --- /dev/null +++ b/src/components/professional/dashboard/Overview.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { StatsCard } from '../common/StatsCard'; + +export const OverviewMock = () => ( +
+
+ + + +
+ +
+

Atividade Recente

+
+ {[1, 2, 3].map(i => ( +
+
US
+
+

João Silva finalizou o treino "Hipertrofia A"

+

Há 2 horas • Duração: 45min

+
+
+ ))} +
+
+
+); diff --git a/src/components/professional/dashboard/StudentsList.tsx b/src/components/professional/dashboard/StudentsList.tsx new file mode 100644 index 0000000..35d04b4 --- /dev/null +++ b/src/components/professional/dashboard/StudentsList.tsx @@ -0,0 +1,231 @@ +import React, { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; +import { User } from '@/types'; +import { Search, PlusCircle, Users, X, Calendar } from 'lucide-react'; + +interface StudentsListProps { + user: User; +} + +export const StudentsList: React.FC = ({ user }) => { + const [students, setStudents] = useState([]); + const [loading, setLoading] = useState(true); + const [isCreateOpen, setIsCreateOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + // New Student Form State + const [newName, setNewName] = useState(''); + const [newEmail, setNewEmail] = useState(''); + const [newPhone, setNewPhone] = useState(''); + + useEffect(() => { + fetchStudents(); + }, [user.id]); + + const fetchStudents = async () => { + try { + setLoading(true); + + // First, ensure the professional profile exists (Auto-create logic if missing) + const { data: proProfile } = await supabase + .from('professionals') + .select('id') + .eq('id', user.id) + .maybeSingle(); + + if (!proProfile) { + // Auto-create professional profile if it doesn't exist (First Login) + await supabase.from('professionals').insert({ + id: user.id, + business_name: user.name, + primary_color: '#059669' + }); + } + + const { data, error } = await supabase + .from('pro_students') + .select('*') + .eq('professional_id', user.id) + .order('created_at', { ascending: false }); + + if (error) throw error; + setStudents(data || []); + } catch (error) { + console.error('Error fetching students:', error); + } finally { + setLoading(false); + } + }; + + const handleCreateStudent = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const { error } = await supabase.from('pro_students').insert({ + professional_id: user.id, + name: newName, + email: newEmail, + phone: newPhone, + status: 'active' + }); + + if (error) throw error; + + setIsCreateOpen(false); + setNewName(''); + setNewEmail(''); + setNewPhone(''); + fetchStudents(); // Refresh list + } catch (error) { + console.error('Error creating student:', error); + } + }; + + const filteredStudents = students.filter(s => + s.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (s.email && s.email.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + return ( +
+ {/* Header / Actions */} +
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ +
+
+ + {/* List */} + {loading ? ( +
Carregando alunos...
+ ) : filteredStudents.length === 0 ? ( +
+
+ +
+

Nenhum aluno encontrado

+

Comece adicionando seu primeiro aluno.

+ +
+ ) : ( +
+ + + + + + + + + + + + {filteredStudents.map(student => ( + + + + + + + + ))} + +
AlunoStatusContatoEntrou em
+
+ {student.name.substring(0, 2)} +
+ {student.name} +
+ + {student.status === 'active' ? 'Ativo' : student.status === 'pending' ? 'Pendente' : 'Inativo'} + + +
+ {student.email} + {student.phone} +
+
{new Date(student.created_at).toLocaleDateString('pt-BR')} + +
+
+ )} + + {/* Create Modal */} + {isCreateOpen && ( +
+
+
+

Novo Aluno

+ +
+ +
+
+ + setNewName(e.target.value)} + /> +
+
+ + setNewEmail(e.target.value)} + /> +
+
+ + setNewPhone(e.target.value)} + /> +
+ + +
+
+
+ )} +
+ ); +}; diff --git a/src/components/professional/dashboard/Workouts.tsx b/src/components/professional/dashboard/Workouts.tsx new file mode 100644 index 0000000..f649c61 --- /dev/null +++ b/src/components/professional/dashboard/Workouts.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Dumbbell, Settings, PlusCircle } from 'lucide-react'; + +export const WorkoutsMock = () => ( +
+ {['Hipertrofia Iniciante', 'Emagrecimento Avançado', 'Funcional Idosos'].map((t, i) => ( +
+
+
+ +
+ +
+

{t}

+

30 alunos vinculados

+
+ ABC + 45-60min +
+
+ ))} +
+ + Criar Novo Treino +
+
+); diff --git a/src/contexts/LanguageContext.tsx b/src/contexts/LanguageContext.tsx new file mode 100644 index 0000000..b2f6ef7 --- /dev/null +++ b/src/contexts/LanguageContext.tsx @@ -0,0 +1,1377 @@ +import React, { createContext, useState, useContext, ReactNode } from 'react'; + +type Language = 'pt' | 'en' | 'es'; + +interface PlanDetails { + title: string; + price: string; + period: string; + billingInfo: string; + description: string; + btnText: string; + features: string[]; + highlight?: string; + savings?: string; +} + +interface FaqItem { + q: string; + a: string; +} + +interface FaqCategory { + title: string; + items: FaqItem[]; +} + +interface Translations { + header: { + howItWorks: string; + features: string; + pricing: string; + login: string; + cta: string; + slogan: string; + tools: string; + }; + hero: { + tag: string; + titleStart: string; + titleHighlight: string; + subtitle: string; + ctaUpload: string; + ctaPlans: string; + stats: string; + analysis: string; + demoTag: string; + demoResult: string; + demoAdvice: string; + demoAdviceText: string; + demoModalTitle: string; + demoModalDesc: string; + demoModalBtn: string; + demoProcessing: string; + }; + howItWorks: { + title: string; + subtitle: string; + step1Title: string; + step1Desc: string; + step2Title: string; + step2Desc: string; + step3Title: string; + step3Desc: string; + }; + features: { + guruTitle: string; + mainTitle: string; + subtitle: string; + f1Title: string; + f1Desc: string; + f2Title: string; + f2Desc: string; + f3Title: string; + f3Desc: string; + f4Title: string; + f4Desc: string; + f5Title: string; + f5Desc: string; + visualTipTitle: string; + visualTipDesc: string; + }; + testimonials: { + title: string; + subtitle: string; + r1Content: string; + r1Role: string; + r2Content: string; + r2Role: string; + r3Content: string; + r3Role: string; + }; + pricing: { + title: string; + subtitle: string; + freeTierTitle: string; + freeTierDesc: string; + secure: string; + plans: { + monthly: PlanDetails; + quarterly: PlanDetails; + annual: PlanDetails; + } + }; + faq: { + title: string; + q1: string; a1: string; + q2: string; a2: string; + q3: string; a3: string; + q4: string; a4: string; + }; + // New Full FAQ Page + faqPage: { + title: string; + subtitle: string; + searchPlaceholder: string; + backHome: string; + categories: { + general: FaqCategory; + account: FaqCategory; + billing: FaqCategory; + technical: FaqCategory; + }; + }; + footer: { + ctaTitle: string; + ctaDesc: string; + ctaBtn: string; + desc: string; + platform: string; + legal: string; + connect: string; + rights: string; + }; + auth: { + welcomeBack: string; + createAccount: string; + completeProfile: string; + accessPanel: string; + fillToAccess: string; + confirmPhone: string; + nameLabel: string; + phoneLabel: string; + emailLabel: string; + passwordLabel: string; + phonePlaceholder: string; + phoneHelper: string; + btnRegister: string; + btnLogin: string; + btnSave: string; + btnSuccess: string; + googleBtn: string; + or: string; + noAccount: string; + hasAccount: string; + registerLink: string; + loginLink: string; + security: string; + errorRequired: string; + errorPhone: string; + successRegister: string; + successLogin: string; + }; + dashboard: { + menuOverview: string; + menuHistory: string; + menuSubscription: string; + logout: string; + hello: string; + status: string; + statDishes: string; + statDishesSub: string; + statCals: string; + statCalsSub: string; + statPlan: string; + activeSub: string; + trialSub: string; + upgradeSub: string; + eatTitle: string; + eatDesc: string; + btnHistory: string; + btnWhatsapp: string; + recentTitle: string; + viewAll: string; + emptyRecent: string; + historyTitle: string; + historySubtitle: string; + searchPlaceholder: string; + emptyHistory: string; + subTitle: string; + subDesc: string; + currentPlan: string; + validUntil: string; + limitedAccess: string; + portalText: string; + btnPortal: string; + upgradeTitle: string; + upgradeDesc: string; + btnUpgrade: string; + btnUpgradeShort: string; + connectTitle: string; + connectDesc: string; + step1: string; + step2: string; + step3: string; + scanLabel: string; + }; + tools: { + title: string; + subtitle: string; + bmi: { + title: string; + desc: string; + labelWeight: string; + labelHeight: string; + result: string; + }; + water: { + title: string; + desc: string; + result: string; + daily: string; + }; + bmr: { + title: string; + desc: string; + labelAge: string; + labelGender: string; + male: string; + female: string; + result: string; + }; + tdee: { + title: string; + desc: string; + activity: string; + sedentary: string; + light: string; + moderate: string; + active: string; + veryActive: string; + result: string; + }; + orm: { + title: string; + desc: string; + lift: string; + reps: string; + result: string; + }; + bodyfat: { + title: string; + desc: string; + waist: string; + neck: string; + hip: string; + result: string; + }; + hr: { + title: string; + desc: string; + result: string; + zone: string; + }; + calculate: string; + back: string; + }; + coach: { + title: string; + subtitle: string; + photosStep: { + alert: string; + front: string; + side: string; + back: string; + camera: string; + gallery: string; + }; + goalStep: { + title: string; + hypertrophy: { title: string; desc: string; }; + definition: { title: string; desc: string; }; + maintenance: { title: string; desc: string; }; + strength: { title: string; desc: string; }; + }; + processing: { + errorTitle: string; + retry: string; + analyzing: string; + wait: string; + steps: string[]; + }; + buttons: { + next: string; + back: string; + generate: string; + }; + }; +} + +const dictionary: Record = { + pt: { + header: { + howItWorks: 'Como Funciona', + features: 'Vantagens', + pricing: 'Planos', + login: 'Área do Membro', + cta: 'Começar Transformação', + slogan: 'Seu Nutricionista de Bolso', + tools: 'Ferramentas' + }, + hero: { + tag: 'NOVA IA GERATIVA 2.0', + titleStart: 'Transforme seu corpo', + titleHighlight: 'com apenas uma foto.', + subtitle: 'O FoodSnap.ai elimina a necessidade de pesar comida. Tire uma foto e nossa Inteligência Artificial calcula calorias, macros e te dá dicas em tempo real para atingir seu peso ideal.', + ctaUpload: 'Testar IA Agora', + ctaPlans: 'Ver Preços', + stats: '25k+ refeições otimizadas', + analysis: 'Raio-X Nutricional', + demoTag: 'Análise FoodSnap', + demoResult: 'Estimativa Precisa', + demoAdvice: 'Dica FoodSnap:', + demoAdviceText: 'Ótima proteína! Reduza o arroz pela metade no jantar para acelerar a queima de gordura.', + demoModalTitle: 'Experimente a Mágica', + demoModalDesc: 'Envie uma foto da sua refeição. O FoodSnap.ai identifica ingredientes e calcula tudo instantaneamente.', + demoModalBtn: 'Enviar Foto', + demoProcessing: 'FoodSnap analisando...' + }, + howItWorks: { + title: 'Nutrição simplificada em 3 passos', + subtitle: 'Sem planilhas chatas, sem aplicativos complicados. Usamos o WhatsApp que você já ama.', + step1Title: 'Fotografe', + step1Desc: 'Vai comer? Aponte a câmera. Não precisa descrever nada, nossa visão computacional faz o trabalho pesado.', + step2Title: 'Envie no Zap', + step2Desc: 'Mande a foto para o nosso número oficial. É como conversar com um amigo nutricionista.', + step3Title: 'Receba o Feedback', + step3Desc: 'Em segundos, saiba se está dentro da meta e receba sugestões para melhorar a próxima refeição.' + }, + features: { + guruTitle: 'Tecnologia FoodSnap', + mainTitle: 'Emagreça sem passar fome e sem neura.', + subtitle: 'O FoodSnap não é apenas um contador de calorias. É um assistente que te ensina a comer melhor, refeição após refeição.', + f1Title: 'Scanner de Calorias', + f1Desc: 'Adeus balança! Nossa IA estima porções visualmente com alta precisão.', + f2Title: 'Coach Nutricional', + f2Desc: 'Receba elogios quando acertar e correções gentis quando exagerar. Feedback humano e motivador.', + f3Title: 'Sugestões de Trocas', + f3Desc: 'Exagerou no almoço? O FoodSnap sugere um lanche mais leve para compensar e manter o dia no verde.', + f4Title: 'Diário Automático', + f4Desc: 'Todo o seu histórico fica salvo. Gere relatórios de evolução para compartilhar com seu médico.', + f5Title: 'Tire Dúvidas 24h', + f5Desc: 'Pergunte qualquer coisa: "Quantas calorias tem uma maçã?" ou "O que comer antes do treino?".', + visualTipTitle: 'Insight FoodSnap', + visualTipDesc: 'Essa refeição está rica em fibras! Isso vai te manter saciado por mais tempo. Continue assim!' + }, + testimonials: { + title: 'Resultados Reais', + subtitle: 'Milhares de pessoas já transformaram sua relação com a comida usando o FoodSnap.ai.', + r1Content: 'Eu desisti de 5 dietas porque tinha preguiça de anotar tudo. Com o FoodSnap, só tiro foto e pronto. Perdi 8kg em 2 meses!', + r1Role: 'Advogado', + r2Content: 'Como nutricionista, recomendo aos pacientes. A adesão ao plano alimentar triplicou porque ficou divertido acompanhar.', + r2Role: 'Nutricionista Funcional', + r3Content: 'A precisão é impressionante. Ele identificou até o azeite na salada. Vale cada centavo pela praticidade.', + r3Role: 'Personal Trainer' + }, + pricing: { + title: 'Invista na sua Saúde', + subtitle: 'Menos que um café por dia para ter um nutricionista IA no seu bolso.', + freeTierTitle: 'Teste Gratuito', + freeTierDesc: 'Analise suas primeiras 5 refeições sem custo algum.', + secure: 'Compra segura. Satisfação garantida ou seu dinheiro de volta em 7 dias.', + plans: { + monthly: { + title: 'Mensal', + price: 'R$ 49,90', + period: '/mês', + billingInfo: 'Sem fidelidade', + description: 'Para quem quer flexibilidade.', + btnText: 'Começar Mensal', + features: [ + 'Fotos Ilimitadas', + 'Feedback Imediato', + 'Chat Nutricional 24h', + 'Histórico Completo' + ] + }, + quarterly: { + title: 'Trimestral', + price: 'R$ 39,90', + period: '/mês', + billingInfo: 'Cobrado a cada 3 meses', + description: 'O empurrão que faltava.', + btnText: 'Garantir Desconto', + features: [ + 'Tudo do Mensal', + 'Economia de 20%', + 'Prioridade no Suporte', + 'Acesso a Novas Features' + ] + }, + annual: { + title: 'Anual', + price: 'R$ 29,90', + period: '/mês', + billingInfo: 'Faturamento anual', + description: 'Para transformar o estilo de vida.', + btnText: 'Quero o Melhor Preço', + highlight: 'Mais Vendido', + savings: 'Economize 40%', + features: [ + 'Tudo do Trimestral', + 'Relatório Mensal em PDF', + 'Suporte VIP', + 'Menor valor mensal' + ] + } + } + }, + faq: { + title: 'Dúvidas Comuns', + q1: 'O FoodSnap substitui um nutricionista?', + a1: 'O FoodSnap é uma ferramenta de apoio educacional e monitoramento. Para dietas prescritas para condições de saúde específicas, consulte sempre um profissional.', + q2: 'Preciso baixar algum app?', + a2: 'Não! Toda a mágica acontece no WhatsApp. Você não ocupa memória do celular e usa o app que já conhece.', + q3: 'A IA acerta sempre?', + a3: 'Nossa IA tem precisão superior a 90% para alimentos comuns. Em pratos muito misturados, ela faz a melhor estimativa possível baseada em padrões visuais.', + q4: 'Como cancelo?', + a4: 'Super simples. Dentro do seu painel de usuário, há um botão "Gerenciar Assinatura". Cancele quando quiser com um clique.' + }, + faqPage: { + title: 'Central de Ajuda', + subtitle: 'Encontre respostas para suas dúvidas sobre o FoodSnap.ai', + searchPlaceholder: 'Busque sua dúvida (ex: cancelamento, whatsapp...)', + backHome: 'Voltar para Home', + categories: { + general: { + title: 'Geral', + items: [ + { q: 'O que é o FoodSnap.ai?', a: 'O FoodSnap.ai é um serviço de nutrição inteligente que usa inteligência artificial para analisar fotos de suas refeições, calcular calorias e macros, e fornecer feedback em tempo real via WhatsApp.' }, + { q: 'Como funciona a análise?', a: 'Basta enviar uma foto do seu prato para nosso número no WhatsApp. Nossa IA identifica os alimentos, estima as porções e retorna um relatório nutricional completo em segundos.' }, + { q: 'Preciso de um app?', a: 'Não. Tudo funciona dentro do WhatsApp. Você também tem acesso a um painel web para ver seu histórico completo e gráficos de evolução.' } + ] + }, + account: { + title: 'Minha Conta', + items: [ + { q: 'Como mudo minha senha?', a: 'Você pode redefinir sua senha na tela de login clicando em "Esqueci minha senha" ou dentro do painel do usuário nas configurações.' }, + { q: 'Posso compartilhar minha conta?', a: 'A assinatura é individual. O histórico e as recomendações são personalizados para o perfil de um único usuário.' }, + { q: 'Como mudo meu telefone cadastrado?', a: 'No momento, para mudar o telefone vinculado ao WhatsApp, entre em contato com nosso suporte via chat no painel.' } + ] + }, + billing: { + title: 'Planos e Pagamento', + items: [ + { q: 'Quais as formas de pagamento?', a: 'Aceitamos cartões de crédito (Visa, Mastercard, Elo, Amex) através da plataforma segura Stripe.' }, + { q: 'Tem fidelidade?', a: 'O plano mensal não tem fidelidade. Os planos trimestrais e anuais possuem o compromisso pelo período contratado em troca de um desconto maior.' }, + { q: 'Como cancelo?', a: 'Acesse o Painel do Usuário > Minha Assinatura > Portal do Cliente. Lá você pode cancelar a renovação automática a qualquer momento.' }, + { q: 'Tenho reembolso?', a: 'Sim, oferecemos garantia incondicional de 7 dias para novos assinantes. Se não gostar, devolvemos 100% do valor.' } + ] + }, + technical: { + title: 'Suporte Técnico', + items: [ + { q: 'O bot não responde no WhatsApp', a: 'Verifique se você salvou o número corretamente e se seu plano está ativo. Às vezes, pode haver uma breve fila de processamento. Tente enviar "Oi" para reiniciar.' }, + { q: 'A IA identificou errado meu prato', a: 'Embora rara, imprecisões podem ocorrer. Tente tirar fotos com boa iluminação e onde todos os ingredientes estejam visíveis. Você pode enviar uma mensagem de texto corrigindo (ex: "não é frango, é peixe") e a IA ajustará.' } + ] + } + } + }, + footer: { + ctaTitle: 'Comece sua transformação hoje', + ctaDesc: 'Não deixe para segunda-feira. Sua melhor versão começa com o próximo prato.', + ctaBtn: 'Quero Experimentar o FoodSnap', + desc: 'Tecnologia de ponta para simplificar a nutrição e promover saúde acessível para todos.', + platform: 'Produto', + legal: 'Legal', + connect: 'Redes', + rights: 'Todos os direitos reservados.' + }, + auth: { + welcomeBack: 'Login FoodSnap', + createAccount: 'Criar Conta FoodSnap', + completeProfile: 'Quase lá!', + accessPanel: 'Gerencie sua assinatura e histórico.', + fillToAccess: 'Preencha para liberar seu acesso.', + confirmPhone: 'Confirme seu WhatsApp para sincronizar.', + nameLabel: 'Nome Completo', + phoneLabel: 'Seu WhatsApp (com DDD)', + emailLabel: 'Melhor E-mail', + passwordLabel: 'Senha Segura', + phonePlaceholder: '11999999999', + phoneHelper: 'Digite apenas números com DDD.', + btnRegister: 'Criar Conta Grátis', + btnLogin: 'Entrar no Painel', + btnSave: 'Finalizar Cadastro', + btnSuccess: 'Sucesso!', + googleBtn: 'Entrar com Google', + or: 'Ou', + noAccount: 'Ainda não tem conta?', + hasAccount: 'Já é cliente?', + registerLink: 'Criar conta', + loginLink: 'Fazer login', + security: 'Seus dados estão 100% seguros.', + errorRequired: 'Preencha todos os campos.', + errorPhone: 'WhatsApp inválido.', + successRegister: 'Conta criada! Acessando...', + successLogin: 'Login efetuado!' + }, + dashboard: { + menuOverview: 'Visão Geral', + menuHistory: 'Diário Alimentar', + menuSubscription: 'Minha Assinatura', + logout: 'Sair', + hello: 'Olá', + status: 'Status da Conta', + statDishes: 'Refeições', + statDishesSub: 'Registradas', + statCals: 'Média Diária', + statCalsSub: 'Calorias', + statPlan: 'Seu Plano', + activeSub: 'FoodSnap PRO', + trialSub: 'Período Gratuito', + upgradeSub: 'Conta Básica', + eatTitle: 'Hora de comer?', + eatDesc: 'Envie a foto no WhatsApp ou faça upload aqui para registrar seus macros.', + btnHistory: 'Ver Diário', + btnWhatsapp: 'Abrir WhatsApp', + recentTitle: 'Últimas Refeições', + viewAll: 'Ver tudo', + emptyRecent: 'Nenhuma refeição registrada hoje.', + historyTitle: 'Seu Diário Alimentar', + historySubtitle: 'Acompanhe sua evolução nutricional.', + searchPlaceholder: 'Buscar refeição...', + emptyHistory: 'Seu histórico está vazio.', + subTitle: 'Assinatura', + subDesc: 'Gerencie seu plano FoodSnap.', + currentPlan: 'Plano Atual', + validUntil: 'Renova em:', + limitedAccess: 'Você está no plano gratuito.', + portalText: 'Gerenciar cartão ou cancelar assinatura?', + btnPortal: 'Portal do Cliente', + upgradeTitle: 'Seja FoodSnap PRO', + upgradeDesc: 'Tenha análises ilimitadas, relatórios detalhados e suporte prioritário para atingir seus objetivos mais rápido.', + btnUpgrade: 'Assinar Agora', + btnUpgradeShort: 'Virar PRO', + connectTitle: 'Ativar no WhatsApp', + connectDesc: 'Sincronize sua conta para enviar fotos direto pelo Zap.', + step1: 'Leia o QR Code', + step2: 'Envie um "Oi"', + step3: 'Mande a foto do prato', + scanLabel: 'Ler QR Code' + }, + tools: { + title: 'Calculadoras Fit', + subtitle: 'Ferramentas para seu planejamento.', + bmi: { + title: 'Calculadora de IMC', + desc: 'Descubra seu Índice de Massa Corporal.', + labelWeight: 'Peso (kg)', + labelHeight: 'Altura (cm)', + result: 'Seu IMC é' + }, + water: { + title: 'Hidratação Diária', + desc: 'Descubra quanta água beber.', + result: 'Sua meta é', + daily: 'litros por dia' + }, + bmr: { + title: 'Gasto Calórico (TMB)', + desc: 'Quantas calorias você queima em repouso.', + labelAge: 'Idade', + labelGender: 'Gênero', + male: 'Homem', + female: 'Mulher', + result: 'Sua TMB é' + }, + tdee: { + title: 'Gasto Total (TDEE)', + desc: 'Calorias diárias considerando atividade.', + activity: 'Nível de Atividade', + sedentary: 'Sedentário', + light: 'Levemente Ativo', + moderate: 'Moderadamente Ativo', + active: 'Muito Ativo', + veryActive: 'Atleta / Extremo', + result: 'Gasto Diário' + }, + orm: { + title: 'Força Máxima (1RM)', + desc: 'Carga máxima teórica para 1 repetição.', + lift: 'Peso Levantado (kg)', + reps: 'Repetições', + result: 'Seu 1RM Estimado' + }, + bodyfat: { + title: 'Gordura Corporal', + desc: 'Estimativa baseada no método da Marinha.', + waist: 'Cintura (cm)', + neck: 'Pescoço (cm)', + hip: 'Quadril (cm)', + result: 'Gordura Estimada' + }, + hr: { + title: 'Zonas de FC', + desc: 'Frequência Cardíaca Máxima e Zonas.', + result: 'FC Máxima', + zone: 'Zona de Queima de Gordura' + }, + calculate: 'Calcular', + back: 'Voltar' + }, + coach: { + title: 'Personal Trainer IA', + subtitle: 'Gere seu protocolo ideal baseado no seu biotipo.', + photosStep: { + alert: 'Fotos de Avaliação: Use roupas leves. Posicione a câmera na altura do peito, corpo relaxado.', + front: 'Frente', + side: 'Perfil', + back: 'Costas', + camera: 'Câmera', + gallery: 'Galeria' + }, + goalStep: { + title: 'Qual seu objetivo principal?', + hypertrophy: { title: 'Hipertrofia', desc: 'Ganhar massa muscular e volume.' }, + definition: { title: 'Definição', desc: 'Queimar gordura e definir músculos.' }, + maintenance: { title: 'Saúde e Manutenção', desc: 'Melhorar alimentação e energia.' }, + strength: { title: 'Força Pura', desc: 'Focar em progressão de carga.' } + }, + processing: { + errorTitle: 'Ops! Algo deu errado.', + retry: 'Tentar Novamente', + analyzing: 'Buscando biotipo...', + wait: 'A IA está processando suas fotos. Isso pode levar até 30 segundos.', + steps: [ + "Identificando Biotipo...", + "Analisando Postura e Simetria...", + "Calculando Estimativa de Gordura...", + "Ajustando Macros para seu Objetivo...", + "Gerando Treino Personalizado..." + ] + }, + buttons: { + next: 'Próximo', + back: 'Voltar', + generate: 'Gerar Protocolo' + } + } + }, + en: { + header: { + howItWorks: 'How it Works', + features: 'Features', + pricing: 'Pricing', + login: 'Login', + cta: 'Start for Free', + slogan: 'Intelligence on your plate', + tools: 'Tools' + }, + hero: { + tag: 'Computational Nutrition AI', + titleStart: 'Calorie science,', + titleHighlight: 'simplified in one snap.', + subtitle: 'More than just calories. Our AI analyzes nutrient quality, suggests smart swaps, and optimizes your diet in real-time.', + ctaUpload: 'Live Demo', + ctaPlans: 'View Pro Plans', + stats: '10k+ meals analyzed', + analysis: 'Full Analysis', + demoTag: 'Insight', + demoResult: 'Estimate', + demoAdvice: 'Tip:', + demoAdviceText: 'Great choice! To lower the glycemic index, consider adding more fiber.', + demoModalTitle: 'Try the Technology', + demoModalDesc: 'Take a photo of your dish or choose from gallery. Our AI will analyze the nutrients in seconds.', + demoModalBtn: 'Choose Photo', + demoProcessing: 'Analyzing food items...' + }, + howItWorks: { + title: 'Frictionless Flow', + subtitle: 'We eliminated the complexity of tracking your diet. Just point and send.', + step1Title: 'Visual Capture', + step1Desc: 'Take a clear photo of your dish. Our AI accepts varied angles and identifies multiple items.', + step2Title: 'Instant Send', + step2Desc: 'Share via WhatsApp. No forms, no complex logins, no barriers.', + step3Title: 'Detailed Analysis', + step3Desc: 'Receive the full nutritional report and ask questions to the assistant in real-time.' + }, + features: { + guruTitle: 'Advanced AI', + mainTitle: 'Your pocket nutritionist, available 24/7.', + subtitle: 'We don\'t just deliver numbers. Our AI understands the context of your diet and offers qualitative feedback to help you eat better.', + f1Title: 'Nutritional X-Ray', + f1Desc: 'Automatic ingredient identification with detailed breakdown of Proteins, Carbs, Fats, and Fiber.', + f2Title: 'Improvement Suggestions', + f2Desc: 'The AI doesn\'t just read, it opines. Get tips like: "Add green leaves for more satiety".', + f3Title: 'Smart Swaps', + f3Desc: 'Love eating well? The system suggests tasty substitutions to reduce calories without sacrificing pleasure.', + f4Title: 'Visual Volumetrics', + f4Desc: 'Intelligent weight estimation based on plate proportion. Retire the kitchen scale.', + f5Title: 'Chat Consulting', + f5Desc: 'Ask questions: "Can I eat this pre-workout?" or "What\'s the best option on this menu?".', + visualTipTitle: 'Smart Insight', + visualTipDesc: 'Great choice of fats! How about adding pumpkin seeds for more crunch and zinc?' + }, + testimonials: { + title: 'Approved by Users', + subtitle: 'Join a community focused on real results.', + r1Content: 'The accuracy of macro reading changed my game. I no longer waste time weighing food at restaurants.', + r1Role: 'Crossfit Athlete', + r2Content: 'I recommend it to all my patients who struggle with food diaries. Plan adherence increased by 40%.', + r2Role: 'Sports Nutritionist', + r3Content: 'Clean interface, fast, and frictionless. Exactly what I needed to stay in shape without stress.', + r3Role: 'Software Engineer' + }, + pricing: { + title: 'Pro Plans', + subtitle: 'Choose the flexibility your lifestyle demands.', + freeTierTitle: 'Start Free', + freeTierDesc: 'All users start with 5 free queries.', + secure: 'Secure payment via Stripe. Cancel anytime.', + plans: { + monthly: { + title: 'Monthly', + price: '$9.99', + period: '/mo', + billingInfo: 'Billed monthly', + description: 'Total flexibility.', + btnText: 'Subscribe Monthly', + features: [ + 'Unlimited Queries', + 'AI Nutritionist Chat', + 'Unlimited History', + 'Micronutrient Analysis' + ] + }, + quarterly: { + title: 'Quarterly', + price: '$7.99', + period: '/mo', + billingInfo: 'Billed every 3 months', + description: 'Medium term commitment.', + btnText: 'Choose Quarterly', + features: [ + 'All Monthly features', + 'Priority Support', + 'Save 20%', + 'Access to Beta features' + ] + }, + annual: { + title: 'Annual', + price: '$5.99', + period: '/mo', + billingInfo: 'Billed annually', + description: 'Best for real results.', + btnText: 'Go Annual', + highlight: 'Best Value', + savings: 'Save 40%', + features: [ + 'All Quarterly features', + 'Evolution Reports', + 'VIP Support', + 'Price locked for 1 year' + ] + } + } + }, + faq: { + title: 'Frequently Asked Questions', + q1: 'Is the information 100% accurate?', + a1: 'No. Analyses are estimates based on the visual image sent. Factors like preparation method and hidden oils can vary. Use as a guide.', + q2: 'Does it work with any food?', + a2: 'Yes! Works well with homemade meals, lunchboxes, restaurants, and fast food. The clearer the photo, the better.', + q3: 'Do I need to install an app?', + a3: 'No. Everything works directly through WhatsApp. You send the photo as if chatting with a friend.', + q4: 'Can I cancel anytime?', + a4: 'Yes, no strings attached. Cancel the Pro subscription anytime via the dashboard.' + }, + faqPage: { + title: 'Help Center', + subtitle: 'Find answers to your questions about FoodSnap.ai', + searchPlaceholder: 'Search your question (ex: cancel, whatsapp...)', + backHome: 'Back to Home', + categories: { + general: { + title: 'General', + items: [ + { q: 'What is FoodSnap.ai?', a: 'FoodSnap.ai is an intelligent nutrition service that uses AI to analyze photos of your meals, calculate calories/macros, and provide real-time feedback via WhatsApp.' }, + { q: 'How does analysis work?', a: 'Just send a photo of your dish to our WhatsApp number. Our AI identifies foods, estimates portions, and returns a full nutritional report in seconds.' }, + { q: 'Do I need an app?', a: 'No. Everything works within WhatsApp. You also get a web dashboard to view your full history and progress charts.' } + ] + }, + account: { + title: 'My Account', + items: [ + { q: 'How do I change my password?', a: 'You can reset your password at the login screen by clicking "Forgot Password" or inside the user dashboard under settings.' }, + { q: 'Can I share my account?', a: 'Subscriptions are individual. History and recommendations are personalized for a single user profile.' }, + { q: 'How to change registered phone?', a: 'Currently, to change the phone linked to WhatsApp, please contact support via chat in the dashboard.' } + ] + }, + billing: { + title: 'Plans & Billing', + items: [ + { q: 'Payment methods?', a: 'We accept credit cards (Visa, Mastercard, Amex) via the secure Stripe platform.' }, + { q: 'Is there a contract?', a: 'The monthly plan has no contract. Quarterly and annual plans have a commitment for the contracted period in exchange for a discount.' }, + { q: 'How to cancel?', a: 'Go to User Dashboard > My Subscription > Customer Portal. You can cancel auto-renewal there anytime.' }, + { q: 'Refund policy?', a: 'Yes, we offer an unconditional 7-day guarantee for new subscribers. If you don\'t like it, we refund 100%.' } + ] + }, + technical: { + title: 'Technical Support', + items: [ + { q: 'Bot not responding on WhatsApp', a: 'Check if you saved the number correctly and your plan is active. Sometimes there may be a short processing queue. Try sending "Hi" to restart.' }, + { q: 'AI identified my dish wrong', a: 'Although rare, inaccuracies can happen. Try taking photos with good lighting where all ingredients are visible. You can send a text correcting it (e.g., "it\'s not chicken, it\'s fish") and the AI will adjust.' } + ] + } + } + }, + footer: { + ctaTitle: 'Ready to take control?', + ctaDesc: 'No complex spreadsheets, no scales. Just you, your food, and the best AI technology.', + ctaBtn: 'Access FoodSnap', + desc: 'Artificial Intelligence applied to nutrition to simplify life for those seeking health and real performance.', + platform: 'Platform', + legal: 'Legal', + connect: 'Connect', + rights: 'All rights reserved.' + }, + auth: { + welcomeBack: 'Welcome Back', + createAccount: 'Create Account', + completeProfile: 'Complete Profile', + accessPanel: 'Access your dashboard and history.', + fillToAccess: 'Fill to access AI features.', + confirmPhone: 'Confirm your WhatsApp to receive analyses.', + nameLabel: 'Full Name', + phoneLabel: 'WhatsApp (with Country Code)', + emailLabel: 'Email', + passwordLabel: 'Password', + phonePlaceholder: '15551234567', + phoneHelper: 'Numbers only, include country code.', + btnRegister: 'Create Free Account', + btnLogin: 'Login to Dashboard', + btnSave: 'Save and Continue', + btnSuccess: 'Success!', + googleBtn: 'Google', + or: 'Or continue with', + noAccount: 'No account?', + hasAccount: 'Already have an account?', + registerLink: 'Sign up', + loginLink: 'Login', + security: 'Data protected and encrypted.', + errorRequired: 'All fields are required.', + errorPhone: 'Invalid WhatsApp.', + successRegister: 'Account created! Redirecting...', + successLogin: 'Login successful!' + }, + dashboard: { + menuOverview: 'Overview', + menuHistory: 'Dish History', + menuSubscription: 'Subscription', + logout: 'Logout', + hello: 'Hello', + status: 'Status', + statDishes: 'Dishes Analyzed', + statDishesSub: 'Total registered', + statCals: 'Average Calories', + statCalsSub: 'kcal/meal', + statPlan: 'Current Plan', + activeSub: 'Active Subscription', + trialSub: 'Free Trial', + upgradeSub: 'Upgrade Available', + eatTitle: 'Eating something now?', + eatDesc: 'Send a photo to our WhatsApp or upload directly here to register.', + btnHistory: 'View History', + btnWhatsapp: 'WhatsApp', + recentTitle: 'Recent', + viewAll: 'View all', + emptyRecent: 'No dishes registered yet. Take a photo!', + historyTitle: 'Food History', + historySubtitle: 'All your analyses saved automatically.', + searchPlaceholder: 'Search dish...', + emptyHistory: 'You haven\'t sent any photos yet.', + subTitle: 'Manage Subscription', + subDesc: 'Control your payments and plan via Stripe.', + currentPlan: 'Current Plan', + validUntil: 'Valid until:', + limitedAccess: 'Limited access to free plan.', + portalText: 'Do you want to change your credit card or cancel subscription?', + btnPortal: 'Open Customer Portal', + upgradeTitle: 'Upgrade to PRO', + upgradeDesc: 'Unlock unlimited history, detailed micronutrient analysis, and priority support.', + btnUpgrade: 'Subscribe for $9.90/mo', + btnUpgradeShort: 'Get Pro', + connectTitle: 'Connect Now', + connectDesc: 'Follow steps to activate AI on WhatsApp.', + step1: 'Scan QR Code', + step2: 'Send "Hi"', + step3: 'Send photo of your dish', + scanLabel: 'Scan to start' + }, + tools: { + title: 'Free Tools', + subtitle: 'Essential calculators for your journey.', + bmi: { + title: 'BMI Calculator', + desc: 'Find out your Body Mass Index.', + labelWeight: 'Weight (kg)', + labelHeight: 'Height (cm)', + result: 'Your BMI is' + }, + water: { + title: 'Daily Hydration', + desc: 'Find out how much water to drink.', + result: 'Your goal is', + daily: 'liters per day' + }, + bmr: { + title: 'Caloric Burn (BMR)', + desc: 'How many calories you burn at rest.', + labelAge: 'Age', + labelGender: 'Gender', + male: 'Male', + female: 'Female', + result: 'Your BMR is' + }, + tdee: { + title: 'Total Energy (TDEE)', + desc: 'Daily calories including activity level.', + activity: 'Activity Level', + sedentary: 'Sedentary', + light: 'Lightly Active', + moderate: 'Moderadamente Active', + active: 'Very Active', + veryActive: 'Athlete / Extreme', + result: 'Daily Burn' + }, + orm: { + title: 'One Rep Max (1RM)', + desc: 'Theoretical max load for 1 repetition.', + lift: 'Weight Lifted (kg)', + reps: 'Reps Performed', + result: 'Estimated 1RM' + }, + bodyfat: { + title: 'Body Fat %', + desc: 'Estimate based on US Navy method.', + waist: 'Waist (cm)', + neck: 'Neck (cm)', + hip: 'Hip (cm)', + result: 'Est. Body Fat' + }, + hr: { + title: 'Heart Rate Zones', + desc: 'Max Heart Rate and Training Zones.', + result: 'Max HR', + zone: 'Fat Burn Zone' + }, + calculate: 'Calculate', + back: 'Back' + }, + coach: { + title: 'AI Personal Coach', + subtitle: 'Generate your ideal protocol based on your biotype.', + photosStep: { + alert: 'Assessment Photos: Wear light clothing. Position camera at chest height, relaxed body and straight spine.', + front: 'Front', + side: 'Side', + back: 'Back', + camera: 'Camera', + gallery: 'Gallery' + }, + goalStep: { + title: 'What is your main goal?', + hypertrophy: { title: 'Hypertrophy', desc: 'Gain muscle mass and volume.' }, + definition: { title: 'Definition', desc: 'Burn fat and define muscles.' }, + maintenance: { title: 'Health & Maintenance', desc: 'Improve nutrition and energy.' }, + strength: { title: 'Pure Strength', desc: 'Focus on load progression.' } + }, + processing: { + errorTitle: 'Oops! Something went wrong.', + retry: 'Try Again', + analyzing: 'Finding biotype...', + wait: 'AI is processing your photos. This may take up to 30 seconds.', + steps: [ + "Identifying Biotype...", + "Analyzing Posture and Symmetry...", + "Calculating Fat Estimate...", + "Adjusting Macros for your Goal...", + "Generating Personalized Workout Plan..." + ] + }, + buttons: { + next: 'Next', + back: 'Back', + generate: 'Generate Protocol' + } + } + }, + es: { + header: { + howItWorks: 'Cómo Funciona', + features: 'Funciones', + pricing: 'Precios', + login: 'Entrar', + cta: 'Empezar Gratis', + slogan: 'Inteligencia en tu plato', + tools: 'Herramientas' + }, + hero: { + tag: 'IA de Nutrición Computacional', + titleStart: 'La ciencia de las calorías,', + titleHighlight: 'simplificada en una foto.', + subtitle: 'Mucho más que calorías. Nuestra IA analiza la calidad de los nutrientes, sugiere cambios inteligentes y optimiza tu dieta en tiempo real.', + ctaUpload: 'Demostración', + ctaPlans: 'Ver Planes Pro', + stats: '10k+ platos analizados', + analysis: 'Análisis Completo', + demoTag: 'Perspectiva', + demoResult: 'Estimación', + demoAdvice: 'Consejo:', + demoAdviceText: '¡Excelente elección! Para reducir el índice glucémico, añade más fibra.', + demoModalTitle: 'Prueba la Tecnología', + demoModalDesc: 'Toma una foto de tu plato o elige de la galería. Nuestra IA analizará los nutrientes en segundos.', + demoModalBtn: 'Elegir Foto', + demoProcessing: 'Analizando alimentos...' + }, + howItWorks: { + title: 'Flujo sin fricción', + subtitle: 'Eliminamos la complejidad de rastrear tu dieta. Solo apunta y envía.', + step1Title: 'Captura Visual', + step1Desc: 'Toma una foto clara de tu plato. Nuestra IA acepta varios ángulos e identifica múltiples elementos.', + step2Title: 'Envío Instantáneo', + step2Desc: 'Comparte vía WhatsApp. Sin formularios, sin inicios de sesión complejos, sin barreras.', + step3Title: 'Análisis Detallado', + step3Desc: 'Recibe el informe nutricional completo y haz preguntas al asistente en tiempo real.' + }, + features: { + guruTitle: 'IA Avançada', + mainTitle: 'Tu nutricionista de bolsillo, 24/7.', + subtitle: 'No solo entregamos números. Nuestra IA entiende el contexto de tu dieta y ofrece feedback cualitativo.', + f1Title: 'Rayos-X Nutricional', + f1Desc: 'Identificación automática de ingredientes con desglose detallado de Proteínas, Carbohidratos, Grasas y Fibra.', + f2Title: 'Sugerencias de Mejora', + f2Desc: 'La IA no solo lee, opina. Recibe consejos como: "Añade hojas verdes para más saciedad".', + f3Title: 'Cambios Inteligentes', + f3Desc: '¿Amas comer bien? La IA sugiere sustituciones sabrosas para reducir calorías sin sacrificar el placer.', + f4Title: 'Volumetría Visual', + f4Desc: 'Estimación inteligente de peso basada en la proporción del plato. Jubila la báscula de cocina.', + f5Title: 'Consultoría Chat', + f5Desc: 'Pregunta: "¿Puedo comer esto antes de entrenar?" o "¿Cuál es la mejor opción de este menú?".', + visualTipTitle: 'Smart Insight', + visualTipDesc: '¡Gran elección de grasas! ¿Qué tal añadir semillas de calabaza para más crujido y zinc?' + }, + testimonials: { + title: 'Quien usa, aprueba', + subtitle: 'Únete a una comunidad enfocada en resultados reales.', + r1Content: 'La precisión de lectura de macros cambió mi juego. Ya no pierdo tiempo pesando comida.', + r1Role: 'Atleta de Crossfit', + r2Content: 'Lo indico a todos mis pacientes. La adhesión al plan aumentó en 40%.', + r2Role: 'Nutricionista Deportiva', + r3Content: 'Interfaz limpia, rápida y sin fricción. Exactamente lo que necesitaba.', + r3Role: 'Ingeniero de Software' + }, + pricing: { + title: 'Planes Pro', + subtitle: 'Elige la flexibilidad que tu estilo de vida exige.', + freeTierTitle: 'Empieza Gratis', + freeTierDesc: 'Todos los usuarios comienzan con 5 consultas gratis.', + secure: 'Pago seguro vía Stripe. Cancela cuando quieras.', + plans: { + monthly: { + title: 'Mensual', + price: '€ 14,90', + period: '/mes', + billingInfo: 'Cobrado mensualmente', + description: 'Flexibilidad total.', + btnText: 'Suscribir Mensual', + features: [ + 'Consultas Ilimitadas', + 'Chat con Nutricionista IA', + 'Historial Ilimitado', + 'Análisis de Micronutrientes' + ] + }, + quarterly: { + title: 'Trimestral', + price: '€ 11,90', + period: '/mes', + billingInfo: 'Cobrado cada 3 meses', + description: 'Compromiso a medio plazo.', + btnText: 'Elegir Trimestral', + features: [ + 'Todo lo del Mensual', + 'Soporte Prioritario', + 'Ahorra 20%', + 'Acceso a funciones Beta' + ] + }, + annual: { + title: 'Anual', + price: '€ 9,90', + period: '/mes', + billingInfo: 'Cobrado anualmente', + description: 'El favorito para resultados.', + btnText: 'Suscribir Anual', + highlight: 'Mejor Valor', + savings: 'Ahorra 40%', + features: [ + 'Todo lo del Trimestral', + 'Reportes de Evolución', + 'Soporte VIP', + 'Precio congelado por 1 año' + ] + } + } + }, + faq: { + title: 'Preguntas Frecuentes', + q1: '¿La información es 100% precisa?', + a1: 'No. Los análisis son estimaciones visuales. Factores como la preparación pueden variar. Úsalo como guía.', + q2: '¿Funciona con cualquier comida?', + a2: '¡Sí! Funciona con platos caseros, fiambreras, restaurantes y comida rápida.', + q3: '¿Necesito instalar una app?', + a3: 'No. Todo funciona directamente por WhatsApp.', + q4: '¿Puedo cancelar cuando quiera?', + a4: 'Sí, sin fidelidad. Cancela la suscripción Pro en cualquier momento.' + }, + faqPage: { + title: 'Centro de Ayuda', + subtitle: 'Encuentra respuestas a tus dudas sobre FoodSnap.ai', + searchPlaceholder: 'Busca tu duda (ej: cancelar, whatsapp...)', + backHome: 'Volver al Inicio', + categories: { + general: { + title: 'General', + items: [ + { q: '¿Qué es FoodSnap.ai?', a: 'FoodSnap.ai es un servicio de nutrición inteligente que usa inteligencia artificial para analizar fotos de tus comidas, calcular calorías/macros y dar feedback en tiempo real vía WhatsApp.' }, + { q: '¿Cómo funciona el análisis?', a: 'Solo envía una foto de tu plato a nuestro número de WhatsApp. Nuestra IA identifica los alimentos, estima porciones y devuelve un informe nutricional completo en segundos.' }, + { q: '¿Necesito una app?', a: 'No. Todo funciona dentro de WhatsApp. También tienes acceso a un panel web para ver tu historial completo y gráficos de evolución.' } + ] + }, + account: { + title: 'Mi Cuenta', + items: [ + { q: '¿Cómo cambio mi contraseña?', a: 'Puedes restablecer tu contraseña en la pantalla de inicio de sesión haciendo clic en "¿Olvidaste tu contraseña?" o dentro del panel de usuario en configuración.' }, + { q: '¿Puedo compartir mi cuenta?', a: 'La suscripción es individual. El historial y las recomendaciones están personalizados para un único perfil de usuario.' }, + { q: '¿Cómo cambio mi teléfono registrado?', a: 'Actualmente, para cambiar el teléfono vinculado a WhatsApp, contacta con soporte vía chat en el panel.' } + ] + }, + billing: { + title: 'Planes y Pagos', + items: [ + { q: '¿Métodos de pago?', a: 'Aceptamos tarjetas de crédito (Visa, Mastercard, Amex) a través de la plataforma segura Stripe.' }, + { q: '¿Hay permanencia?', a: 'El plan mensual no tiene permanencia. Los planes trimestrales y anuales tienen compromiso por el período contratado a cambio de un descuento.' }, + { q: '¿Cómo cancelo?', a: 'Accede al Panel de Usuario > Mi Suscripción > Portal de Cliente. Puedes cancelar la renovación automática allí en cualquier momento.' }, + { q: '¿Política de reembolso?', a: 'Sí, ofrecemos garantía incondicional de 7 días para nuevos suscriptores. Si no te gusta, devolvemos el 100%.' } + ] + }, + technical: { + title: 'Soporte Técnico', + items: [ + { q: 'El bot no responde en WhatsApp', a: 'Verifica si guardaste el número correctamente y si tu plan está activo. A veces puede haber una breve cola de procesamiento. Intenta enviar "Hola" para reiniciar.' }, + { q: 'La IA identificó mal mi plato', a: 'Aunque raro, pueden ocurrir imprecisiones. Intenta tomar fotos con buena iluminación donde todos los ingredientes sean visibles. Puedes enviar un texto corrigiendo (ej: "no es pollo, es pescado") y la IA ajustará.' } + ] + } + } + }, + footer: { + ctaTitle: '¿Listo para tomar el control?', + ctaDesc: 'Sin hojas de cálculo complejas, sin básculas. Solo tú, tu comida y la mejor tecnología de IA.', + ctaBtn: 'Acceder a FoodSnap', + desc: 'Inteligencia Artificial aplicada a la nutrición para simplificar la vida de quien busca salud.', + platform: 'Plataforma', + legal: 'Legal', + connect: 'Conectar', + rights: 'Todos los derechos reservados.' + }, + auth: { + welcomeBack: 'Bienvenido de nuevo', + createAccount: 'Crea tu cuenta', + completeProfile: 'Completa tu perfil', + accessPanel: 'Accede a tu panel e historial.', + fillToAccess: 'Rellena para acceder a la IA.', + confirmPhone: 'Confirma tu WhatsApp para recibir análisis.', + nameLabel: 'Nombre Completo', + phoneLabel: 'WhatsApp (con código país)', + emailLabel: 'Correo Electrónico', + passwordLabel: 'Contraseña', + phonePlaceholder: '34600123456', + phoneHelper: 'Solo números, incluye código de país.', + btnRegister: 'Crear Cuenta Gratis', + btnLogin: 'Entrar al Panel', + btnSave: 'Guardar y Continuar', + btnSuccess: '¡Éxito!', + googleBtn: 'Google', + or: 'O continúa con', + noAccount: '¿No tienes cuenta?', + hasAccount: '¿Ya tienes cuenta?', + registerLink: 'Regístrate', + loginLink: 'Inicia sesión', + security: 'Datos protegidos y encriptados.', + errorRequired: 'Todos los campos son obligatorios.', + errorPhone: 'WhatsApp inválido.', + successRegister: '¡Cuenta creada!', + successLogin: '¡Login exitoso!' + }, + dashboard: { + menuOverview: 'Visión General', + menuHistory: 'Historial', + menuSubscription: 'Suscripción', + logout: 'Cerrar sesión', + hello: 'Hola', + status: 'Estado', + statDishes: 'Platos Analisados', + statDishesSub: 'Total registrado', + statCals: 'Promedio Calorías', + statCalsSub: 'kcal/comida', + statPlan: 'Plan Actual', + activeSub: 'Suscripción Activa', + trialSub: 'Prueba Gratis', + upgradeSub: 'Mejora Disponible', + eatTitle: '¿Vas a comer algo ahora?', + eatDesc: 'Envía una foto a nuestro WhatsApp o súbela directamente aquí para registrar.', + btnHistory: 'Ver Historial', + btnWhatsapp: 'WhatsApp', + recentTitle: 'Recentes', + viewAll: 'Ver todo', + emptyRecent: 'Ningún plato registrado aún. ¡Toma una foto!', + historyTitle: 'Historial de Comidas', + historySubtitle: 'Todos tus análisis guardados automáticamente.', + searchPlaceholder: 'Buscar plato...', + emptyHistory: 'Aún no has enviado ninguna foto.', + subTitle: 'Gestionar Suscripción', + subDesc: 'Controla tus pagos y plan vía Stripe.', + currentPlan: 'Plan Actual', + validUntil: 'Válido hasta:', + limitedAccess: 'Acceso limitado al plan gratuito.', + portalText: '¿Deseas cambiar tu tarjeta de crédito o cancelar la suscripción?', + btnPortal: 'Abrir Portal de Cliente', + upgradeTitle: 'Mejora al PRO', + upgradeDesc: 'Desbloquea historial ilimitado, análisis detallados y soporte prioritario.', + btnUpgrade: 'Suscribir por € 9,90/mes', + btnUpgradeShort: 'Obtener Pro', + connectTitle: 'Conectar ahora', + connectDesc: 'Sigue los pasos para activar la IA en WhatsApp.', + step1: 'Escanea el Código QR', + step2: 'Envía "Hola"', + step3: 'Envía una foto de tu plato', + scanLabel: 'Escanea para iniciar' + }, + tools: { + title: 'Herramientas Gratuitas', + subtitle: 'Calculadoras esenciales para tu viaje.', + bmi: { + title: 'Calculadora de IMC', + desc: 'Descubre tu Índice de Massa Corporal.', + labelWeight: 'Peso (kg)', + labelHeight: 'Altura (cm)', + result: 'Tu IMC es' + }, + water: { + title: 'Hidratación Diaria', + desc: 'Descubre cuánta agua debes beber.', + result: 'Tu meta es', + daily: 'litros al día' + }, + bmr: { + title: 'Gasto Calórico (TMB)', + desc: 'Cuántas calorías quemas en reposo.', + labelAge: 'Edad', + labelGender: 'Género', + male: 'Hombre', + female: 'Mujer', + result: 'Tu TMB es' + }, + tdee: { + title: 'Gasto Total (TDEE)', + desc: 'Calorías diarias considerando actividad.', + activity: 'Nivel de Actividad', + sedentary: 'Sedentario', + light: 'Ligeramente Activo', + moderate: 'Moderadamente Activo', + active: 'Muy Activo', + veryActive: 'Atleta / Extremo', + result: 'Gasto Diario' + }, + orm: { + title: 'Fuerza Máxima (1RM)', + desc: 'Carga máxima teórica para 1 repetição.', + lift: 'Peso Levantado (kg)', + reps: 'Repeticiones', + result: 'Tu 1RM Estimado' + }, + bodyfat: { + title: 'Grasa Corporal', + desc: 'Estimación basada en el método de la Marina.', + waist: 'Cintura (cm)', + neck: 'Cuello (cm)', + hip: 'Cadera (cm)', + result: 'Grasa Estimada' + }, + hr: { + title: 'Zonas de FC', + desc: 'Frecuencia Cardíaca Máxima y Zonas.', + result: 'FC Máxima', + zone: 'Zona Quema Grasa' + }, + calculate: 'Calcular', + back: 'Volver' + }, + coach: { + title: 'Entrenador Personal IA', + subtitle: 'Genera tu protocolo ideal basado en tu biotipo.', + photosStep: { + alert: 'Fotos de Evaluación: Usa ropa ligera. Posiciona la cámara a la altura del pecho, cuerpo relajado.', + front: 'Frente', + side: 'Perfil', + back: 'Espalda', + camera: 'Cámara', + gallery: 'Galería' + }, + goalStep: { + title: '¿Cuál es tu objetivo principal?', + hypertrophy: { title: 'Hipertrofia', desc: 'Ganar masa muscular y volumen.' }, + definition: { title: 'Definición', desc: 'Quemar grasa y definir músculos.' }, + maintenance: { title: 'Salud y Mantenimiento', desc: 'Mejorar alimentación y energía.' }, + strength: { title: 'Fuerza Pura', desc: 'Enfocar en progresión de carga.' } + }, + processing: { + errorTitle: '¡Ups! Algo salió mal.', + retry: 'Intentar de Nuevo', + analyzing: 'Buscando biotipo...', + wait: 'La IA está procesando tus fotos. Esto puede tardar hasta 30 segundos.', + steps: [ + "Identificando Biotipo...", + "Analizando Postura y Simetría...", + "Calculando Estimación de Grasa...", + "Ajustando Macros para tu Objetivo...", + "Generando Rutina de Entrenamiento..." + ] + }, + buttons: { + next: 'Siguiente', + back: 'Volver', + generate: 'Generar Protocolo' + } + } + } +}; + +interface LanguageContextType { + language: Language; + setLanguage: (lang: Language) => void; + t: Translations; +} + +const LanguageContext = createContext(undefined); + +export const LanguageProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [language, setLanguage] = useState('pt'); + + return ( + + {children} + + ); +}; + +export const useLanguage = () => { + const context = useContext(LanguageContext); + if (!context) { + throw new Error('useLanguage must be used within a LanguageProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/hooks/useCoachPlan.ts b/src/hooks/useCoachPlan.ts new file mode 100644 index 0000000..98b7237 --- /dev/null +++ b/src/hooks/useCoachPlan.ts @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; + +export const useCoachPlan = (userId: string) => { + const [coachPlan, setCoachPlan] = useState(null); + const [coachHistory, setCoachHistory] = useState([]); + const [loadingCoachPlan, setLoadingCoachPlan] = useState(false); + + const fetchCoachPlan = async () => { + if (!userId) return; + + setLoadingCoachPlan(true); + try { + const { data, error } = await supabase + .from('coach_analyses') + .select('*') + .eq('user_id', userId) + .order('created_at', { ascending: false }); + + if (error) { + console.error("Error fetching coach plan:", error); + return; + } + console.log("Coach Plan Data:", data); // DEBUG + + + if (data) { + setCoachHistory(data); + if (data.length > 0) { + // Set latest as default + const latest = data[0]; + const structured = typeof latest.ai_structured === 'string' + ? JSON.parse(latest.ai_structured) + : latest.ai_structured; + setCoachPlan(structured); + } else { + setCoachPlan(null); + } + } + } catch (err) { + console.error("Error fetching coach plan:", err); + } finally { + setLoadingCoachPlan(false); + } + }; + + useEffect(() => { + fetchCoachPlan(); + }, [userId]); + + return { coachPlan, setCoachPlan, coachHistory, loadingCoachPlan, refetchCoachPlan: fetchCoachPlan }; +}; diff --git a/src/hooks/useDashboardHistory.ts b/src/hooks/useDashboardHistory.ts new file mode 100644 index 0000000..42ed2a1 --- /dev/null +++ b/src/hooks/useDashboardHistory.ts @@ -0,0 +1,73 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; + +export const useDashboardHistory = (userId: string) => { + const [history, setHistory] = useState([]); + const [loadingHistory, setLoadingHistory] = useState(false); + + const fetchHistory = async () => { + if (!userId) return; + + setLoadingHistory(true); + try { + const { data, error } = await supabase + .from('food_analyses') + .select('*') + .eq('user_id', userId) + .order('created_at', { ascending: false }) + .limit(20); + + if (error) { + console.error("Error fetching history:", error); + setHistory([]); + return; + } + + if (data) { + const formatted = data.map((item: any) => { + // Parse do ai_structured para pegar os itens + let itemDetails = ''; + try { + // Verifica se é string antes de parsear, se já for objeto usa direto + const structured = typeof item.ai_structured === 'string' + ? JSON.parse(item.ai_structured) + : item.ai_structured; + + if (structured?.items && Array.isArray(structured.items)) { + itemDetails = structured.items.map((i: any) => i.name).join(', '); + } + } catch (e) { + console.log('Error parsing AI structure', e); + } + + // Construção da URL do Bucket + const bucketUrl = `https://mnhgpnqkwuqzpvfrwftp.supabase.co/storage/v1/object/public/consultas/${item.user_id}/${item.id}.jpg`; + + return { + id: item.id, + date: new Date(item.created_at).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }), + category: item.category || 'Refeição', + details: itemDetails, + score: item.nutrition_score || 0, + cals: Math.round(item.total_calories || 0), + protein: Math.round(item.total_protein || 0) + 'g', + carbs: Math.round(item.total_carbs || 0) + 'g', + fat: Math.round(item.total_fat || 0) + 'g', + img: bucketUrl + }; + }); + setHistory(formatted); + } + } catch (err) { + console.error("Error fetching history:", err); + } finally { + setLoadingHistory(false); + } + }; + + useEffect(() => { + fetchHistory(); + }, [userId]); + + return { history, loadingHistory, refetchHistory: fetchHistory }; +}; diff --git a/src/hooks/useDashboardStats.ts b/src/hooks/useDashboardStats.ts new file mode 100644 index 0000000..06ed0c1 --- /dev/null +++ b/src/hooks/useDashboardStats.ts @@ -0,0 +1,57 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; + +interface DashboardStats { + totalCount: number; + avgCals: number; +} + +export const useDashboardStats = (userId: string) => { + const [stats, setStats] = useState({ totalCount: 0, avgCals: 0 }); + const [loadingStats, setLoadingStats] = useState(false); + + const fetchStats = async () => { + if (!userId) return; + + setLoadingStats(true); + try { + // 1. Get Total Count + const { count, error: countError } = await supabase + .from('food_analyses') + .select('*', { count: 'exact', head: true }) + .eq('user_id', userId); + + if (countError) throw countError; + + // 2. Get Average Calories + const { data: calData, error: calError } = await supabase + .from('food_analyses') + .select('total_calories') + .eq('user_id', userId); + + if (calError) throw calError; + + let calculatedAvg = 0; + if (calData && calData.length > 0) { + const sum = calData.reduce((acc, curr) => acc + (curr.total_calories || 0), 0); + calculatedAvg = Math.round(sum / calData.length); + } + + setStats({ + totalCount: count || 0, + avgCals: calculatedAvg + }); + + } catch (err) { + console.error("Error fetching stats:", err); + } finally { + setLoadingStats(false); + } + }; + + useEffect(() => { + fetchStats(); + }, [userId]); + + return { stats, loadingStats, refetchStats: fetchStats }; +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..7edb9cc --- /dev/null +++ b/src/index.css @@ -0,0 +1,113 @@ +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Base Styles */ +:root { + --brand-primary: #059669; +} + +html { + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: 'Plus Jakarta Sans', 'Inter', sans-serif; + background-color: #f8fafc; + /* Lighter, cleaner background */ +} + +/* Premium Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Glassmorphism Utilities */ +.glass { + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.5); +} + +.glass-dark { + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeIn 0.4s ease-out forwards; +} + +/* Typography Enhancements */ +h1, +h2, +h3, +h4, +h5, +h6 { + letter-spacing: -0.02em; + /* Tight tracking for headings */ +} + +/* Selection */ +::selection { + background: rgba(16, 185, 129, 0.2); + color: #064e3b; +} + +/* Utilities not in Tailwind default config */ +.text-balance { + text-wrap: balance; +} + +/* Premium Shadows & Depth */ +.shadow-premium { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02), 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(0, 0, 0, 0.02); +} + +.shadow-glow { + box-shadow: 0 0 20px rgba(5, 150, 105, 0.15); +} + +.shadow-card-hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.05), 0 10px 10px -5px rgba(0, 0, 0, 0.01); +} + +/* Subtle Texture */ +.bg-noise { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.03'/%3E%3C/svg%3E"); +} \ No newline at end of file diff --git a/src/lib/database.types.ts b/src/lib/database.types.ts new file mode 100644 index 0000000..a977f76 --- /dev/null +++ b/src/lib/database.types.ts @@ -0,0 +1,651 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export interface Database { + public: { + Tables: { + app_settings: { + Row: { + key: string + value: string + created_at: string + updated_at: string + } + Insert: { + key: string + value: string + created_at?: string + updated_at?: string + } + Update: { + key?: string + value?: string + created_at?: string + updated_at?: string + } + Relationships: [] + } + coupons: { + Row: { + id: string + code: string + discount_percent: number + max_uses: number | null + uses_count: number | null + is_active: boolean | null + valid_until: string | null + created_at: string | null + } + Insert: { + id?: string + code: string + discount_percent: number + max_uses?: number | null + uses_count?: number | null + is_active?: boolean | null + valid_until?: string | null + created_at?: string | null + } + Update: { + id?: string + code?: string + discount_percent?: number + max_uses?: number | null + uses_count?: number | null + is_active?: boolean | null + valid_until?: string | null + created_at?: string | null + } + Relationships: [] + } + food_analyses: { + Row: { + id: string + user_id: string + source: string + image_url: string | null + ai_raw_response: string + ai_structured: Json + total_calories: number | null + total_protein: number | null + total_carbs: number | null + total_fat: number | null + total_fiber: number | null + total_sodium_mg: number | null + nutrition_score: number | null + confidence_level: string | null + used_free_quota: boolean | null + created_at: string | null + source_message_id: string | null + } + Insert: { + id?: string + user_id: string + source?: string + image_url?: string | null + ai_raw_response: string + ai_structured: Json + total_calories?: number | null + total_protein?: number | null + total_carbs?: number | null + total_fat?: number | null + total_fiber?: number | null + total_sodium_mg?: number | null + nutrition_score?: number | null + confidence_level?: string | null + used_free_quota?: boolean | null + created_at?: string | null + source_message_id?: string | null + } + Update: { + id?: string + user_id?: string + source?: string + image_url?: string | null + ai_raw_response?: string + ai_structured?: Json + total_calories?: number | null + total_protein?: number | null + total_carbs?: number | null + total_fat?: number | null + total_fiber?: number | null + total_sodium_mg?: number | null + nutrition_score?: number | null + confidence_level?: string | null + used_free_quota?: boolean | null + created_at?: string | null + source_message_id?: string | null + } + Relationships: [ + { + foreignKeyName: "food_analyses_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users" // implied, usually auth.users but referenced as generic + referencedColumns: ["id"] + } + ] + } + food_analysis_items: { + Row: { + id: string + analysis_id: string + user_id: string + name: string | null + portion: string | null + calories: number | null + protein: number | null + carbs: number | null + fat: number | null + fiber: number | null + sugar: number | null + sodium_mg: number | null + flags: Json | null + created_at: string | null + } + Insert: { + id?: string + analysis_id: string + user_id: string + name?: string | null + portion?: string | null + calories?: number | null + protein?: number | null + carbs?: number | null + fat?: number | null + fiber?: number | null + sugar?: number | null + sodium_mg?: number | null + flags?: Json | null + created_at?: string | null + } + Update: { + id?: string + analysis_id?: string + user_id?: string + name?: string | null + portion?: string | null + calories?: number | null + protein?: number | null + carbs?: number | null + fat?: number | null + fiber?: number | null + sugar?: number | null + sodium_mg?: number | null + flags?: Json | null + created_at?: string | null + } + Relationships: [ + { + foreignKeyName: "food_analysis_items_analysis_id_fkey" + columns: ["analysis_id"] + referencedRelation: "food_analyses" + referencedColumns: ["id"] + }, + { + foreignKeyName: "food_analysis_items_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + payments: { + Row: { + id: string + user_id: string | null + amount_cents: number + currency: string | null + status: string | null + plan_type: string | null + stripe_payment_id: string | null + created_at: string | null + } + Insert: { + id?: string + user_id?: string | null + amount_cents: number + currency?: string | null + status?: string | null + plan_type?: string | null + stripe_payment_id?: string | null + created_at?: string | null + } + Update: { + id?: string + user_id?: string | null + amount_cents?: number + currency?: string | null + status?: string | null + plan_type?: string | null + stripe_payment_id?: string | null + created_at?: string | null + } + Relationships: [ + { + foreignKeyName: "payments_user_id_fkey" + columns: ["user_id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + pro_assessments: { + Row: { + id: string + professional_id: string + student_id: string + date: string | null + weight: number | null + height: number | null + age: number | null + bf_percent: number | null + muscle_percent: number | null + bmi: number | null + measurements: Json | null + methodology: Json | null + photos: string[] | null + created_at: string + } + Insert: { + id?: string + professional_id: string + student_id: string + date?: string | null + weight?: number | null + height?: number | null + age?: number | null + bf_percent?: number | null + muscle_percent?: number | null + bmi?: number | null + measurements?: Json | null + methodology?: Json | null + photos?: string[] | null + created_at?: string + } + Update: { + id?: string + professional_id?: string + student_id?: string + date?: string | null + weight?: number | null + height?: number | null + age?: number | null + bf_percent?: number | null + muscle_percent?: number | null + bmi?: number | null + measurements?: Json | null + methodology?: Json | null + photos?: string[] | null + created_at?: string + } + Relationships: [ + { + foreignKeyName: "pro_assessments_professional_id_fkey" + columns: ["professional_id"] + referencedRelation: "professionals" + referencedColumns: ["id"] + }, + { + foreignKeyName: "pro_assessments_student_id_fkey" + columns: ["student_id"] + referencedRelation: "pro_students" + referencedColumns: ["id"] + } + ] + } + pro_assignments: { + Row: { + id: string + professional_id: string + student_id: string + workout_id: string + start_date: string | null + end_date: string | null + notes: string | null + created_at: string + } + Insert: { + id?: string + professional_id: string + student_id: string + workout_id: string + start_date?: string | null + end_date?: string | null + notes?: string | null + created_at?: string + } + Update: { + id?: string + professional_id?: string + student_id?: string + workout_id?: string + start_date?: string | null + end_date?: string | null + notes?: string | null + created_at?: string + } + Relationships: [ + { + foreignKeyName: "pro_assignments_professional_id_fkey" + columns: ["professional_id"] + referencedRelation: "professionals" + referencedColumns: ["id"] + }, + { + foreignKeyName: "pro_assignments_student_id_fkey" + columns: ["student_id"] + referencedRelation: "pro_students" + referencedColumns: ["id"] + }, + { + foreignKeyName: "pro_assignments_workout_id_fkey" + columns: ["workout_id"] + referencedRelation: "pro_workouts" + referencedColumns: ["id"] + } + ] + } + pro_students: { + Row: { + id: string + professional_id: string + name: string + email: string | null + phone: string | null + status: 'active' | 'inactive' | 'pending' | null + linked_user_id: string | null + goals: string | null + notes: string | null + created_at: string + updated_at: string + } + Insert: { + id?: string + professional_id: string + name: string + email?: string | null + phone?: string | null + status?: 'active' | 'inactive' | 'pending' | null + linked_user_id?: string | null + goals?: string | null + notes?: string | null + created_at?: string + updated_at?: string + } + Update: { + id?: string + professional_id?: string + name?: string + email?: string | null + phone?: string | null + status?: 'active' | 'inactive' | 'pending' | null + linked_user_id?: string | null + goals?: string | null + notes?: string | null + created_at?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "pro_students_professional_id_fkey" + columns: ["professional_id"] + referencedRelation: "professionals" + referencedColumns: ["id"] + }, + { + foreignKeyName: "pro_students_linked_user_id_fkey" + columns: ["linked_user_id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + pro_workouts: { + Row: { + id: string + professional_id: string + title: string + description: string | null + difficulty: 'beginner' | 'intermediate' | 'advanced' | null + exercises: Json | null + tags: string[] | null + created_at: string + } + Insert: { + id?: string + professional_id: string + title: string + description?: string | null + difficulty?: 'beginner' | 'intermediate' | 'advanced' | null + exercises?: Json | null + tags?: string[] | null + created_at?: string + } + Update: { + id?: string + professional_id?: string + title?: string + description?: string | null + difficulty?: 'beginner' | 'intermediate' | 'advanced' | null + exercises?: Json | null + tags?: string[] | null + created_at?: string + } + Relationships: [ + { + foreignKeyName: "pro_workouts_professional_id_fkey" + columns: ["professional_id"] + referencedRelation: "professionals" + referencedColumns: ["id"] + } + ] + } + professionals: { + Row: { + id: string + business_name: string | null + cref_crn: string | null + bio: string | null + specialties: string[] | null + logo_url: string | null + primary_color: string | null + contacts: Json | null + created_at: string + updated_at: string + } + Insert: { + id: string + business_name?: string | null + cref_crn?: string | null + bio?: string | null + specialties?: string[] | null + logo_url?: string | null + primary_color?: string | null + contacts?: Json | null + created_at?: string + updated_at?: string + } + Update: { + id?: string + business_name?: string | null + cref_crn?: string | null + bio?: string | null + specialties?: string[] | null + logo_url?: string | null + primary_color?: string | null + contacts?: Json | null + created_at?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "professionals_id_fkey" + columns: ["id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + profiles: { + Row: { + id: string + full_name: string + email: string + phone: string | null + created_at: string | null + updated_at: string | null + public_id: string | null + phone_e164: string | null + is_admin: boolean | null + is_professional: boolean | null + avatar_url: string | null + } + Insert: { + id: string + full_name: string + email: string + phone?: string | null + created_at?: string | null + updated_at?: string | null + public_id?: string | null + phone_e164?: string | null + is_admin?: boolean | null + is_professional?: boolean | null + avatar_url?: string | null + } + Update: { + id?: string + full_name?: string + email?: string + phone?: string | null + created_at?: string | null + updated_at?: string | null + public_id?: string | null + phone_e164?: string | null + is_admin?: boolean | null + is_professional?: boolean | null + avatar_url?: string | null + } + Relationships: [ + { + foreignKeyName: "profiles_id_fkey" + columns: ["id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + stripe_customers: { + Row: { + user_id: string + stripe_customer_id: string + email: string | null + created_at: string + updated_at: string + } + Insert: { + user_id: string + stripe_customer_id: string + email?: string | null + created_at?: string + updated_at?: string + } + Update: { + user_id?: string + stripe_customer_id?: string + email?: string | null + created_at?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "stripe_customers_pkey" // It's actually a PK but often a FK too + columns: ["user_id"] + referencedRelation: "users" // implicit + referencedColumns: ["id"] + } + ] + } + stripe_events: { + Row: { + id: string + type: string | null + created_at: string + } + Insert: { + id: string + type?: string | null + created_at?: string + } + Update: { + id?: string + type?: string | null + created_at?: string + } + Relationships: [] + } + user_entitlements: { + Row: { + user_id: string + entitlement_code: string + is_trial: boolean + is_active: boolean + valid_until: string | null + usage: Json + created_at: string + updated_at: string + plan_type: string | null + } + Insert: { + user_id: string + entitlement_code: string + is_trial?: boolean + is_active?: boolean + valid_until?: string | null + usage?: Json + created_at?: string + updated_at?: string + plan_type?: string | null + } + Update: { + user_id?: string + entitlement_code?: string + is_trial?: boolean + is_active?: boolean + valid_until?: string | null + usage?: Json + created_at?: string + updated_at?: string + plan_type?: string | null + } + Relationships: [ + { + foreignKeyName: "user_entitlements_pkey" + columns: ["user_id"] + referencedRelation: "users" + referencedColumns: ["id"] + } + ] + } + } + Views: { + user_access_summary: { + Row: { + user_id: string | null + free_used: number | null + free_remaining: number | null + plan_active: boolean | null + plan_code: string | null + plan_started_at: string | null + plan_valid_until: string | null + can_use_paid: boolean | null + } + } + } + } +} diff --git a/src/lib/gemini.ts b/src/lib/gemini.ts new file mode 100644 index 0000000..60cdc9b --- /dev/null +++ b/src/lib/gemini.ts @@ -0,0 +1,112 @@ +import { GoogleGenAI } from "@google/genai"; + +const SYSTEM_PROMPT = ` +Você é o FoodSnap.ai, um nutricionista comportamental e científico. +Analise a imagem enviada e retorne um JSON puro (sem markdown) seguindo estritamente este schema: + +{ + "items": [ + { + "name": "Nome do alimento", + "portion": "Quantidade estimada (ex: 150g, 1 unidade)", + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0, + "fiber": 0, + "sugar": 0, + "sodium_mg": 0, + "flags": ["fritura", "processado", "saudavel", "alto_acucar"] + } + ], + "total": { + "calories": 0, + "protein": 0, + "carbs": 0, + "fat": 0, + "fiber": 0, + "sugar": 0, + "sodium_mg": 0 + }, + "category": "Café da Manhã" | "Almoço" | "Jantar" | "Lanche" | "Pré-Treino" | "Pós-Treino", + "health_score": 0, + "confidence": "alta" | "media" | "baixa", + "tip": { + "title": "Titulo curto", + "text": "Dica prática e motivadora de até 2 frases sobre a refeição.", + "reason": "Explicação científica curta" + } +} + +Regras: +1. Health Score de 0 a 100. Considere densidade nutritiva, não apenas calorias. +2. Se não identificar comida, retorne lista de itens vazia e confidence "baixa". +`; + +export interface AnalysisResult { + items: { + name: string; + portion: string; + calories: number; + protein: number; + carbs: number; + fat: number; + fiber: number; + sugar: number; + sodium_mg: number; + flags: string[]; + }[]; + total: { + calories: number; + protein: number; + carbs: number; + fat: number; + fiber: number; + sugar: number; + sodium_mg: number; + }; + category: string; + health_score: number; + confidence: 'alta' | 'media' | 'baixa'; + tip: { + title: string; + text: string; + reason: string; + }; +} + +export const analyzeImage = async (base64Image: string, mimeType: string = 'image/jpeg'): Promise => { + const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); + + try { + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: { + parts: [ + { + inlineData: { + mimeType: mimeType, + data: base64Image + } + }, + { + text: SYSTEM_PROMPT + } + ] + }, + config: { + responseMimeType: 'application/json', + temperature: 0.1 + } + }); + + if (response.text) { + return JSON.parse(response.text) as AnalysisResult; + } + + throw new Error("Resposta vazia da IA"); + } catch (error) { + console.error("Erro na análise Gemini:", error); + throw error; + } +}; \ No newline at end of file diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..2f4d946 --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,8 @@ +import { createClient } from "@supabase/supabase-js"; +import { Database } from "./database.types"; + +// Credentials provided in the prompt +const SUPABASE_URL = "https://mnhgpnqkwuqzpvfrwftp.supabase.co"; +const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1uaGdwbnFrd3VxenB2ZnJ3ZnRwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjU3MTk4NTUsImV4cCI6MjA4MTI5NTg1NX0.DBYmhgiZoCmA0AlejJRsTh85HxRDEnG_ihkEQ2cXcpk"; + +export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..30597ce --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; +import './index.css'; + +const container = document.getElementById('root'); + +if (container) { + const root = createRoot(container); + root.render( + + + + ); +} else { + console.error("FATAL: Elemento root não encontrado no HTML."); +} \ No newline at end of file diff --git a/src/n8n-coach-whatsapp.json b/src/n8n-coach-whatsapp.json new file mode 100644 index 0000000..dc3e5a3 --- /dev/null +++ b/src/n8n-coach-whatsapp.json @@ -0,0 +1,572 @@ +{ + "name": "FoodSnap - Coach AI (WhatsApp)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "wa/coach-inbound", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -500, + -940 + ], + "id": "webhook-coach", + "name": "Webhook (Whatsapp)" + }, + { + "parameters": { + "jsCode": "const body = $json.body ?? $json;\nconst data = body.data ?? {};\n\nconst remoteJid = data?.key?.remoteJid?.includes('@s.whatsapp.net') ? data.key.remoteJid : data?.key?.remoteJidAlt || '';\nconst number = remoteJid.replace(/\\D/g, '');\nconst message_id = data?.key?.id || '';\nconst text = data?.message?.conversation || data?.message?.extendedTextMessage?.text || '';\n\n// Check for image\nconst imageMessage = data?.message?.imageMessage || data?.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || null;\n\nreturn [{\n number,\n remoteJid,\n message_id,\n text,\n hasImage: !!imageMessage,\n imageMessage,\n username: data?.pushName || 'Atleta'\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -280, + -940 + ], + "id": "normalize-inbound", + "name": "Normalizar Dados" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "select * from check_access_by_whatsapp('{{ $json.number }}')", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + -60, + -940 + ], + "id": "validate-user", + "name": "Validar Usuario (RPC)", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-user", + "leftValue": "={{ $json.exists }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 160, + -940 + ], + "id": "if-exists", + "name": "Usuario Existe?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-process", + "leftValue": "={{ $json.can_process }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 400, + -1040 + ], + "id": "if-quota", + "name": "Tem Quota/Plano?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-image", + "leftValue": "={{ $node[\"Normalizar Dados\"].json.hasImage }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 640, + -940 + ], + "id": "if-image", + "name": "Tem Imagem?" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "💪 *Coach AI*: Olá! Envie uma foto do seu corpo (preferencialmente de frente, roupa de treino) para eu fazer uma análise rápida do seu biótipo e sugestão de treino.", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 860, + -840 + ], + "id": "msg-intro", + "name": "Msg Intro", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "🧐 Analisando seu físico... Um momento!", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 860, + -1040 + ], + "id": "msg-ack", + "name": "Msg Ack", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "chat-api", + "operation": "get-media-base64", + "instanceName": "FoodSnap", + "messageId": "={{ $('Normalizar Dados').item.json.message_id }}" + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 1080, + -1040 + ], + "id": "get-image", + "name": "Baixar Imagem", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "operation": "toBinary", + "sourceProperty": "data.base64", + "options": {} + }, + "type": "n8n-nodes-base.convertToFile", + "typeVersion": 1.1, + "position": [ + 1300, + -1040 + ], + "id": "convert-binary", + "name": "Converter Binario" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-pro-vision", + "mode": "list", + "cachedResultName": "models/gemini-pro-vision" + }, + "text": "=Você é um Treinador Físico de Elite e Nutricionista Esportivo.\nAnalise a imagem fornecida (foto de corpo inteiro/físico).\n\n1. Verifique se é uma foto de corpo humano válida para análise fitness. Se não, retorne \"valid_body\": false.\n2. Se for válida, estime:\n - Biótipo Predominante (Ectomorfo, Mesomorfo, Endomorfo)\n - Percentual de Gordura (BF% aproximado)\n - Nível de Massa Muscular (Baixo, Médio, Alto)\n3. Sugira:\n - Objetivo Principal (Cutting, Bulking, Manutenção)\n - Divisão de Treino Recomendada (ex: ABC, ABCDE, Upper/Lower)\n - Foco Dietético (ex: Carb Cycling, Alto Carbo, Keto)\n - Dica de Ouro (uma frase curta de impacto)\n\nResponda APENAS em JSON estrito (sem markdown):\n{\n \"valid_body\": true,\n \"biotype\": \"...\",\n \"estimated_body_fat\": 15,\n \"muscle_mass\": \"Médio\",\n \"goal\": \"Bulking\",\n \"workout\": \"...\",\n \"diet\": \"...\",\n \"tip\": \"...\"\n}", + "inputType": "binary", + "simplify": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 1520, + -1100 + ], + "id": "analyze-gemini", + "name": "Gemini Coach Analysis", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "jsCode": "const raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data = {};\ntry { \n data = JSON.parse(clean); \n} catch(e) {\n data = { valid_body: false, error: 'Failed to parse AI' };\n}\n\nreturn [{ ...data }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1740, + -1100 + ], + "id": "parse-response", + "name": "Parse AI JSON" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-validity", + "leftValue": "={{ $json.valid_body }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1960, + -1100 + ], + "id": "if-valid-body", + "name": "Corpo Valido?" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "insert into public.coach_analyses\n(\n user_id,\n source,\n image_url,\n ai_raw_response,\n ai_structured,\n biotype,\n estimated_body_fat,\n goal_suggestion,\n muscle_mass_level,\n used_free_quota\n)\nvalues\n(\n cast('{{ $(\"Validar Usuario & Quota\").item.json.user_id }}' as uuid),\n 'whatsapp',\n null,\n '{{ JSON.stringify($json) }}',\n '{{ JSON.stringify($json) }}',\n '{{ $json.biotype }}',\n cast({{ $json.estimated_body_fat || 0 }} as numeric),\n '{{ $json.goal }}',\n '{{ $json.muscle_mass }}',\n CASE\n WHEN {{ $(\"Validar Usuario & Quota\").item.json.plan_active }} = true THEN false\n ELSE true\n END\n)\nreturning id as analysis_id;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 2200, + -1180 + ], + "id": "save-db", + "name": "Salvar DB", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "=⚡ *Análise Coach AI*\n\n🧬 *Biótipo*: {{$json.biotype}}\n⚖️ *Gordura (BF)*: ~{{$json.estimated_body_fat}}%\n💪 *Massa Muscular*: {{$json.muscle_mass}}\n\n🎯 *Objetivo Sugerido*: {{$json.goal}}\n🏋️ *Treino*: {{$json.workout}}\n🥗 *Dieta*: {{$json.diet}}\n\n💡 *Dica*: {{$json.tip}}\n\n_Para ver o plano completo, acesse o App!_", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 2420, + -1180 + ], + "id": "reply-success", + "name": "Responder Resultado", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "⚠️ Não consegui identificar um físico claro nesta foto. Tente enviar uma foto de corpo inteiro ou tronco, com boa iluminação.", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 2200, + -980 + ], + "id": "reply-invalid", + "name": "Responder Invalido", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "🚫 *Limite do Coach Atingido*\n\nVocê já usou suas 3 análises de Coach gratuitas. Assine o plano PRO para avaliações ilimitadas! 🚀", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 640, + -1140 + ], + "id": "reply-limit", + "name": "Msg Limite", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + } + ], + "connections": { + "Webhook (Whatsapp)": { + "main": [ + [ + { + "node": "Normalizar Dados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Normalizar Dados": { + "main": [ + [ + { + "node": "Validar Usuario & Quota", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar Usuario & Quota": { + "main": [ + [ + { + "node": "Usuario Existe?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Usuario Existe?": { + "main": [ + [ + { + "node": "Tem Quota/Plano?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tem Quota/Plano?": { + "main": [ + [ + { + "node": "Tem Imagem?", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Msg Limite", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tem Imagem?": { + "main": [ + [ + { + "node": "Msg Ack", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Msg Intro", + "type": "main", + "index": 0 + } + ] + ] + }, + "Msg Ack": { + "main": [ + [ + { + "node": "Baixar Imagem", + "type": "main", + "index": 0 + } + ] + ] + }, + "Baixar Imagem": { + "main": [ + [ + { + "node": "Converter Binario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Converter Binario": { + "main": [ + [ + { + "node": "Gemini Coach Analysis", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gemini Coach Analysis": { + "main": [ + [ + { + "node": "Parse AI JSON", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse AI JSON": { + "main": [ + [ + { + "node": "Corpo Valido?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Corpo Valido?": { + "main": [ + [ + { + "node": "Salvar DB", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Responder Invalido", + "type": "main", + "index": 0 + } + ] + ] + }, + "Salvar DB": { + "main": [ + [ + { + "node": "Responder Resultado", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/src/n8n-daily-report.json b/src/n8n-daily-report.json new file mode 100644 index 0000000..e9d3a72 --- /dev/null +++ b/src/n8n-daily-report.json @@ -0,0 +1,199 @@ +{ + "name": "FoodSnap - Daily Report (Cron)", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 20 * * *" + } + ] + } + }, + "type": "n8n-nodes-base.schedule", + "typeVersion": 1.1, + "position": [ + -300, + -740 + ], + "id": "schedule-trigger", + "name": "Every Day 20h" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "SELECT \n p.id as user_id,\n p.phone_e164\nFROM public.profiles p\nJOIN public.food_analyses f ON f.user_id = p.id\nWHERE f.created_at >= CURRENT_DATE\nGROUP BY p.id, p.phone_e164;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + -80, + -740 + ], + "id": "get-active-users", + "name": "Get Users Active Today", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "batchSize": 1, + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 140, + -740 + ], + "id": "split-users", + "name": "Split Users" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "SELECT \n SUM(total_calories) as total_cals, \n SUM(total_protein) as total_prot,\n SUM(total_carbs) as total_carbs, \n SUM(total_fat) as total_fat,\n COUNT(*) as meal_count,\n AVG(nutrition_score)::numeric(10,1) as avg_score\nFROM public.food_analyses \nWHERE user_id = '{{ $json.user_id }}'::uuid \nAND created_at >= CURRENT_DATE;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 360, + -740 + ], + "id": "get-daily-stats", + "name": "Get Daily Stats", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "modelId": { + "__rl": true, + "value": "models/gemini-pro", + "mode": "list", + "cachedResultName": "models/gemini-pro" + }, + "promptType": "define", + "text": "=Você é um Nutricionista IA do FoodSnap.\n\nDados do Usuário Hoje:\n- Refeições: {{ $json.meal_count }}\n- Calorias Totais: {{ $json.total_cals }} kcal\n- Proteínas: {{ $json.total_prot }}g\n- Carbos: {{ $json.total_carbs }}g\n- Gorduras: {{ $json.total_fat }}g\n- Score Médio (0-100): {{ $json.avg_score }}\n\nCrie uma mensagem curta (máx 3 linhas) para o WhatsApp.\n1. Elogie se bateu meta (assuma 2000kcal base se não tiver dado).\n2. Dê uma dica rápida para amanhã baseada nos macros.\n3. Termine motivacional.\n4. Use emojis.\n5. Não use markdown bold (*) excessivamente, só em palavras chave.\n\nExemplo:\n\"Olá! Hoje você mandou bem nas proteínas (120g)! 💪 Amanhã tente reduzir um pouco a gordura no jantar. Continue assim! 🚀\"", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 580, + -740 + ], + "id": "generate-insight", + "name": "Generate Insight", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Split Users').item.json.phone_e164.replace('+', '') }}", + "messageText": "=📊 *FoodSnap Diário*\n\nHoje você registrou {{ $('Get Daily Stats').item.json.meal_count }} refeições.\n🔥 *{{ $('Get Daily Stats').item.json.total_cals }} kcal* | 🥩 {{ $('Get Daily Stats').item.json.total_prot }}g Prot\n\n{{ $json.text }}\n\n_Até amanhã!_", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 800, + -740 + ], + "id": "send-whatsapp", + "name": "Enviar WhatsApp", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + } + ], + "connections": { + "Every Day 20h": { + "main": [ + [ + { + "node": "Get Users Active Today", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Users Active Today": { + "main": [ + [ + { + "node": "Split Users", + "type": "main", + "index": 0 + } + ] + ] + }, + "Split Users": { + "main": [ + [ + { + "node": "Get Daily Stats", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Daily Stats": { + "main": [ + [ + { + "node": "Generate Insight", + "type": "main", + "index": 0 + } + ] + ] + }, + "Generate Insight": { + "main": [ + [ + { + "node": "Enviar WhatsApp", + "type": "main", + "index": 0 + } + ] + ] + }, + "Enviar WhatsApp": { + "main": [ + [ + { + "node": "Split Users", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/src/n8n-foodsnap-branched.json b/src/n8n-foodsnap-branched.json new file mode 100644 index 0000000..213042a --- /dev/null +++ b/src/n8n-foodsnap-branched.json @@ -0,0 +1,911 @@ +{ + "name": "FoodSnap - Switch (Food & Coach)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "wa/inbound", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -740, + -940 + ], + "id": "f33b8fb6-babb-4beb-ab36-ec6a25f14eb2", + "name": "Requisicao - Whatsapp", + "webhookId": "2179d0c4-aaf5-4ce4-9463-332f09919612" + }, + { + "parameters": { + "jsCode": "const body = $json.body ?? $json;\nconst data = body.data ?? {};\n\nconst remoteJid = data?.key?.remoteJid?.includes('@s.whatsapp.net') ? data.key.remoteJid : data?.key?.remoteJidAlt || '';\nconst number = remoteJid.replace(/\\D/g, '');\nconst message_id = data?.key?.id || '';\n\n// Texto (incluindo legenda de imagem)\nconst text = data?.message?.conversation || data?.message?.extendedTextMessage?.text || data?.message?.imageMessage?.caption || '';\n\nconst imageMessage = data?.message?.imageMessage || data?.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage || null;\n\nreturn [{\n number,\n remoteJid,\n message_id,\n text,\n hasImage: !!imageMessage,\n imageMessage,\n pushName: data?.pushName || 'Usuário',\n timestamp: new Date().toISOString(),\n raw: body\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -520, + -940 + ], + "id": "32f29e03-c120-4425-b8da-5f9984503e63", + "name": "NormalizeInbound" + }, + { + "parameters": { + "dataType": "string", + "value1": "={{ $json.text }}", + "rules": { + "rules": [ + { + "operation": "contains", + "value2": "coach", + "output": 1 + }, + { + "operation": "contains", + "value2": "treino", + "output": 1 + }, + { + "operation": "contains", + "value2": "biotipo", + "output": 1 + }, + { + "operation": "contains", + "value2": "shape", + "output": 1 + } + ] + }, + "fallbackOutput": 0 + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 1, + "position": [ + -300, + -940 + ], + "id": "switch-router", + "name": "Roteador (Food/Coach)" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "with u as ( select id from public.profiles where phone_e164 = cast({{ $('NormalizeInbound').item.json.number }} as text) limit 1 ), ent as ( select ue.user_id, ue.is_active, ue.entitlement_code, ue.valid_until from public.user_entitlements ue where ue.user_id = (select id from u) order by ue.valid_until desc nulls last limit 1 ), usage as ( select (select id from u) as user_id, count(*) filter (where fa.used_free_quota = true) as free_used from public.food_analyses fa where fa.user_id = (select id from u) ) select (select id from u) is not null as exists, (select id from u) as user_id, coalesce((select free_used from usage), 0)::int as free_used, greatest(0, 5 - coalesce((select free_used from usage), 0))::int as free_remaining, coalesce((select is_active from ent), false) as plan_active, ( (select id from u) is not null and ( ( coalesce((select is_active from ent), false) and ( (select valid_until from ent) is null or (select valid_until from ent) > now() ) ) or greatest(0, 5 - coalesce((select free_used from usage), 0)) > 0 ) ) as can_process;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + -60, + -1040 + ], + "id": "a329a262-e03c-41f2-9d96-5d37ed5f6159", + "name": "Validar Usuario (Food)", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "operation": "executeQuery", + "query": "with u as ( select id from public.profiles where phone_e164 = cast({{ $('NormalizeInbound').item.json.number }} as text) limit 1 ), ent as ( select ue.is_active, ue.entitlement_code, ue.valid_until from public.user_entitlements ue where ue.user_id = (select id from u) order by ue.valid_until desc nulls last limit 1 ), usage as ( select count(*) as used_count from public.coach_analyses fa where fa.user_id = (select id from u) and fa.used_free_quota = true ) select (select id from u) as user_id, (select id from u) is not null as exists, coalesce((select used_count from usage), 0)::int as free_used, greatest(0, 3 - coalesce((select used_count from usage), 0))::int as free_remaining, coalesce((select is_active from ent), false) as plan_active, (coalesce((select is_active from ent), false) = true OR greatest(0, 3 - coalesce((select used_count from usage), 0)) > 0) as can_process", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + -60, + -700 + ], + "id": "validate-coach", + "name": "Validar Coach", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-process-coach", + "leftValue": "={{ $json.can_process }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 180, + -700 + ], + "id": "if-coach-quota", + "name": "Pode usar Coach?" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "🏋️ *Coach AI*: Analisando seu físico... Aguarde!", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 420, + -700 + ], + "id": "msg-ack-coach", + "name": "Ack Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "chat-api", + "operation": "get-media-base64", + "instanceName": "FoodSnap", + "messageId": "={{ $('NormalizeInbound').item.json.message_id }}" + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 640, + -700 + ], + "id": "get-img-coach", + "name": "Baixar IMG Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "operation": "toBinary", + "sourceProperty": "data.base64", + "options": {} + }, + "type": "n8n-nodes-base.convertToFile", + "typeVersion": 1.1, + "position": [ + 860, + -700 + ], + "id": "bin-coach", + "name": "Binary Coach" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash" + }, + "text": "=Você é um Treinador Físico de Elite e Nutricionista Esportivo.\nAnalise a imagem fornecida (foto de corpo inteiro/físico).\n\n1. Verifique se é uma foto de corpo humano válida para análise fitness. Se não, retorne \"valid_body\": false.\n2. Se for válida, estime:\n - Biótipo Predominante (Ectomorfo, Mesomorfo, Endomorfo)\n - Percentual de Gordura (BF% aproximado)\n - Nível de Massa Muscular (Baixo, Médio, Alto)\n3. Sugira:\n - Objetivo Principal (Cutting, Bulking, Manutenção)\n - Divisão de Treino Recomendada (ex: ABC, ABCDE, Upper/Lower)\n - Foco Dietético (ex: Carb Cycling, Alto Carbo, Keto)\n - Dica de Ouro (uma frase curta de impacto)\n\nResponda APENAS em JSON estrito (sem markdown):\n{\n \"valid_body\": true,\n \"biotype\": \"...\",\n \"estimated_body_fat\": 15,\n \"muscle_mass\": \"Médio\",\n \"goal\": \"Bulking\",\n \"workout\": \"...\",\n \"diet\": \"...\",\n \"tip\": \"...\"\n}", + "inputType": "binary", + "simplify": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 1080, + -700 + ], + "id": "gemini-coach", + "name": "Gemini Coach", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "jsCode": "const raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data = {};\ntry { \n data = JSON.parse(clean); \n} catch(e) {\n data = { valid_body: false, error: 'Failed to parse AI' };\n}\n\nreturn [{ ...data }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1300, + -700 + ], + "id": "parse-coach", + "name": "Parse Coach" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "insert into public.coach_analyses\n(\n user_id,\n source,\n image_url,\n ai_raw_response,\n ai_structured,\n biotype,\n estimated_body_fat,\n goal_suggestion,\n muscle_mass_level,\n used_free_quota\n)\nvalues\n(\n cast('{{ $(\"Validar Coach\").item.json.user_id }}' as uuid),\n 'whatsapp',\n null,\n '{{ JSON.stringify($json) }}',\n '{{ JSON.stringify($json) }}',\n '{{ $json.biotype }}',\n cast({{ $json.estimated_body_fat || 0 }} as numeric),\n '{{ $json.goal }}',\n '{{ $json.muscle_mass }}',\n CASE\n WHEN {{ $(\"Validar Coach\").item.json.plan_active }} = true THEN false\n ELSE true\n END\n)\nreturning id as analysis_id;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 1520, + -700 + ], + "id": "save-coach", + "name": "Salvar Coach DB", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "=⚡ *Coach AI*\n\n🧬 *Biótipo*: {{$json.biotype}}\n⚖️ *BF*: ~{{$json.estimated_body_fat}}%\n💪 *Massa*: {{$json.muscle_mass}}\n\n🎯 *Foco*: {{$json.goal}}\n🏋️ *Treino*: {{$json.workout}}\n\n💡 *Dica*: {{$json.tip}}\n\n_Veja mais no App!_", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 1740, + -700 + ], + "id": "reply-coach", + "name": "Responder Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "70a7760f-4a83-4a80-bbbc-9eaf93a06a33", + "leftValue": "={{$json.exists}}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 160, + -1040 + ], + "id": "9e1ea558-2466-4426-9c9f-f5051e76da4f", + "name": "Usuário existe?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "873dc279-9223-464a-b632-bf019f20c030", + "leftValue": "={{ $json.can_process }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 380, + -1120 + ], + "id": "63ea8c4f-ebe3-4c0b-95bb-51dc0c64d639", + "name": "Pode usar Food?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "a5e5b4e5-ce26-4b2b-90e6-ba96e55006a8", + "leftValue": "={{$node[\"NormalizeInbound\"].json.hasImage}}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 600, + -1020 + ], + "id": "7130f3dd-fc60-4cba-b064-d7d98e846b86", + "name": "If texto? imagem?" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "👋 Olá! Envie uma *foto do prato* para calorias ou escreva *'Coach'* e envie uma foto do corpo para análise física.", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 820, + -920 + ], + "id": "5f897b38-e120-4f68-8870-6d793d22a3ff", + "name": "Enviar texto help", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "📸 Recebi sua foto! Analisando o prato... ⏳", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 820, + -1120 + ], + "id": "704eec2f-2f98-4872-b414-c805f0642ef3", + "name": "Ack_Recebi_Foto", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "chat-api", + "operation": "get-media-base64", + "instanceName": "FoodSnap", + "messageId": "={{ $('Requisicao - Whatsapp').item.json.body.data.key.id }}" + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 1040, + -1120 + ], + "id": "200672ed-2daf-46ee-9853-08bff8b55c86", + "name": "Imagem Base64", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "operation": "toBinary", + "sourceProperty": "data.base64", + "options": {} + }, + "type": "n8n-nodes-base.convertToFile", + "typeVersion": 1.1, + "position": [ + 1260, + -1120 + ], + "id": "0076759b-6f96-4f58-a828-bd329a803054", + "name": "Converter Base64/Binario" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-2.5-flash", + "mode": "list", + "cachedResultName": "models/gemini-2.5-flash" + }, + "text": "=Você é um assistente nutricional... (Prompt Original de Comida)... Retorne SOMENTE JSON.", + "inputType": "binary", + "simplify": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 1480, + -1120 + ], + "id": "629515c5-8021-4162-9a5f-aac2c5f4cb82", + "name": "Analyze an image Food", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "jsCode": "// Código original de limpeza do Food\nconst raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\n// ... continuação do código original ...\nconst clean = raw.replace(/```json/gi, \"\").replace(/```/g, \"\").trim();\nlet parsed = JSON.parse(clean);\n// ... normalizações ...\nreturn [parsed];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1700, + -1120 + ], + "id": "6c7ed1f5-61d5-4911-b518-6bb3ff1205b5", + "name": "Limpar Resultado Food" + }, + { + "parameters": { + "jsCode": "const payload = Array.isArray($json) ? $json[0] : $json;\nconst sender = payload?.sender || $node[\"NormalizeInbound\"]?.json?.number || \"\";\nconst analysis_json = payload && typeof payload === \"object\" ? payload : {};\nconst updated_at = new Date().toISOString();\nreturn [{ sender, analysis_json, updated_at }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1920, + -1120 + ], + "id": "b6f194fa-71ba-4b05-b448-fdb67957ae1b", + "name": "Salvar Analise Prep" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "insert into public.food_analyses (user_id, source, ai_raw_response, ai_structured, total_calories) values (cast('{{ $(\"Validar Usuario (Food)\").item.json.user_id }}' as uuid), 'whatsapp', cast('{{ $node[\"Analyze an image Food\"].json.candidates[0].content.parts[0].text }}' as text), cast('{{ JSON.stringify($node[\"Limpar Resultado Food\"].json) }}' as jsonb), cast({{ $node[\"Limpar Resultado Food\"].json.total.calories }} as int)) returning id as analysis_id;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 2140, + -1120 + ], + "id": "556c631a-4a54-4cc1-b6f1-dd0473135a0f", + "name": "Salvar historico Food", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "jsCode": "const items = $json.analysis_json?.items || [];\nconst total = $json.analysis_json?.total || {};\nconst lines = [\"🥗 *RELATÓRIO PRATOFIT*\"];\n// ... lógica original de formatação ...\nreturn [{ text: lines.join(\"\\n\") }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2360, + -1120 + ], + "id": "e3bdb17a-fb9c-4178-b751-3c80ee616bd7", + "name": "Formatar Resposta Food" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{$node[\"NormalizeInbound\"].json.number}}", + "messageText": "={{$json.text}}", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 2580, + -1120 + ], + "id": "e887d0a0-9bcb-4820-95dc-9c115a9b2a48", + "name": "Resposta WPP Food", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "🚫 *Acesso restrito* Seu número não está cadastrado. Cadastre-se em: https://foodsnap.com.br", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 400, + -880 + ], + "id": "c9352ebc-63bc-421a-9e79-a98492ec996a", + "name": "Nao Cadastrado" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('NormalizeInbound').item.json.number }}", + "messageText": "🚫 Limite gratuito atingido. Assine um plano em foodsnap.com.br", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 600, + -1220 + ], + "id": "baea905c-add5-48a8-9c9e-81441c6c56d9", + "name": "Sem Plano Food", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + } + ], + "connections": { + "Requisicao - Whatsapp": { + "main": [ + [ + { + "node": "NormalizeInbound", + "type": "main", + "index": 0 + } + ] + ] + }, + "NormalizeInbound": { + "main": [ + [ + { + "node": "Roteador (Food/Coach)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Roteador (Food/Coach)": { + "main": [ + [ + { + "node": "Validar Usuario (Food)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Validar Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar Usuario (Food)": { + "main": [ + [ + { + "node": "Usuário existe?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Usuário existe?": { + "main": [ + [ + { + "node": "Pode usar Food?", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Nao Cadastrado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Pode usar Food?": { + "main": [ + [ + { + "node": "If texto? imagem?", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Sem Plano Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "If texto? imagem?": { + "main": [ + [ + { + "node": "Ack_Recebi_Foto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Enviar texto help", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ack_Recebi_Foto": { + "main": [ + [ + { + "node": "Imagem Base64", + "type": "main", + "index": 0 + } + ] + ] + }, + "Imagem Base64": { + "main": [ + [ + { + "node": "Converter Base64/Binario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Converter Base64/Binario": { + "main": [ + [ + { + "node": "Analyze an image Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Analyze an image Food": { + "main": [ + [ + { + "node": "Limpar Resultado Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Limpar Resultado Food": { + "main": [ + [ + { + "node": "Salvar Analise Prep", + "type": "main", + "index": 0 + } + ] + ] + }, + "Salvar Analise Prep": { + "main": [ + [ + { + "node": "Salvar historico Food", + "type": "main", + "index": 0 + }, + { + "node": "Formatar Resposta Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Formatar Resposta Food": { + "main": [ + [ + { + "node": "Resposta WPP Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validar Coach": { + "main": [ + [ + { + "node": "Pode usar Coach?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Pode usar Coach?": { + "main": [ + [ + { + "node": "Ack Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ack Coach": { + "main": [ + [ + { + "node": "Baixar IMG Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Baixar IMG Coach": { + "main": [ + [ + { + "node": "Binary Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Binary Coach": { + "main": [ + [ + { + "node": "Gemini Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gemini Coach": { + "main": [ + [ + { + "node": "Parse Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Coach": { + "main": [ + [ + { + "node": "Salvar Coach DB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Salvar Coach DB": { + "main": [ + [ + { + "node": "Responder Coach", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/src/n8n-foodsnap-unified.json b/src/n8n-foodsnap-unified.json new file mode 100644 index 0000000..4710b91 --- /dev/null +++ b/src/n8n-foodsnap-unified.json @@ -0,0 +1,713 @@ +{ + "name": "FoodSnap - Unified (Food & Coach)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "wa/inbound", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -680, + -940 + ], + "id": "webhook-unified", + "name": "Webhook (Whatsapp)" + }, + { + "parameters": { + "jsCode": "const body = $json.body ?? $json;\nconst data = body.data ?? {};\n\n// =========================\n// RemoteJid (prioridade s.whatsapp.net)\n// =========================\nconst remoteJid =\n data?.key?.remoteJid?.includes('@s.whatsapp.net')\n ? data.key.remoteJid\n : data?.key?.remoteJidAlt || '';\n\n// número limpo (E.164 sem +)\nconst number = remoteJid.replace(/\\D/g, '');\n\n// =========================\n// Message ID\n// =========================\nconst message_id = data?.key?.id || '';\n\n// =========================\n// Texto e Caption\n// =========================\n// Verifica conversation, extendedTextMessage (text) e imageMessage (caption)\nconst text =\n data?.message?.conversation ||\n data?.message?.extendedTextMessage?.text ||\n data?.message?.imageMessage?.caption ||\n '';\n\n// =========================\n// Imagem\n// =========================\nconst imageMessage =\n data?.message?.imageMessage ||\n data?.message?.extendedTextMessage?.contextInfo?.quotedMessage?.imageMessage ||\n null;\n\n// =========================\n// Return normalizado\n// =========================\nreturn [\n {\n number,\n remoteJid,\n message_id,\n text,\n hasImage: !!imageMessage,\n imageMessage,\n pushName: data?.pushName || 'Atleta',\n timestamp: new Date().toISOString(),\n raw: body\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + -460, + -940 + ], + "id": "normalize-inbound", + "name": "Normalizar Dados" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": false, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-coach", + "leftValue": "={{ $json.text }}", + "rightValue": "coach,treino,shape,biotipo,fisico,musculo", + "operator": { + "type": "string", + "operation": "contains", + "singleValue": true + } + } + ], + "combinator": "or" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -240, + -940 + ], + "id": "router-intent", + "name": "É Coach?" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "with u as (\n select id\n from public.profiles\n where phone_e164 = cast({{ $('Normalizar Dados').item.json.number }} as text)\n limit 1\n),\nent as (\n select ue.is_active, ue.entitlement_code, ue.valid_until\n from public.user_entitlements ue\n where ue.user_id = (select id from u)\n order by ue.valid_until desc nulls last\n limit 1\n),\nusage as (\n select count(*) as used_count\n from public.coach_analyses fa\n where fa.user_id = (select id from u)\n and fa.used_free_quota = true\n)\nselect\n (select id from u) as user_id,\n (select id from u) is not null as exists,\n coalesce((select used_count from usage), 0)::int as free_used,\n greatest(0, 3 - coalesce((select used_count from usage), 0))::int as free_remaining,\n coalesce((select is_active from ent), false) as plan_active,\n (coalesce((select is_active from ent), false) = true OR greatest(0, 3 - coalesce((select used_count from usage), 0)) > 0) as can_process", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 20, + -1160 + ], + "id": "validate-coach", + "name": "Validação Coach", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "operation": "executeQuery", + "query": "with u as (\n select id\n from public.profiles\n where phone_e164 = cast({{ $('Normalizar Dados').item.json.number }} as text)\n limit 1\n),\nent as (\n select\n ue.user_id,\n ue.is_active,\n ue.entitlement_code\n from public.user_entitlements ue\n where ue.user_id = (select id from u)\n order by ue.valid_until desc nulls last\n limit 1\n),\nusage as (\n select count(*) filter (where fa.used_free_quota = true) as free_used\n from public.food_analyses fa\n where fa.user_id = (select id from u)\n)\nselect\n (select id from u) is not null as exists,\n (select id from u) as user_id,\n coalesce((select free_used from usage), 0)::int as free_used,\n greatest(0, 5 - coalesce((select free_used from usage), 0))::int as free_remaining,\n coalesce((select is_active from ent), false) as plan_active,\n (\n (select id from u) is not null\n and (\n coalesce((select is_active from ent), false) = true\n or greatest(0, 5 - coalesce((select free_used from usage), 0)) > 0\n )\n ) as can_process;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 20, + -740 + ], + "id": "validate-food", + "name": "Validação Food", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-process", + "leftValue": "={{ $json.can_process }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 260, + -1160 + ], + "id": "if-coach-quota", + "name": "Coach OK?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "check-process-food", + "leftValue": "={{ $json.can_process }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 260, + -740 + ], + "id": "if-food-quota", + "name": "Food OK?" + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "🧐 *Coach AI*: Analisando seu biótipo e gerando seu treino... Aguarde!", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 500, + -1260 + ], + "id": "msg-ack-coach", + "name": "Ack Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "chat-api", + "operation": "get-media-base64", + "instanceName": "FoodSnap", + "messageId": "={{ $('Normalizar Dados').item.json.message_id }}" + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 720, + -1260 + ], + "id": "get-image-coach", + "name": "Baixar IMG Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "operation": "toBinary", + "sourceProperty": "data.base64", + "options": {} + }, + "type": "n8n-nodes-base.convertToFile", + "typeVersion": 1.1, + "position": [ + 940, + -1260 + ], + "id": "convert-binary-coach", + "name": "Binário Coach" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-1.5-flash", + "mode": "list", + "cachedResultName": "models/gemini-1.5-flash" + }, + "text": "=Você é um Treinador Físico de Elite e Nutricionista Esportivo.\nAnalise a imagem fornecida (foto de corpo inteiro/físico).\n\n1. Verifique se é uma foto de corpo humano válida para análise fitness. Se não, retorne \"valid_body\": false.\n2. Se for válida, estime:\n - Biótipo Predominante (Ectomorfo, Mesomorfo, Endomorfo)\n - Percentual de Gordura (BF% aproximado)\n - Nível de Massa Muscular (Baixo, Médio, Alto)\n3. Sugira:\n - Objetivo Principal (Cutting, Bulking, Manutenção)\n - Divisão de Treino Recomendada (ex: ABC, ABCDE, Upper/Lower)\n - Foco Dietético (ex: Carb Cycling, Alto Carbo, Keto)\n - Dica de Ouro (uma frase curta de impacto)\n\nResponda APENAS em JSON estrito (sem markdown):\n{\n \"valid_body\": true,\n \"biotype\": \"...\",\n \"estimated_body_fat\": 15,\n \"muscle_mass\": \"Médio\",\n \"goal\": \"Bulking\",\n \"workout\": \"...\",\n \"diet\": \"...\",\n \"tip\": \"...\"\n}", + "inputType": "binary", + "simplify": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 1160, + -1260 + ], + "id": "gemini-coach", + "name": "Gemini Coach", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "jsCode": "const raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data = {};\ntry { \n data = JSON.parse(clean); \n} catch(e) {\n data = { valid_body: false, error: 'Failed to parse AI' };\n}\n\nreturn [{ ...data }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1380, + -1260 + ], + "id": "parse-coach-json", + "name": "Parse Coach JSON" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "insert into public.coach_analyses\n(\n user_id,\n source,\n image_url,\n ai_raw_response,\n ai_structured,\n biotype,\n estimated_body_fat,\n goal_suggestion,\n muscle_mass_level,\n used_free_quota\n)\nvalues\n(\n cast('{{ $(\"Validação Coach\").item.json.user_id }}' as uuid),\n 'whatsapp',\n null,\n '{{ JSON.stringify($json) }}',\n '{{ JSON.stringify($json) }}',\n '{{ $json.biotype }}',\n cast({{ $json.estimated_body_fat || 0 }} as numeric),\n '{{ $json.goal }}',\n '{{ $json.muscle_mass }}',\n CASE\n WHEN {{ $(\"Validação Coach\").item.json.plan_active }} = true THEN false\n ELSE true\n END\n)\nreturning id as analysis_id;", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 1600, + -1260 + ], + "id": "save-coach-db", + "name": "Salvar Coach DB", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "=⚡ *Coach AI Report*\n\n🧬 *Biótipo*: {{$json.biotype}}\n⚖️ *BF Estimado*: ~{{$json.estimated_body_fat}}%\n💪 *Massa Muscular*: {{$json.muscle_mass}}\n\n🎯 *Foco*: {{$json.goal}}\n🏋️ *Treino*: {{$json.workout}}\n🥗 *Dieta*: {{$json.diet}}\n\n💡 *Dica*: {{$json.tip}}\n\n_Acesse o App para ver a ficha completa!_", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 1820, + -1260 + ], + "id": "reply-coach", + "name": "Responder Coach", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "📸 Recebi sua foto! Analisando o prato... ⏳", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 500, + -740 + ], + "id": "ack-food", + "name": "Ack Food", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "chat-api", + "operation": "get-media-base64", + "instanceName": "FoodSnap", + "messageId": "={{ $('Normalizar Dados').item.json.message_id }}" + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 720, + -740 + ], + "id": "get-image-food", + "name": "Baixar IMG Food", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "operation": "toBinary", + "sourceProperty": "data.base64", + "options": {} + }, + "type": "n8n-nodes-base.convertToFile", + "typeVersion": 1.1, + "position": [ + 940, + -740 + ], + "id": "convert-binary-food", + "name": "Binário Food" + }, + { + "parameters": { + "resource": "image", + "operation": "analyze", + "modelId": { + "__rl": true, + "value": "models/gemini-1.5-flash", + "mode": "list", + "cachedResultName": "models/gemini-1.5-flash" + }, + "text": "=Você é um assistente nutricional... (Prompt Original de Comida)... Retorne SOMENTE JSON.", + "inputType": "binary", + "simplify": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.googleGemini", + "typeVersion": 1, + "position": [ + 1160, + -740 + ], + "id": "gemini-food", + "name": "Gemini Food", + "credentials": { + "googlePalmApi": { + "id": "T2uIVBcjJ9h8BFCC", + "name": "Backup APIKEY" + } + } + }, + { + "parameters": { + "jsCode": "// Limpeza de JSON da Comida (Original)\nconst raw = $json?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\n// ... lógica existente de parse do FoodSnap ...\nreturn [{\n items: [],\n total: { calories: 500, protein: 30 },\n tip: { text: \"Exemplo de análise de comida\" }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1380, + -740 + ], + "id": "parse-food", + "name": "Parse Food", + "notes": "Lógica completa de parse de comida aqui (resumida para o arquivo)" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "insert into public.food_analyses ... (SQL Original)", + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 1600, + -740 + ], + "id": "save-food-db", + "name": "Salvar Food DB", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "=🥗 *FoodSnap*: Calorias: {{$json.total.calories}} ... (Formato Original)", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 1820, + -740 + ], + "id": "reply-food", + "name": "Responder Food", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + }, + { + "parameters": { + "resource": "messages-api", + "instanceName": "FoodSnap", + "remoteJid": "={{ $('Normalizar Dados').item.json.number }}", + "messageText": "⚠️ Por favor, envie uma *imagem* para análise.", + "options_message": {} + }, + "type": "n8n-nodes-evolution-api.evolutionApi", + "typeVersion": 1, + "position": [ + 260, + -500 + ], + "id": "msg-no-image", + "name": "Sem Imagem", + "credentials": { + "evolutionApi": { + "id": "nGWBERcZoQWgdxgk", + "name": "FoodSnap" + } + } + } + ], + "connections": { + "Webhook (Whatsapp)": { + "main": [ + [ + { + "node": "Normalizar Dados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Normalizar Dados": { + "main": [ + [ + { + "node": "É Coach?", + "type": "main", + "index": 0 + } + ] + ] + }, + "É Coach?": { + "main": [ + [ + { + "node": "Validação Coach", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Validação Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validação Coach": { + "main": [ + [ + { + "node": "Coach OK?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validação Food": { + "main": [ + [ + { + "node": "Food OK?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Coach OK?": { + "main": [ + [ + { + "node": "Ack Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Food OK?": { + "main": [ + [ + { + "node": "Ack Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ack Coach": { + "main": [ + [ + { + "node": "Baixar IMG Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ack Food": { + "main": [ + [ + { + "node": "Baixar IMG Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Baixar IMG Coach": { + "main": [ + [ + { + "node": "Binário Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Baixar IMG Food": { + "main": [ + [ + { + "node": "Binário Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Binário Coach": { + "main": [ + [ + { + "node": "Gemini Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Binário Food": { + "main": [ + [ + { + "node": "Gemini Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gemini Coach": { + "main": [ + [ + { + "node": "Parse Coach JSON", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gemini Food": { + "main": [ + [ + { + "node": "Parse Food", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Coach JSON": { + "main": [ + [ + { + "node": "Salvar Coach DB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Food": { + "main": [ + [ + { + "node": "Salvar Food DB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Salvar Coach DB": { + "main": [ + [ + { + "node": "Responder Coach", + "type": "main", + "index": 0 + } + ] + ] + }, + "Salvar Food DB": { + "main": [ + [ + { + "node": "Responder Food", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/src/n8n-stripe-webhook.json b/src/n8n-stripe-webhook.json new file mode 100644 index 0000000..3a64154 --- /dev/null +++ b/src/n8n-stripe-webhook.json @@ -0,0 +1,134 @@ +{ + "name": "Stripe Payment Handler", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "stripe-webhook", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 0, + 0 + ], + "id": "webhook-stripe", + "name": "Webhook Stripe" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.body.type }}", + "value2": "checkout.session.completed" + } + ] + } + }, + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 200, + 0 + ], + "id": "check-event-type", + "name": "Is Checkout Completed?" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "SELECT id FROM profiles WHERE email = '{{ $json.body.data.object.customer_details.email }}' LIMIT 1" + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 1, + "position": [ + 450, + -100 + ], + "id": "lookup-user", + "name": "Find User by Email", + "credentials": { + "postgres": { + "id": "2JDD2OJz4cAsWb0J", + "name": "foodsnap_supabase" + } + } + }, + { + "parameters": { + "operation": "executeQuery", + "query": "INSERT INTO payments (user_id, amount_cents, currency, status, stripe_payment_id, plan_type) VALUES ('{{ $json.id }}', {{ $node[\"Webhook Stripe\"].json.body.data.object.amount_total }}, '{{ $node[\"Webhook Stripe\"].json.body.data.object.currency }}', 'succeeded', '{{ $node[\"Webhook Stripe\"].json.body.data.object.payment_intent }}', 'pro') RETURNING id" + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 1, + "position": [ + 650, + -100 + ], + "id": "log-payment", + "name": "Log Payment" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "INSERT INTO user_entitlements (user_id, entitlement_code, is_active, is_trial, valid_until, plan_type) VALUES ('{{ $node[\"Find User by Email\"].json.id }}', 'pro', true, false, NOW() + INTERVAL '30 days', 'pro') ON CONFLICT (user_id) DO UPDATE SET is_active = true, valid_until = NOW() + INTERVAL '30 days', entitlement_code = 'pro', updated_at = NOW();" + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 1, + "position": [ + 850, + -100 + ], + "id": "activate-plan", + "name": "Activate Plan" + } + ], + "connections": { + "Webhook Stripe": { + "main": [ + [ + { + "node": "Is Checkout Completed?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Is Checkout Completed?": { + "main": [ + [ + { + "node": "Find User by Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "Find User by Email": { + "main": [ + [ + { + "node": "Log Payment", + "type": "main", + "index": 0 + } + ] + ] + }, + "Log Payment": { + "main": [ + [ + { + "node": "Activate Plan", + "type": "main", + "index": 0 + } + ] + ] + } + } +} \ No newline at end of file diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx new file mode 100644 index 0000000..116d906 --- /dev/null +++ b/src/pages/AdminPanel.tsx @@ -0,0 +1,798 @@ +import React, { useEffect, useState } from 'react'; +import { + LayoutDashboard, + Users, + CreditCard, + LogOut, + ArrowLeft, + TrendingUp, + Ticket, + Search, + ShieldAlert, + Download, + Plus, + DollarSign, + Calendar, + CheckCircle2, + XCircle, + MoreHorizontal, + UserPlus, + Activity, + AlertTriangle, + User, + Clock, + Info, + Settings, + Save, + Smartphone +} from 'lucide-react'; +import { supabase } from '@/lib/supabase'; +import { User as AppUser } from '@/types'; + +interface AdminPanelProps { + user: AppUser; + onExitAdmin: () => void; + onLogout: () => void; +} + +// Tipos baseados nas novas tabelas SQL +type TabType = 'overview' | 'users' | 'financial' | 'coupons' | 'settings'; + +const AdminPanel: React.FC = ({ user, onExitAdmin, onLogout }) => { + const [activeTab, setActiveTab] = useState('overview'); + const [loading, setLoading] = useState(true); + + // Data States + const [stats, setStats] = useState(null); + const [usersList, setUsersList] = useState([]); + const [coupons, setCoupons] = useState([]); + + // Settings State + const [config, setConfig] = useState({ + whatsapp_number: '' // Inicializa vazio para não confundir + }); + const [savingConfig, setSavingConfig] = useState(false); + + // UI States + const [searchTerm, setSearchTerm] = useState(''); + const [showCouponModal, setShowCouponModal] = useState(false); + const [newCoupon, setNewCoupon] = useState({ code: '', percent: 10, uses: 100 }); + + useEffect(() => { + fetchDashboardData(); + }, []); + + const fetchDashboardData = async () => { + setLoading(true); + try { + // 1. Stats + const { data: sData } = await supabase.rpc('get_admin_dashboard_stats'); + if (sData) setStats(sData); + + // 2. Users (Robust Fetch) + await fetchUsersSafe(); + + // 3. Coupons + const { data: cData } = await supabase.from('coupons').select('*').order('created_at', { ascending: false }); + if (cData) setCoupons(cData); + + // 4. Settings + const { data: configData } = await supabase + .from('app_settings') + .select('value') + .eq('key', 'whatsapp_number') + .maybeSingle(); + + if (configData) { + setConfig({ whatsapp_number: configData.value }); + } else { + // Fallback visual apenas se não tiver nada no banco + setConfig({ whatsapp_number: '5541999999999' }); + } + + } catch (error) { + console.error("Admin fetch error", error); + } finally { + setLoading(false); + } + }; + + const fetchUsersSafe = async () => { + // Tenta usar a função avançada (RPC) que tem dados do plano + const { data: rpcData, error: rpcError } = await supabase.rpc('get_admin_users_list', { limit_count: 50 }); + + if (!rpcError && rpcData) { + setUsersList(rpcData); + return; + } + + // Se falhar (ex: SQL não atualizado), busca o básico da tabela profiles para não deixar a tela vazia + console.warn("RPC falhou, usando fallback de perfis:", rpcError); + const { data: basicData } = await supabase + .from('profiles') + .select('*') + .order('created_at', { ascending: false }) + .limit(50); + + if (basicData) { + const mapped = basicData.map(p => ({ + id: p.id, + full_name: p.full_name, + email: p.email, + phone: p.phone_e164, + created_at: p.created_at, + plan_status: 'free', + plan_interval: 'free', + lifetime_value: 0, + plan_start_date: null, + plan_end_date: null + })); + setUsersList(mapped); + } + }; + + const handleCreateCoupon = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const { error } = await supabase.rpc('admin_create_coupon', { + p_code: newCoupon.code, + p_percent: newCoupon.percent, + p_uses: newCoupon.uses + }); + + if (error) throw error; + + const { data: cData } = await supabase.from('coupons').select('*').order('created_at', { ascending: false }); + if (cData) setCoupons(cData); + + setShowCouponModal(false); + setNewCoupon({ code: '', percent: 10, uses: 100 }); + alert("Cupom criado com sucesso!"); + } catch (err: any) { + alert("Erro ao criar cupom: " + err.message); + } + }; + + const handleToggleProfessional = async (userId: string, newValue: boolean) => { + try { + const { error } = await supabase + .from('profiles') + .update({ is_professional: newValue }) + .eq('id', userId); + + if (error) throw error; + + // Optimistic update + setUsersList(prev => prev.map(u => + u.id === userId ? { ...u, is_professional: newValue } : u + )); + + // If enhancing to Professional, check/create the professionals record + if (newValue) { + const { data: existing } = await supabase.from('professionals').select('id').eq('id', userId).maybeSingle(); + if (!existing) { + // Auto-init profile + // We don't have the user name here easily unless we look it up, + // but we can trust the 'professionals' RLS or just let them create it on first login. + // Ideally, we create a stub here. + const user = usersList.find(u => u.id === userId); + if (user) { + await supabase.from('professionals').insert({ + id: userId, + business_name: user.full_name || 'Novo Profissional', + primary_color: '#059669' // Default Green + }); + } + } + } + + // toast.success(`Status alterado para ${newValue ? 'Profissional' : 'Aluno'}`); + + } catch (error) { + console.error("Error toggling pro status:", error); + alert("Erro ao alterar status!"); + } + }; + + const handleSaveSettings = async (e: React.FormEvent) => { + e.preventDefault(); + setSavingConfig(true); + try { + const { error } = await supabase + .from('app_settings') + .upsert({ key: 'whatsapp_number', value: config.whatsapp_number }, { onConflict: 'key' }); + + if (error) throw error; + alert("Configurações salvas com sucesso!"); + } catch (err: any) { + console.error(err); + alert("Erro ao salvar: " + err.message); + } finally { + setSavingConfig(false); + } + }; + + // Safe filter logic (handles null names) + const filteredUsers = usersList.filter(u => + (u.full_name || '').toLowerCase().includes(searchTerm.toLowerCase()) || + (u.email || '').toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const formatCurrency = (cents: number) => { + return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(cents / 100); + }; + + const formatDate = (dateStr: string | null) => { + if (!dateStr) return '-'; + return new Date(dateStr).toLocaleDateString('pt-BR'); + }; + + return ( +
+ + {/* Sidebar Premium */} + + + {/* Main Content */} +
+ {loading ? ( +
+
+
+ ) : ( +
+ + {/* Top Bar */} +
+
+

+ {activeTab === 'overview' && 'Visão Geral'} + {activeTab === 'users' && 'Gestão de Usuários'} + {activeTab === 'financial' && 'Controle Financeiro'} + {activeTab === 'coupons' && 'Cupons de Desconto'} + {activeTab === 'settings' && 'Configurações do Sistema'} +

+

+ + Sistema Operacional • {new Date().toLocaleDateString()} +

+
+
+ {activeTab !== 'settings' && ( + + )} + {activeTab === 'coupons' && ( + + )} +
+
+ + {/* --- OVERVIEW TAB --- */} + {activeTab === 'overview' && stats && ( +
+ {/* Stats Grid */} +
+ } + color="bg-emerald-500" + trend="+8.2%" + /> + } + color="bg-blue-500" + trend="+12" + /> + } + color="bg-indigo-500" + trend="+24" + /> + } + color="bg-purple-500" + trend="Hoje" + /> +
+ + {/* Recent Activity Section */} +
+ {/* Chart placeholder area */} +
+

Crescimento de Receita (Simulado)

+
+ {[40, 65, 50, 80, 75, 90, 85, 100].map((h, i) => ( +
+
+
+ ))} +
+
+ JanFevMarAbrMaiJunJulAgo +
+
+ + {/* Quick Actions / Recent */} +
+

Ações Rápidas

+
+ + +
+
+
+
+ )} + + {/* --- USERS TAB --- */} + {activeTab === 'users' && ( +
+
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ +
+
+ + + + + + + + + + + + + + {filteredUsers.length === 0 ? ( + + + + ) : filteredUsers.map((u) => ( + + + + + + + + + + ))} + +
UsuárioStatusPlanoInícioTérminoPro?LTV
+ Nenhum usuário encontrado na busca. +
+
+
+ {u.full_name ? u.full_name.substring(0, 2).toUpperCase() : 'US'} +
+
+
{u.full_name || 'Usuário Sem Nome'}
+
{u.email}
+
+
+
+ + + + + {u.plan_start_date ? ( + {formatDate(u.plan_start_date)} + ) : ( +
+ + {formatDate(u.created_at)} +
+ )} +
+ {u.plan_end_date ? formatDate(u.plan_end_date) : -} + + + + {formatCurrency(u.lifetime_value || 0)} +
+
+
+
+ )} + + {/* --- COUPONS TAB --- */} + {activeTab === 'coupons' && ( +
+
+
+

Marketing & Ofertas

+

+ Crie códigos promocionais para influenciadores, campanhas de email ou recuperação de carrinho. +

+
+ +
+ +
+ {coupons.length === 0 ? ( +
+ Nenhum cupom ativo no momento. +
+ ) : coupons.map(c => ( +
+
+ +
+
+
+
+ {c.code} +
+
+ {c.is_active ? 'ATIVO' : 'INATIVO'} +
+
+
+ {c.discount_percent}% + OFF +
+
+
+ + {c.uses_count} / {c.max_uses} usos +
+
+ + Criado em {new Date(c.created_at).toLocaleDateString()} +
+
+
+
+ ))} +
+
+ )} + + {/* --- SETTINGS TAB --- */} + {activeTab === 'settings' && ( +
+
+
+

+ + Integração WhatsApp +

+

+ Configure o número que receberá as mensagens e imagens dos usuários para análise. +

+
+
+
+
+ +
+ setConfig({ ...config, whatsapp_number: e.target.value.replace(/\D/g, '') })} + /> + +
+

+ Insira apenas números, incluindo o código do país (Ex: 55 para Brasil). Este número será usado para gerar o QR Code no painel do usuário. +

+
+ +
+ +
+
+
+
+
+ )} + + {/* --- FINANCIAL TAB --- */} + {activeTab === 'financial' && ( +
+
+
+ +
+

Integração Stripe Connect

+

+ Para visualizar o histórico detalhado de transações em tempo real, configure os Webhooks do Stripe no backend. O sistema atual está pronto para receber os dados na tabela payments. +

+ +
+
+ )} +
+ )} +
+ + {/* Coupon Modal */} + {showCouponModal && ( +
+
+

Criar Novo Cupom

+ +
+ +

+ Atenção: Ao criar o cupom aqui, você apenas registra para métricas internas.
+ Você deve criar o mesmo código de cupom no Dashboard do Stripe para que o desconto funcione no checkout. +

+
+ +
+
+ + setNewCoupon({ ...newCoupon, code: e.target.value })} + /> +
+
+
+ + setNewCoupon({ ...newCoupon, percent: parseInt(e.target.value) })} + /> +
+
+ + setNewCoupon({ ...newCoupon, uses: parseInt(e.target.value) })} + /> +
+
+
+ + +
+
+
+
+ )} +
+ ); +}; + +// UI Components +const NavButton = ({ active, onClick, icon, label }: any) => ( + +); + +const KpiCard = ({ title, value, icon, color, trend }: any) => ( +
+
+ {icon} +
+
+

{title}

+

{value}

+ {trend && ( +
+ {trend} +
+ )} +
+
+); + +const StatusBadge = ({ status }: { status: string }) => { + let styles = 'bg-gray-100 text-gray-600'; + let icon = ; + let label = status; + + if (status === 'pro') { + styles = 'bg-green-100 text-green-700 border border-green-200'; + icon = ; + } + else if (status === 'trial') { + styles = 'bg-orange-100 text-orange-700 border border-orange-200'; + icon = ; + } + else if (status === 'free' || !status) { + return ( + + Gratuito + + ); + } + + return ( + + {icon} {label} + + ); +}; + +const IntervalBadge = ({ interval }: { interval: string }) => { + if (interval === 'free' || !interval) { + return Básico; + } + + let color = 'bg-gray-100 text-gray-600'; + let label = interval; + + if (interval === 'monthly') { color = 'bg-blue-50 text-blue-700 border border-blue-100'; label = 'Mensal'; } + if (interval === 'quarterly') { color = 'bg-indigo-50 text-indigo-700 border border-indigo-100'; label = 'Trimestral'; } + if (interval === 'annual') { color = 'bg-purple-50 text-purple-700 border border-purple-100'; label = 'Anual'; } + + return ( + + {label} + + ); +}; + +const ExternalLinkIcon = () => ( + +); + +export default AdminPanel; \ No newline at end of file diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..f82c54c --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -0,0 +1,234 @@ +import React, { useState, useEffect } from 'react'; +import { + LayoutDashboard, History, CreditCard, Settings, LogOut, Plus, Search, Calendar, ChevronRight, Zap, ExternalLink, MessageCircle, Loader2, Utensils, ShieldAlert, Smartphone, QrCode, CheckCircle2, Dumbbell, Timer, PlayCircle, ScanEye, BrainCircuit, Activity, ScanLine, Sparkles, TrendingUp +} from 'lucide-react'; +import CoachWizard from '@/components/coach/CoachWizard'; +import { User } from '@/types'; +import { supabase } from '@/lib/supabase'; +import { useLanguage } from '@/contexts/LanguageContext'; + +// Custom Hooks +import { useDashboardStats } from '@/hooks/useDashboardStats'; +import { useDashboardHistory } from '@/hooks/useDashboardHistory'; +import { useCoachPlan } from '@/hooks/useCoachPlan'; + +// Layout Components +import Sidebar from '@/components/layout/Sidebar'; +import MobileNav from '@/components/layout/MobileNav'; + +// Feature Components +import DashboardOverview from '@/components/dashboard/DashboardOverview'; +import DashboardHistory from '@/components/dashboard/DashboardHistory'; +import DashboardSubscription from '@/components/dashboard/DashboardSubscription'; +import DashboardCoach from '@/components/dashboard/DashboardCoach'; + +interface DashboardProps { + user: User; + onLogout: () => void; + onOpenAdmin?: () => void; // Optional prop for admin toggle + onOpenPro?: () => void; // Optional prop for professional toggle +} + +const Dashboard: React.FC = ({ user, onLogout, onOpenAdmin, onOpenPro }) => { + const { t, language } = useLanguage(); + const [activeTab, setActiveTab] = useState<'overview' | 'history' | 'subscription' | 'coach'>('overview'); + const [isCoachWizardOpen, setIsCoachWizardOpen] = useState(false); + // Custom Hooks + const { stats, loadingStats } = useDashboardStats(user.id); + const { history, loadingHistory } = useDashboardHistory(user.id); + const { coachPlan, setCoachPlan, coachHistory } = useCoachPlan(user.id); + + // WhatsApp Config + const [whatsappNumber, setWhatsappNumber] = useState("5541999999999"); // Default fallback + + const fetchSystemSettings = async () => { + try { + const { data } = await supabase + .from('app_settings') + .select('value') + .eq('key', 'whatsapp_number') + .maybeSingle(); + + if (data && data.value) { + setWhatsappNumber(data.value); + } + } catch (err) { + console.error("Failed to fetch settings", err); + } + }; + + useEffect(() => { + fetchSystemSettings(); + + // Realtime Subscription: Escuta alterações na tabela app_settings + const settingsChannel = supabase + .channel('public:app_settings') + .on( + 'postgres_changes', + { + event: '*', // Escuta INSERT e UPDATE + schema: 'public', + table: 'app_settings', + filter: 'key=eq.whatsapp_number', + }, + (payload) => { + if (payload.new && (payload.new as any).value) { + setWhatsappNumber((payload.new as any).value); + } + } + ) + .subscribe(); + + return () => { + supabase.removeChannel(settingsChannel); + }; + }, [user.id]); + + const whatsappUrl = `https://wa.me/${whatsappNumber}?text=Oi`; + const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(whatsappUrl)}`; + + const handleStripePortal = async () => { + try { + const { data: { session } } = await supabase.auth.getSession(); + if (!session) { + alert("Sessão expirada. Faça login novamente."); + return; + } + + const response = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-checkout`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${session.access_token}`, + }, + }); + + if (!response.ok) throw new Error("Erro ao gerar link do portal"); + + const { url } = await response.json(); + if (url) { + window.location.href = url; + } else { + alert("Erro: URL do portal não retornada."); + } + } catch (error) { + console.error("Erro no portal:", error); + alert("Não foi possível acessar o portal de pagamentos."); + } + }; + + // Helper para o nome do plano (Correção do bug de nome vazio) + const getPlanLabel = () => { + if (user.plan === 'pro') return 'PRO'; + if (user.plan === 'trial') return 'Trial'; + // Traduções manuais para o plano gratuito + if (language === 'pt') return 'Gratuito'; + if (language === 'es') return 'Gratis'; + return 'Free'; + }; + + const planName = getPlanLabel(); + const fallbackImage = "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=400&q=80"; + + return ( +
+ + {/* Sidebar Navigation */} + { + setCoachPlan(plan); + setActiveTab('coach'); + }} + /> + + {/* Mobile Bottom Navigation */} + + +
+ + {/* Mobile Header */} +
+ FoodSnap +
+ {onOpenAdmin && ( + + )} + +
+
+ + {/* Content Switcher */} + {activeTab === 'overview' && ( + + )} + + {activeTab === 'history' && ( + + )} + + {activeTab === 'subscription' && ( + + )} + + {activeTab === 'coach' && ( + + )} + +
+ + setIsCoachWizardOpen(false)} + onComplete={(data: any) => { + console.log("Wizard Completed:", data); + setCoachPlan(data); + setIsCoachWizardOpen(false); + }} + /> +
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/src/pages/FAQPage.tsx b/src/pages/FAQPage.tsx new file mode 100644 index 0000000..caf0c3e --- /dev/null +++ b/src/pages/FAQPage.tsx @@ -0,0 +1,121 @@ +import React, { useState } from 'react'; +import { Search, ChevronDown, ChevronUp, ArrowLeft, HelpCircle, FileText, CreditCard, Wrench } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface FAQPageProps { + onBack: () => void; +} + +const FAQPage: React.FC = ({ onBack }) => { + const { t } = useLanguage(); + const [search, setSearch] = useState(''); + const [openItem, setOpenItem] = useState(null); + + const categories = [ + { id: 'general', title: t.faqPage.categories.general.title, icon: , items: t.faqPage.categories.general.items }, + { id: 'account', title: t.faqPage.categories.account.title, icon: , items: t.faqPage.categories.account.items }, + { id: 'billing', title: t.faqPage.categories.billing.title, icon: , items: t.faqPage.categories.billing.items }, + { id: 'technical', title: t.faqPage.categories.technical.title, icon: , items: t.faqPage.categories.technical.items }, + ]; + + // Filtra as perguntas baseado na busca + const filteredCategories = categories.map(cat => ({ + ...cat, + items: cat.items.filter(item => + item.q.toLowerCase().includes(search.toLowerCase()) || + item.a.toLowerCase().includes(search.toLowerCase()) + ) + })).filter(cat => cat.items.length > 0); + + const toggleItem = (id: string) => { + setOpenItem(openItem === id ? null : id); + }; + + return ( +
+
+ + {/* Header Section */} +
+ +

{t.faqPage.title}

+

{t.faqPage.subtitle}

+ +
+
+ +
+ setSearch(e.target.value)} + /> +
+
+ + {/* Categories & Questions */} +
+ {filteredCategories.length === 0 ? ( +
+ Nenhuma pergunta encontrada para sua busca. +
+ ) : ( + filteredCategories.map((cat) => ( +
+
+
{cat.icon}
+

{cat.title}

+
+
+ {cat.items.map((item, idx) => { + const itemId = `${cat.id}-${idx}`; + const isOpen = openItem === itemId; + return ( +
+ +
+

+ {item.a} +

+
+
+ ); + })} +
+
+ )) + )} +
+ + {/* Contact CTA */} + + +
+
+ ); +}; + +export default FAQPage; \ No newline at end of file diff --git a/src/pages/ProfessionalDashboard.tsx b/src/pages/ProfessionalDashboard.tsx new file mode 100644 index 0000000..cc490bd --- /dev/null +++ b/src/pages/ProfessionalDashboard.tsx @@ -0,0 +1,176 @@ +import React, { useState, useEffect } from 'react'; +import { supabase } from '@/lib/supabase'; +import { + Users, + Dumbbell, + FileText, + Settings, + LogOut, + LayoutDashboard, + Video, + CreditCard, + Menu, + X, + Search, + Bell, + PlusCircle +} from 'lucide-react'; +import { User } from '@/types'; +import { StudentsList } from '@/components/professional/dashboard/StudentsList'; +import { OverviewMock } from '@/components/professional/dashboard/Overview'; +import { WorkoutsMock } from '@/components/professional/dashboard/Workouts'; +import { PlaceholderModule } from '@/components/professional/common/PlaceholderModule'; + +interface ProfessionalDashboardProps { + user: User; + onExit: () => void; + onLogout: () => void; +} + +type Tab = 'overview' | 'students' | 'workouts' | 'assessments' | 'library' | 'financial' | 'settings'; + +const ProfessionalDashboard: React.FC = ({ user, onExit, onLogout }) => { + const [activeTab, setActiveTab] = useState('overview'); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + return ( +
+ + {/* Mobile Overlay */} + {isSidebarOpen && ( +
setIsSidebarOpen(false)} + /> + )} + + {/* Sidebar (SaaS Style - Dark) */} + + + {/* Main Content Area */} +
+ + {/* Top Header */} +
+
+ +

{activeTab === 'overview' ? 'Visão Geral' : activeTab}

+
+ +
+ + +
+
+ +
+ {/* Dynamic Content */} + {activeTab === 'overview' && } + {activeTab === 'students' && } + {activeTab === 'workouts' && } + {activeTab === 'assessments' && } />} + {activeTab === 'library' && } />} + {activeTab === 'financial' && } />} +
+ +
+
+ ); +}; + +const NavItem = ({ icon, label, active, onClick }: any) => ( + +); + +export default ProfessionalDashboard; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..aea3dd1 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,12 @@ +export interface User { + id: string; + name: string; + email: string; + phone?: string; + plan: 'free' | 'pro' | 'trial'; + public_id: string; + avatar?: string; + plan_valid_until?: string; + is_admin?: boolean; + is_professional?: boolean; +} diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest new file mode 100644 index 0000000..8c68db7 --- /dev/null +++ b/supabase/.temp/cli-latest @@ -0,0 +1 @@ +v2.67.1 \ No newline at end of file diff --git a/supabase/.temp/gotrue-version b/supabase/.temp/gotrue-version new file mode 100644 index 0000000..e78dcd1 --- /dev/null +++ b/supabase/.temp/gotrue-version @@ -0,0 +1 @@ +v2.184.0 \ No newline at end of file diff --git a/supabase/.temp/pooler-url b/supabase/.temp/pooler-url new file mode 100644 index 0000000..5c58069 --- /dev/null +++ b/supabase/.temp/pooler-url @@ -0,0 +1 @@ +postgresql://postgres.mnhgpnqkwuqzpvfrwftp@aws-1-sa-east-1.pooler.supabase.com:5432/postgres \ No newline at end of file diff --git a/supabase/.temp/postgres-version b/supabase/.temp/postgres-version new file mode 100644 index 0000000..18f1b99 --- /dev/null +++ b/supabase/.temp/postgres-version @@ -0,0 +1 @@ +17.6.1.054 \ No newline at end of file diff --git a/supabase/.temp/project-ref b/supabase/.temp/project-ref new file mode 100644 index 0000000..9a5d561 --- /dev/null +++ b/supabase/.temp/project-ref @@ -0,0 +1 @@ +mnhgpnqkwuqzpvfrwftp \ No newline at end of file diff --git a/supabase/.temp/rest-version b/supabase/.temp/rest-version new file mode 100644 index 0000000..93c142b --- /dev/null +++ b/supabase/.temp/rest-version @@ -0,0 +1 @@ +v13.0.5 \ No newline at end of file diff --git a/supabase/.temp/storage-migration b/supabase/.temp/storage-migration new file mode 100644 index 0000000..5a41722 --- /dev/null +++ b/supabase/.temp/storage-migration @@ -0,0 +1 @@ +buckets-objects-grants-postgres \ No newline at end of file diff --git a/supabase/.temp/storage-version b/supabase/.temp/storage-version new file mode 100644 index 0000000..89c19be --- /dev/null +++ b/supabase/.temp/storage-version @@ -0,0 +1 @@ +v1.33.0 \ No newline at end of file diff --git a/supabase/functions/coach-generator/index.ts b/supabase/functions/coach-generator/index.ts new file mode 100644 index 0000000..33d955c --- /dev/null +++ b/supabase/functions/coach-generator/index.ts @@ -0,0 +1,115 @@ + +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { COACH_SYSTEM_PROMPT } from "./prompt.ts"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +serve(async (req) => { + // Handle CORS preflight requests + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const { photos, goal } = await req.json(); + + if (!photos || (!photos.front && !photos.side && !photos.back)) { + throw new Error("Pelo menos uma foto é necessária."); + } + + const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY"); + if (!GEMINI_API_KEY) { + throw new Error("Servidor não configurado (API Key ausente)."); + } + + // Prepare Image Parts + const parts = []; + + // System Prompt + parts.push({ text: COACH_SYSTEM_PROMPT }); + + // User Goal + parts.push({ text: `Objetivo do Usuário: ${goal}\nAnalise as fotos e gere o protocolo.` }); + + // Images + for (const [key, value] of Object.entries(photos)) { + if (typeof value === 'string' && value.includes('base64,')) { + // value example: "data:image/jpeg;base64,/9j/4AAQSkZJRg..." + const base64Data = value.split(',')[1]; + // Detect mime type + const mimeMatch = value.match(/^data:(.*);base64/); + const mimeType = mimeMatch ? mimeMatch[1] : 'image/jpeg'; + + parts.push({ + inline_data: { + mime_type: mimeType, + data: base64Data + } + }); + } + } + + // Call Gemini API via Fetch (More stable than SDK in Deno Edge) + // Using user-specified model: gemini-2.5-flash + const response = await fetch( + `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + contents: [{ parts: parts }], + generationConfig: { + temperature: 0.2, + response_mime_type: "application/json" + } + }) + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("Gemini API Error:", errorText); + throw new Error(`Erro na IA (${response.status}): ${errorText}`); + } + + const data = await response.json(); + const generatedText = data.candidates?.[0]?.content?.parts?.[0]?.text; + + if (!generatedText) { + console.error("Gemini Empty Response:", JSON.stringify(data)); + throw new Error("A IA não conseguiu analisar as imagens. Tente fotos com melhor iluminação."); + } + + let jsonResponse; + try { + // Clean markdown blocks if present (common in Gemini responses) + const cleaned = generatedText.replace(/```json/g, '').replace(/```/g, '').trim(); + jsonResponse = JSON.parse(cleaned); + } catch (e) { + console.error("JSON Parse Error:", generatedText); + throw new Error("Erro ao processar a resposta da IA. Tente novamente."); + } + + // Basic validation of the response structure + if (!jsonResponse.analysis || !jsonResponse.diet || !jsonResponse.workout) { + throw new Error("A resposta da IA veio incompleta. Por favor, tente novamente."); + } + + return new Response(JSON.stringify(jsonResponse), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200 + }); + + } catch (error) { + console.error("Function Error:", error); + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400 // Return 400 so client sees it as error, but with body + }); + } +}); diff --git a/supabase/functions/coach-generator/prompt.ts b/supabase/functions/coach-generator/prompt.ts new file mode 100644 index 0000000..630bc0f --- /dev/null +++ b/supabase/functions/coach-generator/prompt.ts @@ -0,0 +1,100 @@ +export const COACH_SYSTEM_PROMPT = ` +Você é o "Titan Coach", um treinador olímpico de elite e nutricionista esportivo PhD. +Sua missão é analisar o físico de um usuário através de 3 fotos (Frente, Lado, Costas) e criar um **Protocolo de Transformação** completo, rico e detalhado. + +RETORNE APENAS JSON. +NÃO use Markdown. +Formato de Resposta (Siga estritamente esta estrutura): + +{ + "analysis": { + "body_fat_percentage": 0, + "somatotype": "Ectomorfo" | "Mesomorfo" | "Endomorfo", + "muscle_mass_level": "Baixo" | "Médio" | "Alto", + "posture_analysis": "Texto detalhado sobre postura (ex: leve cifose, lordose, desvios laterais)", + "strengths": ["Ombros largos", "Cintura fina", "Bons quadríceps"], + "weaknesses": ["Panturrilhas pouco desenvolvidas", "Peitoral superior fraco"] + }, + "diet": { + "total_calories": 0, + "macros": { + "protein_g": 0, + "carbs_g": 0, + "fats_g": 0 + }, + "hydration_liters": 0, + "supplements": [ + { "name": "Creatina", "dosage": "5g pós-treino", "reason": "Aumento de força e recuperação" }, + { "name": "Whey Protein", "dosage": "30g se não bater a meta", "reason": "Praticidade para bater proteínas" }, + { "name": "Multivitamínico", "dosage": "1 caps almoço", "reason": "Micro-nutrientes essenciais" } + ], + "meal_plan_example": [ + { + "name": "Café da Manhã", + "time_range": "07:00 - 08:00", + "options": [ + "Opção 1: 3 Ovos mexidos + 1 Banana + 40g Aveia", + "Opção 2: 2 Fatias Pão Integral + 100g Frango Desfiado + Queijo Cotagge" + ], + "substitution_suggestion": "Para vegetarianos: Trocar frango por Tofu ou ovos por Shake proteico vegano." + }, + { + "name": "Almoço", + "time_range": "12:00 - 13:00", + "options": [ + "Opção 1: 150g Frango Grelhado + 120g Arroz Branco + Vegetais Verdes à vontade", + "Opção 2: 150g Patinho Moído + 150g Batata Inglesa + Salada Mista" + ], + "substitution_suggestion": "Se enjoar de arroz, use Macarrão Integral (mesmo peso) ou Batata Doce (peso x1.3)." + }, + { + "name": "Lanche da Tarde", + "time_range": "16:00 - 16:30", + "options": [ + "Opção 1: 1 Iogurte Grego Zero + 20g Nozes", + "Opção 2: 1 Fruta + 1 Dose de Whey" + ], + "substitution_suggestion": "Pode trocar as gorduras (nozes) por Pasta de Amendoim." + }, + { + "name": "Jantar", + "time_range": "20:00 - 21:00", + "options": [ + "Opção 1: 150g Peixe Branco (Tilápia) + Salada Completa + Azeite de Oliva", + "Opção 2: Omelete de 3 Ovos com Espinafre e Tomate" + ], + "substitution_suggestion": "Evite carboidratos pesados a noite se o objetivo for secar." + } + ] + }, + "workout": { + "split": "ABC" | "ABCD" | "ABCDE" | "Fullbody", + "focus": "Hipertrofia" | "Força" | "Perda de Gordura", + "frequency_days": 0, + "injury_adaptations": { + "knee_pain": "Substituir Agachamento por Leg Press 45 com pés altos", + "shoulder_pain": "Fazer Supino com Halteres pegada neutra ao invés de barra", + "back_pain": "Evitar Terra e Remada Curvada, preferir máquinas apoiadas" + }, + "routine": [ + { + "day": "Segunda", + "muscle_group": "Peito + Tríceps", + "exercises": [ + { "name": "Supino Inclinado com Halteres", "sets": 4, "reps": "8-12", "technique": "Focar na parte superior, descida controlada" }, + { "name": "Crucifixo Máquina", "sets": 3, "reps": "12-15", "technique": "Pico de contração de 1s" } + ] + } + ] + }, + "motivation_quote": "Uma frase curta de impacto." +} + +Regras IMPORTANTES: +1. Seja MUITO DETALHADO na dieta. Dê SEMPRE pelo menos 2 opções para CADA refeição ("options"). +2. Inclua o horário sugerido ("time_range") para cada refeição. +3. O campo "substitution_suggestion" deve dar uma alternativa clara de troca de alimentos (ex: trocar carbo X por Y). +4. Adapte o treino ao biotipo (ex: Ectomorfo menos volume, Endomorfo mais cardio). +5. Nos suplementos, especifique COMO tomar e PORQUE. +6. A resposta DEVE ser um JSON válido. +`; diff --git a/supabase/functions/stripe-checkout/index.ts b/supabase/functions/stripe-checkout/index.ts new file mode 100644 index 0000000..b5df17b --- /dev/null +++ b/supabase/functions/stripe-checkout/index.ts @@ -0,0 +1,125 @@ + +/// +import Stripe from "https://esm.sh/stripe@16.12.0?target=deno"; + +const STRIPE_SECRET_KEY = Deno.env.get("STRIPE_SECRET_KEY")!; +const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; +const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY")!; +const SITE_URL = Deno.env.get("SITE_URL")!; + +// ✅ seus PRICE IDs (recorrentes) +const PRICE_MENSAL = "price_1SeOVpPHwVDouhbBWZj9beS3"; +const PRICE_TRIMESTRAL = "price_1SeOeXPHwVDouhbBcaiUy3vu"; +const PRICE_ANUAL = "price_1SeOg4PHwVDouhbBTEiUPhMl"; + +const stripe = new Stripe(STRIPE_SECRET_KEY, { apiVersion: "2025-11-17.clover" }); + +const corsHeaders = { + "access-control-allow-origin": "*", + "access-control-allow-headers": "authorization, x-client-info, apikey, content-type", + "access-control-allow-methods": "POST, OPTIONS", +}; + +function json(data: unknown, status = 200) { + return new Response(JSON.stringify(data), { + status, + headers: { "content-type": "application/json", ...corsHeaders }, + }); +} + +async function getUserFromJwt(jwt: string) { + const res = await fetch(`${SUPABASE_URL}/auth/v1/user`, { + headers: { + apikey: SUPABASE_ANON_KEY, + authorization: `Bearer ${jwt}`, + }, + }); + if (!res.ok) return null; + return await res.json(); +} + +function assertBaseUrl(raw: string, name: string) { + let u: URL; + try { + u = new URL(raw); + } catch { + throw new Error(`${name} inválida. Use https://... (ex: https://foodsnap.com.br)`); + } + if (u.protocol !== "https:" && u.hostname !== "localhost") { + throw new Error(`${name} deve ser https:// (ou localhost em dev)`); + } + return u; +} + +function normalizePlan(planRaw: unknown) { + const p = String(planRaw ?? "").toLowerCase().trim(); + if (p === "mensal" || p === "monthly") return "mensal"; + if (p === "trimestral" || p === "quarterly") return "trimestral"; + if (p === "anual" || p === "annual" || p === "yearly") return "anual"; + return ""; +} + +function priceIdForPlan(plan: string) { + if (plan === "mensal") return PRICE_MENSAL; + if (plan === "trimestral") return PRICE_TRIMESTRAL; + if (plan === "anual") return PRICE_ANUAL; + return null; +} + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); + if (req.method !== "POST") return json({ ok: false, error: "Method not allowed" }, 405); + + try { + if (!STRIPE_SECRET_KEY) return json({ ok: false, error: "Missing STRIPE_SECRET_KEY" }, 500); + if (!SUPABASE_URL) return json({ ok: false, error: "Missing SUPABASE_URL" }, 500); + if (!SUPABASE_ANON_KEY) return json({ ok: false, error: "Missing SUPABASE_ANON_KEY" }, 500); + if (!SITE_URL) return json({ ok: false, error: "Missing SITE_URL" }, 500); + + const site = assertBaseUrl(SITE_URL, "SITE_URL"); + + const auth = req.headers.get("authorization") || ""; + const jwt = auth.startsWith("Bearer ") ? auth.slice(7) : ""; + if (!jwt) return json({ ok: false, error: "Missing Authorization Bearer token" }, 401); + + const user = await getUserFromJwt(jwt); + if (!user?.id) return json({ ok: false, error: "Invalid token" }, 401); + + const body = await req.json().catch(() => ({})); + const plan = normalizePlan(body?.plan); + if (!plan) return json({ ok: false, error: "Plano inválido. Use: mensal|trimestral|anual" }, 400); + + const priceId = priceIdForPlan(plan); + if (!priceId) return json({ ok: false, error: "Price não configurado para este plano" }, 500); + + // ✅ garante que é recorrente + const price = await stripe.prices.retrieve(priceId); + const isRecurring = (price as any)?.type === "recurring" || !!(price as any)?.recurring; + if (!isRecurring) { + return json( + { ok: false, error: `O price ${priceId} não é recorrente. Precisa ser Recurring para subscription.` }, + 400, + ); + } + + const successUrl = new URL("/dashboard?checkout=success", site).toString(); + const cancelUrl = new URL("/dashboard?checkout=cancel", site).toString(); + + const session = await stripe.checkout.sessions.create({ + mode: "subscription", + line_items: [{ price: priceId, quantity: 1 }], + success_url: successUrl, + cancel_url: cancelUrl, + + // ✅ amarra no usuário + customer_email: user.email ?? undefined, + metadata: { user_id: user.id, plan_code: plan }, + subscription_data: { metadata: { user_id: user.id, plan_code: plan } }, + }); + + return json({ ok: true, url: session.url, plan, priceId }); + } catch (err) { + console.error("stripe-checkout error:", err); + return json({ ok: false, error: String((err as any)?.message ?? err) }, 500); + } +}); diff --git a/supabase/functions/stripe-webhook/index.ts b/supabase/functions/stripe-webhook/index.ts new file mode 100644 index 0000000..10213b8 --- /dev/null +++ b/supabase/functions/stripe-webhook/index.ts @@ -0,0 +1,299 @@ + +/// + +import Stripe from "npm:stripe@16.12.0"; + +type EntitlementCode = "free" | "mensal" | "trimestral" | "anual" | "pro" | "trial"; + +const STRIPE_SECRET_KEY = Deno.env.get("STRIPE_SECRET_KEY") ?? ""; +const STRIPE_WEBHOOK_SECRET = Deno.env.get("STRIPE_WEBHOOK_SECRET") ?? ""; + +// ✅ nomes oficiais no Supabase Edge +const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? ""; +const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""; + +const REQUIRED_OK = !!( + STRIPE_SECRET_KEY && + STRIPE_WEBHOOK_SECRET && + SUPABASE_URL && + SUPABASE_SERVICE_ROLE_KEY +); + +const stripe = new Stripe(STRIPE_SECRET_KEY, { apiVersion: "2025-11-17.clover" }); + +function json(data: unknown, status = 200, extraHeaders: Record = {}) { + return new Response(JSON.stringify(data), { + status, + headers: { + "content-type": "application/json", + ...extraHeaders, + }, + }); +} + +function corsHeaders(origin: string | null) { + const allowOrigin = origin ?? "*"; + return { + "Access-Control-Allow-Origin": allowOrigin, + "Access-Control-Allow-Headers": + "authorization, x-client-info, apikey, content-type, stripe-signature", + "Access-Control-Allow-Methods": "POST, OPTIONS", + }; +} + +async function supabaseAdmin(path: string, init?: RequestInit) { + const url = `${SUPABASE_URL}${path}`; + return fetch(url, { + ...init, + headers: { + "content-type": "application/json", + apikey: SUPABASE_SERVICE_ROLE_KEY, + authorization: `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`, + ...(init?.headers || {}), + }, + }); +} + +async function upsertStripeCustomer( + user_id: string, + stripe_customer_id: string, + email?: string | null, +) { + const res = await supabaseAdmin(`/rest/v1/stripe_customers?on_conflict=user_id`, { + method: "POST", + headers: { Prefer: "resolution=merge-duplicates" }, + body: JSON.stringify({ + user_id, + stripe_customer_id, + email: email ?? null, + updated_at: new Date().toISOString(), + }), + }); + + if (!res.ok) { + const t = await res.text(); + throw new Error(`stripe_customers upsert failed: ${res.status} ${t}`); + } +} + +async function upsertEntitlement( + user_id: string, + entitlement_code: EntitlementCode, + is_active: boolean, + valid_until: string | null, +) { + const res = await supabaseAdmin(`/rest/v1/user_entitlements?on_conflict=user_id`, { + method: "POST", + headers: { Prefer: "resolution=merge-duplicates" }, + body: JSON.stringify({ + user_id, + entitlement_code, + is_active, + valid_until, + updated_at: new Date().toISOString(), + }), + }); + + if (!res.ok) { + const t = await res.text(); + throw new Error(`user_entitlements upsert failed: ${res.status} ${t}`); + } +} + +function safePlanCode(v: unknown): EntitlementCode { + const s = String(v ?? "").toLowerCase().trim(); + if (s === "mensal" || s === "trimestral" || s === "anual" || s === "pro" || s === "trial" || s === "free") { + return s; + } + return "free"; +} + +function secondsToISO(sec?: number | null) { + if (!sec || !Number.isFinite(sec)) return null; + return new Date(sec * 1000).toISOString(); +} + +/** + * ✅ Correção do valid_until: + * Em alguns payloads, `current_period_end` NÃO vem no root da subscription. + * Ele vem em `items.data[0].current_period_end`. + */ +function getPeriodEndISO(sub: Stripe.Subscription) { + const sec = + (sub as any).current_period_end ?? + (sub as any)?.items?.data?.[0]?.current_period_end ?? + null; + + return secondsToISO(sec); +} + +async function resolveUserId(customerId?: string | null, metadataUserId?: string | null) { + if (metadataUserId) return metadataUserId; + if (!customerId) return null; + + const q = new URLSearchParams(); + q.set("stripe_customer_id", `eq.${customerId}`); + q.set("select", "user_id"); + q.set("limit", "1"); + + const res = await supabaseAdmin(`/rest/v1/stripe_customers?${q.toString()}`, { method: "GET" }); + if (!res.ok) return null; + + const rows = await res.json(); + return rows?.[0]?.user_id ?? null; +} + +Deno.serve(async (req) => { + const origin = req.headers.get("origin"); + const cors = corsHeaders(origin); + + // Preflight (não é obrigatório pro Stripe, mas não atrapalha) + if (req.method === "OPTIONS") return new Response("ok", { headers: cors }); + + if (!REQUIRED_OK) { + console.error("Missing required env vars.", { + hasStripeKey: !!STRIPE_SECRET_KEY, + hasWhsec: !!STRIPE_WEBHOOK_SECRET, + hasSbUrl: !!SUPABASE_URL, + hasSr: !!SUPABASE_SERVICE_ROLE_KEY, + }); + return json({ ok: false, error: "Missing required env vars" }, 500, cors); + } + + // Stripe manda POST + if (req.method !== "POST") return json({ ok: false, error: "Method not allowed" }, 405, cors); + + const sig = req.headers.get("stripe-signature") ?? ""; + if (!sig) return json({ ok: false, error: "Missing stripe-signature" }, 400, cors); + + const raw = await req.text(); + + let event: Stripe.Event; + try { + event = await stripe.webhooks.constructEventAsync(raw, sig, STRIPE_WEBHOOK_SECRET); + } catch (err) { + console.error("Webhook signature verification failed:", err); + return json({ ok: false, error: "Invalid signature" }, 400, cors); + } + + try { + const t = event.type; + + // 1) Checkout finalizado + if (t === "checkout.session.completed") { + const s = event.data.object as Stripe.Checkout.Session; + + const customerId = (s.customer as string | null) ?? null; + const userId = await resolveUserId(customerId, (s.metadata?.user_id as string | undefined) ?? null); + if (!userId) return json({ ok: true, skipped: true, reason: "no_user_id" }, 200, cors); + + const plan = safePlanCode(s.metadata?.plan_code); + const email = (s.customer_details?.email ?? s.customer_email ?? null) as string | null; + + if (customerId) await upsertStripeCustomer(userId, customerId, email); + + // ✅ tenta já trazer o valid_until buscando a subscription (quando existir) + let validUntil: string | null = null; + if (s.subscription) { + const sub = await stripe.subscriptions.retrieve(String(s.subscription)); + validUntil = getPeriodEndISO(sub) ?? null; + } + + await upsertEntitlement(userId, plan, true, validUntil); + return json({ ok: true }, 200, cors); + } + + // 2) Subscription é a fonte da verdade + if (t === "customer.subscription.created" || t === "customer.subscription.updated") { + const sub = event.data.object as Stripe.Subscription; + + const customerId = (sub.customer as string | null) ?? null; + const userId = await resolveUserId(customerId, (sub.metadata?.user_id as string | undefined) ?? null); + if (!userId) return json({ ok: true, skipped: true, reason: "no_user_id" }, 200, cors); + + const plan = safePlanCode(sub.metadata?.plan_code); + const isActive = sub.status === "active" || sub.status === "trialing"; + const validUntil = getPeriodEndISO(sub) ?? null; + + if (customerId) await upsertStripeCustomer(userId, customerId, null); + await upsertEntitlement(userId, plan, isActive, validUntil); + + return json({ ok: true, plan, isActive, validUntil }, 200, cors); + } + + // 3) Pause/Delete: volta pro free + if (t === "customer.subscription.paused" || t === "customer.subscription.deleted") { + const sub = event.data.object as Stripe.Subscription; + + const customerId = (sub.customer as string | null) ?? null; + const userId = await resolveUserId(customerId, (sub.metadata?.user_id as string | undefined) ?? null); + if (!userId) return json({ ok: true, skipped: true, reason: "no_user_id" }, 200, cors); + + await upsertEntitlement(userId, "free", false, null); + return json({ ok: true }, 200, cors); + } + + // 4) Pagamento Confirmado (Salvar no Histórico) + if (t === "invoice.payment_succeeded") { + const invoice = event.data.object as Stripe.Invoice; + const customerId = (invoice.customer as string | null) ?? null; + + // Tenta pegar user_id do metadata da subscription ou do cliente + let userId = await resolveUserId(customerId, null); + + // Fallback: Tenta pegar da subscription associada à invoice + if (!userId && invoice.subscription) { + try { + const sub = await stripe.subscriptions.retrieve(String(invoice.subscription)); + userId = await resolveUserId(customerId, (sub.metadata?.user_id as string | undefined) ?? null); + } catch (e) { + console.error("Error retrieving subscription for userId fallback:", e); + } + } + + if (!userId) { + console.error("Invoice payment succeeded but could not resolve userId", { customerId, invoiceId: invoice.id }); + return json({ ok: true, skipped: true, reason: "no_user_id_for_invoice" }, 200, cors); + } + + // Mapeia dados + const amount = (invoice.amount_paid || 0) / 100; // Centavos para Real + const currency = invoice.currency; + const status = "completed"; + const method = invoice.collection_method === "charge_automatically" ? "credit_card" : "other"; // Simplificado + + // Tenta adivinhar o plano pelo valor ou linhas da fatura (básico) + // Idealmente viria do metadata, mas na invoice pode ser mais chato de pegar sem chamada extra + const lines = invoice.lines?.data || []; + const planDescription = lines.length > 0 ? lines[0].description : "Assinatura"; + let planType = "monthly"; + if (planDescription?.toLowerCase().includes("anual")) planType = "yearly"; + if (planDescription?.toLowerCase().includes("trimestral")) planType = "quarterly"; + + // Insere na tabela payments + const { error: payErr } = await supabaseAdmin(`/rest/v1/payments`, { + method: "POST", + body: JSON.stringify({ + user_id: userId, + amount: amount, + status: status, + plan_type: planType, + payment_method: method, + created_at: new Date().toISOString() + }), + }); + + if (payErr) { + // Loga erro mas não retorna 500 para não travar o webhook do Stripe (que tentaria reenviar) + console.error("Error inserting payment record:", payErr); + } + + return json({ ok: true, message: "Payment recorded" }, 200, cors); + } + + return json({ ok: true, ignored: true, type: t }, 200, cors); + } catch (err) { + console.error("stripe-webhook handler error:", err); + return json({ ok: false, error: String((err as any)?.message ?? err) }, 500, cors); + } +}); diff --git a/supabase/functions/validate-access/index.ts b/supabase/functions/validate-access/index.ts new file mode 100644 index 0000000..712e80b --- /dev/null +++ b/supabase/functions/validate-access/index.ts @@ -0,0 +1,117 @@ + +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +serve(async (req) => { + // Handle CORS preflight requests + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + // 1. Initialize Supabase Client with the incoming user's Auth context + // This allows us to use `auth.getUser()` securely based on the JWT sent by the frontend. + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL') ?? '', + Deno.env.get('SUPABASE_ANON_KEY') ?? '', + { + global: { + headers: { Authorization: req.headers.get('Authorization')! }, + }, + } + ); + + // 2. Get User from Token + const { + data: { user }, + error: authError, + } = await supabaseClient.auth.getUser(); + + if (authError || !user) { + return new Response( + JSON.stringify({ allowed: false, error: 'Unauthorized', reason: 'auth_failed' }), + { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + // 3. Check Entitlements (Active Plan?) + // We look for the most recent entitlement that is active. + const { data: entitlement, error: entError } = await supabaseClient + .from('user_entitlements') + .select('is_active, valid_until, entitlement_code') + .eq('user_id', user.id) + .order('valid_until', { ascending: false }) + .maybeSingle(); + + if (entError) { + console.error("Entitlement check error:", entError); + } + + // A plan is active if is_active=true AND (valid_until is NULL (lifetime) OR valid_until > now) + const isActive = entitlement?.is_active && (!entitlement.valid_until || new Date(entitlement.valid_until) > new Date()); + + if (isActive) { + return new Response( + JSON.stringify({ + allowed: true, + plan: entitlement.entitlement_code, + reason: 'plan_active', + quota_remaining: -1 // Infinite/Plan + }), + { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + // 4. Check Free Quota (Coach Analyses) + // Counts how many analyses already consumed the free quota. + const { count, error: countError } = await supabaseClient + .from('coach_analyses') + .select('*', { count: 'exact', head: true }) + .eq('user_id', user.id) + .eq('used_free_quota', true); + + if (countError) { + console.error("Quota check error:", countError); + throw new Error("Failed to check quota usage."); + } + + const FREE_LIMIT = 3; // Defined limit for Coach + const used = count || 0; + const remaining = Math.max(0, FREE_LIMIT - used); + + if (remaining > 0) { + return new Response( + JSON.stringify({ + allowed: true, + plan: 'free', + reason: 'free_quota', + quota_remaining: remaining + }), + { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } + + // 5. Quota Exceeded + return new Response( + JSON.stringify({ + allowed: false, + plan: 'free', + reason: 'quota_exceeded', + quota_remaining: 0 + }), + { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } // 200 OK because logic was successful, just access denied + ); + + } catch (error: any) { + console.error("Validate Access Error:", error); + return new Response( + JSON.stringify({ allowed: false, error: error.message }), + { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } + ); + } +}); diff --git a/supabase/functions/whatsapp-webhook/index.ts b/supabase/functions/whatsapp-webhook/index.ts new file mode 100644 index 0000000..599cc7e --- /dev/null +++ b/supabase/functions/whatsapp-webhook/index.ts @@ -0,0 +1,954 @@ +import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +import { createClient } from "https://esm.sh/@supabase/supabase-js@2.39.7"; +import { GoogleGenerativeAI } from "https://esm.sh/@google/generative-ai@0.21.0"; +import { SYSTEM_PROMPT, COACH_SYSTEM_PROMPT } from "./prompt.ts"; +import { buildCoachPdfHtml } from "./pdf-template.ts"; + +// ─── Config ──────────────────────────────────────────────────────── +const EVOLUTION_API_URL = Deno.env.get("EVOLUTION_API_URL") ?? ""; +const EVOLUTION_API_KEY = Deno.env.get("EVOLUTION_API_KEY") ?? ""; +const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY") ?? ""; +const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? ""; +const SUPABASE_SRK = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""; + +const INSTANCE_NAME = "foodsnap"; +const FREE_FOOD_LIMIT = 5; + +// ─── Types ───────────────────────────────────────────────────────── +interface EvolutionPayload { + event: string; + instance: string; + data: { + key: { remoteJid: string; fromMe: boolean; id: string }; + pushName?: string; + messageType?: string; + messageTimestamp?: number; + message?: { + imageMessage?: { mimetype: string }; + conversation?: string; + extendedTextMessage?: { text: string }; + }; + }; + sender?: string; +} + +// ─── Helpers ─────────────────────────────────────────────────────── + +/** Remove tudo que não é dígito */ +const onlyDigits = (s: string) => s.replace(/\D/g, ""); + +/** + * Gera candidatos de número brasileiro (com/sem DDI 55, com/sem 9º dígito). + * Usado para fazer match com profiles.phone_e164 e profiles.phone. + */ +function generatePhoneCandidates(raw: string): string[] { + const candidates: string[] = []; + const num = onlyDigits(raw); + if (!num) return candidates; + + candidates.push(num); + + const withoutDDI = num.startsWith("55") ? num.slice(2) : num; + if (withoutDDI !== num) candidates.push(withoutDDI); + if (!num.startsWith("55")) candidates.push("55" + num); + + const ddd = withoutDDI.slice(0, 2); + const rest = withoutDDI.slice(2); + + // Adiciona 9º dígito se tem 8 dígitos após DDD + if (rest.length === 8) { + const with9 = ddd + "9" + rest; + candidates.push(with9); + candidates.push("55" + with9); + } + + // Remove 9º dígito se tem 9 dígitos após DDD + if (rest.length === 9 && rest.startsWith("9")) { + const without9 = ddd + rest.slice(1); + candidates.push(without9); + candidates.push("55" + without9); + } + + return candidates; +} + +/** Envia mensagem de texto via Evolution API */ +async function sendWhatsAppMessage(remoteJid: string, text: string) { + if (!EVOLUTION_API_URL) { + console.error("[WH] EVOLUTION_API_URL not set! Cannot send message."); + return; + } + try { + const url = `${EVOLUTION_API_URL}/message/sendText/${INSTANCE_NAME}`; + console.log(`[WH] Sending message to ${remoteJid.slice(0, 8)}... via ${url}`); + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json", apikey: EVOLUTION_API_KEY }, + body: JSON.stringify({ + number: remoteJid, + text: text, + delay: 1200, + }), + }); + const resBody = await res.text(); + console.log(`[WH] Evolution API response: ${res.status} ${resBody.slice(0, 200)}`); + } catch (err) { + console.error("[WH] Error sending WhatsApp message:", err); + } +} + +/** Envia documento (PDF) via Evolution API */ +async function sendWhatsAppDocument(remoteJid: string, mediaUrl: string, fileName: string, caption?: string) { + if (!EVOLUTION_API_URL) { + console.error("[WH] EVOLUTION_API_URL not set! Cannot send document."); + return; + } + try { + const url = `${EVOLUTION_API_URL}/message/sendMedia/${INSTANCE_NAME}`; + console.log(`[WH] Sending document to ${remoteJid.slice(0, 8)}... file=${fileName}`); + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json", apikey: EVOLUTION_API_KEY }, + body: JSON.stringify({ + number: remoteJid, + mediatype: "document", + media: mediaUrl, + fileName: fileName, + caption: caption || "", + delay: 1200, + }), + }); + const resBody = await res.text(); + console.log(`[WH] Evolution sendMedia response: ${res.status} ${resBody.slice(0, 200)}`); + } catch (err) { + console.error("[WH] Error sending WhatsApp document:", err); + } +} + +/** Busca imagem em base64 da Evolution API */ +async function getWhatsAppMedia(messageId: string): Promise { + if (!EVOLUTION_API_URL) { + console.error("[WH] EVOLUTION_API_URL not set for media download!"); + return null; + } + try { + const url = `${EVOLUTION_API_URL}/chat/getBase64FromMediaMessage/${INSTANCE_NAME}`; + console.log(`[WH] Fetching media: ${url}, messageId=${messageId}`); + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json", apikey: EVOLUTION_API_KEY }, + body: JSON.stringify({ + message: { key: { id: messageId } }, + convertToMp4: false, + }), + }); + const resText = await res.text(); + console.log(`[WH] Media API response: ${res.status} ${resText.slice(0, 300)}`); + + if (!res.ok) return null; + + const data = JSON.parse(resText); + // A API pode retornar em diferentes formatos + const base64 = data.base64 || data.data?.base64 || null; + console.log(`[WH] Got base64: ${base64 ? `${base64.length} chars` : "NULL"}`); + return base64; + } catch (err) { + console.error("[WH] Error fetching media:", err); + return null; + } +} + +/** Converte base64 → Uint8Array (para upload storage) */ +function base64ToUint8Array(base64: string): Uint8Array { + const bin = atob(base64); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return bytes; +} + +// ─── Geração de HTML para PDF do Coach ──────────────────────────── +// (Movido para pdf-template.ts) + +// ─── Normalização e limpeza do JSON do Gemini (portado do n8n) ──── + +const toNum = (v: unknown): number => { + if (typeof v === "number") return v; + if (typeof v === "string") { + const n = Number(v.replace(",", ".").trim()); + return Number.isFinite(n) ? n : 0; + } + return 0; +}; + +const ensureArray = (v: unknown): any[] => (Array.isArray(v) ? v : []); + +const keyName = (s: string) => + (s || "") + .trim() + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); + +const clampConfidence = (c: string) => { + const k = keyName(c); + if (k.includes("alta")) return "alta"; + if (k.includes("baixa")) return "baixa"; + return "media"; +}; + +const CITRUS_VARIANTS = /^(tangerina|bergamota|mandarina|clementina|mexerica)/; + +const CANONICAL_MAP = [ + { match: /^laranja/, canonical: "Laranja" }, + { match: /^banana/, canonical: "Banana" }, + { match: /^maca|^maçã/, canonical: "Maçã" }, + { match: /^pera/, canonical: "Pera" }, + { match: /^uva/, canonical: "Uva" }, + { match: /^abacaxi/, canonical: "Abacaxi" }, + { match: /^melancia/, canonical: "Melancia" }, + { match: /^melao|^melão/, canonical: "Melão" }, +]; + +function canonicalizeName(name: string): string { + const k = keyName(name); + if (CITRUS_VARIANTS.test(k)) return "Laranja"; + for (const rule of CANONICAL_MAP) { + if (rule.match.test(k)) return rule.canonical; + } + return (name || "").trim(); +} + +const stripCitrusMention = (s: string) => { + const k = keyName(s); + if (/(tangerina|bergamota|mandarina|clementina|mexerica)/.test(k)) { + return s + .replace(/tangerina\/bergamota/gi, "laranja") + .replace(/tangerina|bergamota|mandarina|clementina|mexerica/gi, "laranja") + .trim(); + } + return s; +}; + +const parseUnitsPortion = (portion: string) => { + const p = (portion || "").toLowerCase().replace(",", "."); + const um = p.match(/(\d+)\s*unidades?/); + const g = p.match(/(\d+(\.\d+)?)\s*g/); + return { + units: um ? Number(um[1]) : null, + grams: g ? Math.round(Number(g[1])) : null, + }; +}; + +const buildUnitsPortion = (units: number | null, grams: number | null) => { + const u = units && units > 0 ? units : null; + const g = grams && grams > 0 ? grams : null; + if (u && g) return `${u} unidades (${g}g)`; + if (u) return `${u} unidades`; + if (g) return `${g}g`; + return ""; +}; + +/** + * Recebe o texto cru do Gemini e retorna o objeto normalizado + * (portado do nó "Limpar Resultado" do n8n) + */ +function parseAndCleanGeminiResponse(rawText: string): any { + // Limpa markdown + let cleaned = rawText.replace(/```json/gi, "").replace(/```/g, "").trim(); + + // Extrai JSON + const m = cleaned.match(/\{[\s\S]*\}/); + if (!m) throw new Error("JSON não encontrado na resposta do Gemini."); + let jsonStr = m[0]; + + // Corrige JSON mal formado + jsonStr = jsonStr.replace(/:\s*\+(\d+(\.\d+)?)/g, ": $1"); + jsonStr = jsonStr.replace(/,\s*([}\]])/g, "$1"); + + const parsed = JSON.parse(jsonStr); + + // Normaliza items + parsed.items = ensureArray(parsed.items).map((it: any) => { + const rawName = (it.name || "").trim(); + const k = keyName(rawName); + const flags = ensureArray(it.flags); + const name = canonicalizeName(rawName); + const nextFlags = CITRUS_VARIANTS.test(k) + ? Array.from(new Set([...flags, "tipo_duvidoso"])) + : flags; + + return { + ...it, + name, + portion: (it.portion || "").trim(), + calories: toNum(it.calories), + protein: toNum(it.protein), + carbs: toNum(it.carbs), + fat: toNum(it.fat), + fiber: toNum(it.fiber), + sugar: toNum(it.sugar), + sodium_mg: toNum(it.sodium_mg), + flags: nextFlags, + }; + }); + + // Deduplica por nome + const byName = new Map(); + for (const it of parsed.items) { + const k = keyName(it.name); + if (!k) continue; + + if (!byName.has(k)) { + byName.set(k, it); + continue; + } + + const cur = byName.get(k); + const a = parseUnitsPortion(cur.portion); + const b = parseUnitsPortion(it.portion); + let mergedPortion = cur.portion; + if (a.units !== null || b.units !== null || a.grams !== null || b.grams !== null) { + const units = (a.units || 0) + (b.units || 0); + const grams = (a.grams || 0) + (b.grams || 0); + const rebuilt = buildUnitsPortion(units || null, grams || null); + if (rebuilt) mergedPortion = rebuilt; + } + + byName.set(k, { + ...cur, + portion: mergedPortion, + calories: toNum(cur.calories) + toNum(it.calories), + protein: toNum(cur.protein) + toNum(it.protein), + carbs: toNum(cur.carbs) + toNum(it.carbs), + fat: toNum(cur.fat) + toNum(it.fat), + fiber: toNum(cur.fiber) + toNum(it.fiber), + sugar: toNum(cur.sugar) + toNum(it.sugar), + sodium_mg: toNum(cur.sodium_mg) + toNum(it.sodium_mg), + flags: Array.from( + new Set([...ensureArray(cur.flags), ...ensureArray(it.flags), "deduplicado"]) + ), + }); + } + parsed.items = Array.from(byName.values()); + + // Recalcula totais + const sum = (arr: any[], f: string) => arr.reduce((a: number, b: any) => a + toNum(b[f]), 0); + parsed.total = { + calories: Math.round(sum(parsed.items, "calories")), + protein: +sum(parsed.items, "protein").toFixed(1), + carbs: +sum(parsed.items, "carbs").toFixed(1), + fat: +sum(parsed.items, "fat").toFixed(1), + fiber: +sum(parsed.items, "fiber").toFixed(1), + sugar: +sum(parsed.items, "sugar").toFixed(1), + sodium_mg: Math.round(sum(parsed.items, "sodium_mg")), + }; + + // Outros campos + parsed.health_score = toNum(parsed.health_score); + parsed.confidence = clampConfidence(parsed.confidence || ""); + parsed.assumptions = ensureArray(parsed.assumptions).map(stripCitrusMention); + parsed.questions = ensureArray(parsed.questions); + parsed.insights = ensureArray(parsed.insights).map(stripCitrusMention); + parsed.swap_suggestions = ensureArray(parsed.swap_suggestions); + parsed.next_best_actions = ensureArray(parsed.next_best_actions); + + parsed.tip = + parsed.tip && typeof parsed.tip === "object" + ? parsed.tip + : { title: "", text: "", reason: "" }; + parsed.tip.title = String(parsed.tip.title || ""); + parsed.tip.text = stripCitrusMention(String(parsed.tip.text || "")); + parsed.tip.reason = stripCitrusMention(String(parsed.tip.reason || "")); + + return parsed; +} + +/** + * Formata a análise em mensagem rica para WhatsApp + * (portado do nó "Formatar Resposta WHATS" do n8n) + */ +function formatWhatsAppResponse(analysis: any): string { + if (!analysis || !Array.isArray(analysis.items) || !analysis.items.length) { + return "Não foi possível identificar um alimento válido na imagem."; + } + + const items = analysis.items; + const total = analysis.total || {}; + + const fmt = (n: unknown) => { + if (n === undefined || n === null || n === "") return "—"; + const num = Number(n); + if (!Number.isFinite(num)) return String(n); + return (Math.round(num * 10) / 10).toString(); + }; + + const v = (x: unknown) => (x === undefined || x === null || x === "" ? "—" : x); + const lines: string[] = []; + + lines.push("🥗 *RELATÓRIO PRATOFIT*"); + lines.push(""); + lines.push("*Itens identificados*"); + items.forEach((it: any, idx: number) => { + lines.push(`${idx + 1}) ${v(it.name)} — ${v(it.portion)} — ${fmt(it.calories)} kcal`); + }); + + lines.push(""); + lines.push("*Total do prato*"); + lines.push(`Energia: ${fmt(total.calories)} kcal`); + lines.push(""); + lines.push("*Macronutrientes (total)*"); + lines.push(`Proteínas: ${fmt(total.protein)} g`); + lines.push(`Carboidratos: ${fmt(total.carbs)} g`); + lines.push(`Gorduras: ${fmt(total.fat)} g`); + lines.push(""); + lines.push("*Outros nutrientes (total)*"); + lines.push(`Fibras: ${fmt(total.fiber)} g`); + lines.push(`Açúcares: ${fmt(total.sugar)} g`); + lines.push(`Sódio: ${fmt(total.sodium_mg)} mg`); + + if (analysis.health_score !== undefined) { + lines.push(`Score nutricional: ${fmt(analysis.health_score)} / 100`); + } + if (analysis.confidence) { + lines.push(`Confiabilidade: ${String(analysis.confidence).toLowerCase()}`); + } + + lines.push(""); + + if (analysis.tip && analysis.tip.text) { + lines.push("💡 *Dica prática*"); + lines.push(analysis.tip.text); + } + + return lines.join("\n"); +} + +// ─── Main Handler ────────────────────────────────────────────────── + +serve(async (req) => { + if (req.method !== "POST") { + return new Response("Method not allowed", { status: 405 }); + } + + try { + const payload: EvolutionPayload = await req.json(); + + // ── 0. Filtrar eventos irrelevantes ───────────────────────── + const event = payload.event || ""; + console.log(`[WH] Event received: ${event}`); + + const IGNORED_EVENTS = [ + "connection.update", + "qrcode.updated", + "presence.update", + "contacts.update", + "groups.update", + "chats.update", + ]; + if (IGNORED_EVENTS.includes(event)) { + console.log(`[WH] Event ignored: ${event}`); + return new Response("Event ignored", { status: 200 }); + } + + const data = payload.data; + if (!data || !data.key) { + console.log(`[WH] Invalid payload — missing data or data.key`); + return new Response("Invalid payload", { status: 200 }); + } + + const remoteJid = data.key.remoteJid; + + // Ignorar mensagens próprias ou de status + if (data.key.fromMe || remoteJid.includes("status@")) { + console.log(`[WH] Ignored: fromMe=${data.key.fromMe}, jid=${remoteJid}`); + return new Response("Ignored", { status: 200 }); + } + + // ── 1. Extrair dados ──────────────────────────────────────── + const senderNumber = onlyDigits(remoteJid.replace(/@.*$/, "")); + const senderFromPayload = payload.sender + ? onlyDigits(String(payload.sender).replace(/@.*$/, "")) + : ""; + const messageId = data.key.id; + const isImage = !!data.message?.imageMessage; + const textMessage = + data.message?.conversation || data.message?.extendedTextMessage?.text || ""; + + console.log(`[WH] sender=${senderNumber}, isImage=${isImage}, text="${textMessage.slice(0, 50)}"`); + + // Gerar candidatos de número BR + const allCandidates = [ + ...generatePhoneCandidates(senderNumber), + ...(senderFromPayload ? generatePhoneCandidates(senderFromPayload) : []), + ]; + const phoneCandidates = [...new Set(allCandidates)]; + console.log(`[WH] phoneCandidates: ${JSON.stringify(phoneCandidates)}`); + + // ── 2. Init Supabase ──────────────────────────────────────── + const supabase = createClient(SUPABASE_URL, SUPABASE_SRK); + + // ── 3. Buscar usuário com phone_candidates ────────────────── + let user: { id: string } | null = null; + + for (const candidate of phoneCandidates) { + const { data: directMatch, error: matchErr } = await supabase + .from("profiles") + .select("id") + .or(`phone_e164.eq.${candidate},phone.eq.${candidate}`) + .maybeSingle(); + + if (matchErr) { + console.error(`[WH] DB error matching candidate ${candidate}:`, matchErr.message); + } + if (directMatch) { + user = directMatch; + console.log(`[WH] User found: ${user.id} (matched candidate: ${candidate})`); + break; + } + } + + if (!user) { + console.log(`[WH] User NOT found for candidates: ${phoneCandidates.join(", ")}`); + await sendWhatsAppMessage( + remoteJid, + "🚫 *Acesso restrito*\nSeu número não está cadastrado no *FoodSnap*.\n\nCadastre-se em: https://foodsnap.com.br\n\nApós o cadastro, envie novamente a foto do prato 🍽️" + ); + return new Response("User not found", { status: 200 }); + } + + const userId = user.id; + + // ── 4. Estado da conversa (Coach state machine) ───────────── + let { data: conv } = await supabase + .from("whatsapp_conversations") + .select("*") + .eq("phone_number", senderNumber) + .maybeSingle(); + + if (!conv) { + const { data: newConv } = await supabase + .from("whatsapp_conversations") + .insert({ phone_number: senderNumber, state: "IDLE", temp_data: {} }) + .select() + .single(); + conv = newConv; + } + + const state = conv?.state || "IDLE"; + console.log(`[WH] Conversation state: ${state}, conv exists: ${!!conv}`); + + // ── 5. Coach Flow ─────────────────────────────────────────── + + // TRIGGER: texto contendo palavras-chave coach + if ( + state === "IDLE" && + textMessage && + /coach|treino|avalia[çc][aã]o/i.test(textMessage) + ) { + // [LOGIC START] Verificar última avaliação (Limite de 7 dias) + const { data: lastAnalysis } = await supabase + .from("coach_analyses") + .select("created_at") + .eq("user_id", userId) + .order("created_at", { ascending: false }) + .limit(1) + .maybeSingle(); + + if (lastAnalysis && lastAnalysis.created_at) { + const lastDate = new Date(lastAnalysis.created_at); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - lastDate.getTime()); + const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000; + + if (diffTime < sevenDaysInMs) { + const daysRemaining = Math.ceil((sevenDaysInMs - diffTime) / (1000 * 60 * 60 * 24)); + + await sendWhatsAppMessage( + remoteJid, + `⏳ *Calma, atleta!* O corpo precisa de tempo para evoluir.\n\nSua última avaliação foi há menos de uma semana.\nVocê poderá fazer uma nova avaliação em *${daysRemaining} dia(s)*.\n\nFoque no plano atual! 💪` + ); + return new Response("Coach Cooldown", { status: 200 }); + } + } + // [LOGIC END] + + await supabase + .from("whatsapp_conversations") + .update({ state: "COACH_FRONT", temp_data: {} }) + .eq("phone_number", senderNumber); + + await sendWhatsAppMessage( + remoteJid, + "🏋️‍♂️ *Coach AI Iniciado!*\n\nVamos montar seu protocolo de treino e dieta.\nPara começar, envie uma foto do seu corpo de *FRENTE* (mostrando do pescoço até os joelhos, se possível)." + ); + return new Response("Coach Started", { status: 200 }); + } + + // COACH_FRONT + if (state === "COACH_FRONT") { + if (!isImage) { + await sendWhatsAppMessage(remoteJid, "⚠️ Por favor, envie a foto de *FRENTE* para continuarmos."); + return new Response("Waiting Front", { status: 200 }); + } + + const base64 = await getWhatsAppMedia(messageId); + if (!base64) return new Response("Error downloading media", { status: 200 }); + + const fileName = `${userId}_front_${Date.now()}.jpg`; + await supabase.storage + .from("coach-uploads") + .upload(fileName, base64ToUint8Array(base64), { contentType: "image/jpeg" }); + + await supabase + .from("whatsapp_conversations") + .update({ state: "COACH_SIDE", temp_data: { ...conv!.temp_data, front_image: fileName } }) + .eq("phone_number", senderNumber); + + await sendWhatsAppMessage(remoteJid, "✅ Foto de frente recebida!\nAgora, envie uma foto de *LADO* (Perfil)."); + return new Response("Front Received", { status: 200 }); + } + + // COACH_SIDE + if (state === "COACH_SIDE") { + if (!isImage) { + await sendWhatsAppMessage(remoteJid, "⚠️ Por favor, envie a foto de *LADO*."); + return new Response("Waiting Side", { status: 200 }); + } + + const base64 = await getWhatsAppMedia(messageId); + if (!base64) return new Response("Error downloading media", { status: 200 }); + + const fileName = `${userId}_side_${Date.now()}.jpg`; + await supabase.storage + .from("coach-uploads") + .upload(fileName, base64ToUint8Array(base64), { contentType: "image/jpeg" }); + + await supabase + .from("whatsapp_conversations") + .update({ state: "COACH_BACK", temp_data: { ...conv!.temp_data, side_image: fileName } }) + .eq("phone_number", senderNumber); + + await sendWhatsAppMessage(remoteJid, "✅ Perfil recebido!\nPor último, envie uma foto de *COSTAS*."); + return new Response("Side Received", { status: 200 }); + } + + // COACH_BACK + if (state === "COACH_BACK") { + if (!isImage) { + await sendWhatsAppMessage(remoteJid, "⚠️ Por favor, envie a foto de *COSTAS*."); + return new Response("Waiting Back", { status: 200 }); + } + + const base64 = await getWhatsAppMedia(messageId); + if (!base64) return new Response("Error downloading media", { status: 200 }); + + const fileName = `${userId}_back_${Date.now()}.jpg`; + await supabase.storage + .from("coach-uploads") + .upload(fileName, base64ToUint8Array(base64), { contentType: "image/jpeg" }); + + await supabase + .from("whatsapp_conversations") + .update({ state: "COACH_GOAL", temp_data: { ...conv!.temp_data, back_image: fileName } }) + .eq("phone_number", senderNumber); + + await sendWhatsAppMessage( + remoteJid, + "📸 Todas as fotos recebidas!\n\nAgora digite o número do seu objetivo principal:\n1️⃣ Hipertrofia (Ganhar massa)\n2️⃣ Emagrecimento (Secar)\n3️⃣ Definição (Manter peso/trocar gordura por músculo)" + ); + return new Response("Back Received", { status: 200 }); + } + + // COACH_GOAL + if (state === "COACH_GOAL") { + let goal = "Hipertrofia"; + if (textMessage.includes("2") || /emagreci/i.test(textMessage)) goal = "Emagrecimento"; + else if (textMessage.includes("3") || /defini/i.test(textMessage)) goal = "Definição"; + else if (!textMessage.includes("1") && !/hiper/i.test(textMessage)) { + await sendWhatsAppMessage(remoteJid, "⚠️ Não entendi. Responda com 1, 2 ou 3."); + return new Response("Waiting Goal", { status: 200 }); + } + + await sendWhatsAppMessage( + remoteJid, + "🤖 Estou analisando seu físico e montando o plano com a IA...\nIsso pode levar cerca de 10-15 segundos." + ); + + try { + const { front_image, side_image, back_image } = conv!.temp_data; + const images = [front_image, side_image, back_image]; + const parts: any[] = [{ text: COACH_SYSTEM_PROMPT }, { text: `Objetivo: ${goal}` }]; + + for (const imgPath of images) { + if (imgPath) { + const { data: blob } = await supabase.storage.from("coach-uploads").download(imgPath); + if (blob) { + const buffer = await blob.arrayBuffer(); + const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer))); + parts.push({ inlineData: { mimeType: "image/jpeg", data: base64 } }); + } + } + } + + const genAI = new GoogleGenerativeAI(GEMINI_API_KEY); + const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" }); + + const result = await model.generateContent({ + contents: [{ role: "user", parts }], + generationConfig: { temperature: 0.2, responseMimeType: "application/json" }, + }); + + const responseText = result.response.text(); + const plan = JSON.parse(responseText); + + let msg = `🔥 *SEU PROTOCOLO TITAN* 🔥\n\n`; + msg += `🧬 *Análise*: ${plan.analysis?.somatotype}, ${plan.analysis?.muscle_mass_level} massa muscular.\n`; + msg += `🎯 *Foco*: ${plan.workout?.focus}\n\n`; + msg += `🏋️ *Treino*: Divisão ${plan.workout?.split} (${plan.workout?.frequency_days}x/semana)\n`; + msg += `🥗 *Dieta*: ${Math.round(plan.diet?.total_calories)} kcal\n`; + msg += ` • P: ${plan.diet?.macros?.protein_g}g | C: ${plan.diet?.macros?.carbs_g}g | G: ${plan.diet?.macros?.fats_g}g\n\n`; + msg += `💊 *Suplementos*: ${plan.diet?.supplements?.map((s: any) => s.name).join(", ")}\n\n`; + msg += `💡 *Dica*: ${plan.motivation_quote}\n\n`; + msg += `📲 *Acesse o app para ver o plano completo e detalhado!*`; + + await sendWhatsAppMessage(remoteJid, msg); + + // ── Gerar PDF e enviar via WhatsApp ───────────────── + try { + const pdfFileName = `FoodSnap_Titan_${new Date().toISOString().split("T")[0]}`; + const pdfHtml = buildCoachPdfHtml(plan); + + console.log("[WH] Generating PDF via n8n/Gotenberg..."); + const pdfResponse = await fetch("https://n8n.seureview.com.br/webhook/pdf-coach", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ html: pdfHtml, file_name: pdfFileName }), + }); + + if (pdfResponse.ok) { + const pdfBlob = await pdfResponse.arrayBuffer(); + const pdfBytes = new Uint8Array(pdfBlob); + const storagePath = `${userId}/${pdfFileName}.pdf`; + + // Upload para Supabase Storage + const { error: uploadErr } = await supabase.storage + .from("coach-pdfs") + .upload(storagePath, pdfBytes, { + contentType: "application/pdf", + upsert: true, + }); + + if (uploadErr) { + console.error("[WH] PDF upload error:", uploadErr); + } else { + // URL Assinada (funciona mesmo com bucket privado) + const { data: urlData, error: signErr } = await supabase.storage + .from("coach-pdfs") + .createSignedUrl(storagePath, 60 * 60); // 1 hora de validade + + if (signErr || !urlData?.signedUrl) { + console.error("[WH] Signed URL error:", signErr); + } else { + await sendWhatsAppDocument( + remoteJid, + urlData.signedUrl, + `${pdfFileName}.pdf`, + "📄 Seu Protocolo Titan completo em PDF!" + ); + } + } + } else { + console.error("[WH] n8n PDF error:", pdfResponse.status, await pdfResponse.text()); + } + } catch (pdfErr) { + console.error("[WH] PDF generation/send error (non-blocking):", pdfErr); + // PDF is non-blocking — user already got the text summary + } + + // ── Salvar análise coach (enriquecido p/ dashboard) ─ + const { error: saveCoachErr } = await supabase.from("coach_analyses").insert({ + user_id: userId, + source: "whatsapp", + ai_raw_response: responseText, + ai_structured: plan, + goal_suggestion: goal, + biotype: plan.analysis?.somatotype || null, + estimated_body_fat: parseFloat(String(plan.analysis?.body_fat_percentage || 0)) || 0, + muscle_mass_level: plan.analysis?.muscle_mass_level || null, + }); + + if (saveCoachErr) { + console.error("[WH] Error saving coach analysis to DB:", saveCoachErr); + } else { + console.log("[WH] Coach analysis saved successfully for user:", userId); + } + + // Reset state + await supabase + .from("whatsapp_conversations") + .update({ state: "IDLE", temp_data: {} }) + .eq("phone_number", senderNumber); + } catch (err) { + console.error("Coach Gen Error:", err); + await sendWhatsAppMessage( + remoteJid, + "⚠️ Ocorreu um erro ao gerar seu plano. Tente novamente digitando 'Coach'." + ); + await supabase + .from("whatsapp_conversations") + .update({ state: "IDLE", temp_data: {} }) + .eq("phone_number", senderNumber); + } + + return new Response("Coach Workflow Completed", { status: 200 }); + } + + // ── 6. Food Scan Flow (IDLE) ──────────────────────────────── + if (state === "IDLE") { + console.log(`[WH] Entering Food Scan flow. isImage=${isImage}`); + // 6a. Verificar plano e quota + const { data: entitlement } = await supabase + .from("user_entitlements") + .select("is_active, valid_until, entitlement_code") + .eq("user_id", userId) + .eq("is_active", true) + .order("valid_until", { ascending: false, nullsFirst: false }) + .maybeSingle(); + + const isPaid = + entitlement?.is_active && + (!entitlement.valid_until || new Date(entitlement.valid_until) > new Date()); + + if (!isPaid) { + const { count: freeUsed } = await supabase + .from("food_analyses") + .select("*", { count: "exact", head: true }) + .eq("user_id", userId) + .eq("used_free_quota", true); + + if ((freeUsed || 0) >= FREE_FOOD_LIMIT) { + await sendWhatsAppMessage( + remoteJid, + `🚫 Limite gratuito atingido\nVocê já usou suas ${FREE_FOOD_LIMIT} análises grátis.\n\nPara continuar, assine um plano em:\nhttps://foodsnap.com.br\n\nDepois é só enviar outra foto 📸` + ); + return new Response("Quota exceeded", { status: 200 }); + } + } + + // 6b. Sem imagem → mensagem de boas-vindas + if (!isImage) { + await sendWhatsAppMessage( + remoteJid, + "👋 Olá! Envie uma *foto do seu prato* (bem nítida e de cima 📸) que eu te retorno *calorias e macronutrientes* em segundos.\n\nOu digite *Coach* para iniciar uma consultoria completa." + ); + return new Response("Text handled", { status: 200 }); + } + + // 6c. Processar imagem + await sendWhatsAppMessage(remoteJid, "📸 Recebi sua foto! Estou analisando o prato agora… ⏳"); + + const base64Image = await getWhatsAppMedia(messageId); + if (!base64Image) { + await sendWhatsAppMessage(remoteJid, "⚠️ Não consegui baixar a imagem. Tente enviar novamente."); + return new Response("Error downloading image", { status: 200 }); + } + + // 6d. Chamar Gemini + const genAI = new GoogleGenerativeAI(GEMINI_API_KEY); + const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" }); + + const geminiResult = await model.generateContent({ + contents: [ + { + role: "user", + parts: [ + { text: SYSTEM_PROMPT }, + { inlineData: { mimeType: "image/jpeg", data: base64Image } }, + ], + }, + ], + generationConfig: { temperature: 0.1, responseMimeType: "application/json" }, + }); + + const rawResponseText = geminiResult.response.text(); + + // 6e. Limpar e normalizar resultado + let analysis: any; + try { + analysis = parseAndCleanGeminiResponse(rawResponseText); + } catch (parseErr) { + console.error("Parse error:", parseErr); + await sendWhatsAppMessage( + remoteJid, + "⚠️ Houve um erro ao interpretar a análise. Tente enviar a foto novamente com boa iluminação." + ); + return new Response("Parse error", { status: 200 }); + } + + // 6f. Formatar e enviar resposta + const replyText = formatWhatsAppResponse(analysis); + await sendWhatsAppMessage(remoteJid, replyText); + + // 6g. Mapear confidence para enum do banco + const confidenceMap: Record = { + alta: "high", + media: "medium", + média: "medium", + baixa: "low", + }; + + // 6h. Salvar no banco + const { data: inserted } = await supabase + .from("food_analyses") + .insert({ + user_id: userId, + source: "whatsapp", + image_url: null, // será atualizado após upload + ai_raw_response: rawResponseText, + ai_structured: analysis, + total_calories: analysis.total?.calories || 0, + total_protein: analysis.total?.protein || 0, + total_carbs: analysis.total?.carbs || 0, + total_fat: analysis.total?.fat || 0, + total_fiber: analysis.total?.fiber || 0, + total_sodium_mg: analysis.total?.sodium_mg || 0, + nutrition_score: analysis.health_score || 0, + confidence_level: confidenceMap[analysis.confidence] || "medium", + used_free_quota: !isPaid, + }) + .select("id") + .single(); + + // 6i. Upload imagem para Supabase Storage (bucket consultas) + if (inserted?.id) { + try { + const imgPath = `${userId}/${inserted.id}.jpg`; + const imgBytes = base64ToUint8Array(base64Image); + await supabase.storage + .from("consultas") + .upload(imgPath, imgBytes, { contentType: "image/jpeg", upsert: true }); + + // Atualizar image_url no registro + const { data: { publicUrl } } = supabase.storage + .from("consultas") + .getPublicUrl(imgPath); + + await supabase + .from("food_analyses") + .update({ image_url: publicUrl }) + .eq("id", inserted.id); + } catch (uploadErr) { + console.error("Image upload error (non-fatal):", uploadErr); + // Não falha o fluxo principal por erro de upload + } + } + + return new Response("Food Analyzed", { status: 200 }); + } + + return new Response("Nothing happened", { status: 200 }); + } catch (err: any) { + console.error("Critical Error:", err); + return new Response(`Server error: ${err.message}`, { status: 500 }); + } +}); diff --git a/supabase/functions/whatsapp-webhook/pdf-template.ts b/supabase/functions/whatsapp-webhook/pdf-template.ts new file mode 100644 index 0000000..3358ec5 --- /dev/null +++ b/supabase/functions/whatsapp-webhook/pdf-template.ts @@ -0,0 +1,249 @@ + +// ─── Geração de HTML para PDF do Coach (Premium 3 Páginas Compacto) ──────── + +function truncateText(text: string, max = 500): string { + const t = (text || "").trim(); + if (!t) return "-"; + return t.length > max ? t.slice(0, max - 1) + "…" : t; +} + +function safeStr(v: any, fallback = "-"): string { + if (v === null || v === undefined) return fallback; + if (typeof v === "string") return v.trim() || fallback; + if (typeof v === "number") return Number.isFinite(v) ? String(v) : fallback; + return fallback; +} + +export function buildCoachPdfHtml(plan: any): string { + const diet = plan.diet || {}; + const workout = plan.workout || {}; + const analysis = plan.analysis || {}; + const quote = plan.motivation_quote || "Disciplina é a ponte entre metas e conquistas."; + + // --- Data Prep --- + const protein = diet.macros?.protein_g ?? "–"; + const carbs = diet.macros?.carbs_g ?? "–"; + const fats = diet.macros?.fats_g ?? "–"; + const water = diet.hydration_liters ?? "–"; + const calories = Math.round(diet.total_calories || 0); + + const somatotype = safeStr(analysis.somatotype); + const goal = safeStr(workout.focus); + const split = safeStr(workout.split); + + // Lists + const positives = (Array.isArray(analysis.strengths) ? analysis.strengths : []) + .map((x: any) => typeof x === "string" ? x : x?.text).filter(Boolean); // Removed slice limit + + // Map 'weaknesses' to 'improvements' (Prompt returns weaknesses) + const improvements = (Array.isArray(analysis.weaknesses) ? analysis.weaknesses : []) + .map((x: any) => typeof x === "string" ? x : x?.text).filter(Boolean); + + const meals: any[] = Array.isArray(diet.meal_plan_example) ? diet.meal_plan_example : []; + const supplements: any[] = Array.isArray(diet.supplements) ? diet.supplements : []; + const routine: any[] = Array.isArray(workout.routine) ? workout.routine : []; + + // --- HTML Generators --- + + const positivesHtml = positives.length + ? `
    ${positives.map((t: string) => `
  • ${truncateText(t, 500)}
  • `).join("")}
` + : `

${safeStr(analysis.summary, "Sem detalhes.")}

`; + + const improvementsHtml = improvements.length + ? `
    ${improvements.map((t: string) => `
  • ${truncateText(t, 500)}
  • `).join("")}
` + : `

${safeStr(analysis.improvement_summary, "Sem detalhes.")}

`; + + const mealsHtml = meals.map((meal: any, i: number) => { + const options = Array.isArray(meal.options) ? meal.options : []; + const opt1 = options[0] || meal.main_option || ""; + const opt2 = options[1] || ""; + const sub = meal.substitution_suggestion || meal.substitution || ""; + + let html = `
`; + html += `
`; + html += `
${meal.name || `Refeição ${i + 1}`}
`; + if (meal.time_range) html += `
${meal.time_range}
`; + html += `
#${i + 1}
`; + html += `
`; + if (opt1) html += `
Opção 1: ${truncateText(String(opt1), 500)}
`; + if (opt2) html += `
Opção 2: ${truncateText(String(opt2), 500)}
`; + if (sub) html += `
Substituição: ${truncateText(String(sub), 300)}
`; + html += `
`; + return html; + }).join(""); + + const supplementsHtml = supplements.map((sup: any) => { + const name = typeof sup === "string" ? sup : sup.name || "Suplemento"; + const dosage = typeof sup === "string" ? "" : sup.dosage || ""; + const reason = typeof sup === "string" ? "" : sup.reason || ""; // Added reason if available + let html = `
`; + html += `
💊
${truncateText(String(name), 100)}
`; + if (dosage) html += `
${truncateText(String(dosage), 100)}
`; + if (reason) html += `
${truncateText(String(reason), 150)}
`; + html += `
`; + return html; + }).join(""); + + const daysHtml = routine.map((day: any, idx: number) => { + const exs: any[] = Array.isArray(day.exercises) ? day.exercises : []; + const dayName = day.day || day.name || `Dia ${idx + 1}`; + const muscle = day.muscle_group || day.focus || ""; + + const exLines = exs.map((ex: any) => { + if (typeof ex === "string") return `
  • ${ex}
  • `; + const name = ex.name || ex.exercise || ""; + const sets = ex.sets ?? ""; + const reps = ex.reps ?? ""; + const technique = ex.technique || ex.notes || ""; + const sr = [sets ? `${sets}x` : "", reps].filter(Boolean).join(" "); + const left = [name, sr].filter(Boolean).join(" — "); + const full = [left, technique].filter(Boolean).join(" • "); + return `
  • ${truncateText(full, 500) || "-"}
  • `; + }).join(""); + + return `
    +
    +
    +
    ${dayName}
    +
    ${muscle}
    +
    +
    ${workout.split || "Diff"}
    +
    +
      ${exLines}
    +
    `; + }).join(""); + + // --- Template Compacto --- + + return ` + + + + + + + + + + + +
    +
    +
    +
    +
    +
    Protocolo Titan • FoodSnap Coach
    +

    01. Diagnóstico

    +
    +
    +
    + +
    +
    Biótipo
    ${somatotype}
    +
    Objetivo
    ${goal}
    +
    Calorias
    ${calories}
    +
    Split
    ${split}
    +
    + +
    +
    +
    Pontos Fortes
    + ${positivesHtml} +
    +
    +
    Melhorias
    + ${improvementsHtml} +
    +
    + +
    +

    "O sucesso é a soma de pequenos esforços repetidos dia após dia."

    +
    +
    +
    + + +
    +
    +
    +

    02. Dieta

    +
    🥗
    +
    + +
    +
    +
    PROT: ${protein}
    +
    CARB: ${carbs}
    +
    GORD: ${fats}
    +
    💧 ${water}L
    +
    +
    + +
    +
    +
    Refeições
    +
    ${mealsHtml}
    +
    +
    +
    Suplementos
    +
    +
    ${supplementsHtml}
    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    03. Treino

    +
    🏋️
    +
    + +
    + ${daysHtml} +
    + +
    + "${truncateText(quote, 100)}" +
    +
    +
    +
    + +`; +} diff --git a/supabase/functions/whatsapp-webhook/prompt.ts b/supabase/functions/whatsapp-webhook/prompt.ts new file mode 100644 index 0000000..d744277 --- /dev/null +++ b/supabase/functions/whatsapp-webhook/prompt.ts @@ -0,0 +1,257 @@ +export const SYSTEM_PROMPT = ` +Você é um assistente nutricional especializado em análise visual de alimentos. + +Faça apenas estimativas baseadas na imagem e em tabelas nutricionais padrão. +Não dê aconselhamento médico, nem diagnóstico. +Use linguagem objetiva, estilo app fitness. +Seja claro sobre incertezas, sem usar palavras como “aproximadamente”. + +Retorne SOMENTE JSON puro. +NÃO use markdown. +NÃO use \`\`\` . +NÃO escreva qualquer texto fora do JSON. +A resposta DEVE ser um objeto JSON único (nunca um array solto). + +⸻ + +ANALISE a imagem de um alimento ou prato. + +⸻ +REGRAS IMPORTANTES DE IDENTIFICAÇÃO (OBRIGATÓRIAS) + +• Identifique e liste TODOS os alimentos CLARAMENTE VISÍVEIS e EM FOCO na imagem. +• IGNORE completamente: + – Itens desfocados ou fora de foco (bokeh/blur de fundo) + – Reflexos, sombras ou duplicações visuais do mesmo alimento + – Alimentos em segundo plano, mesas vizinhas ou embalagens decorativas + – Qualquer coisa que NÃO esteja no prato/recipiente principal sendo fotografado +• Considere APENAS o prato/recipiente principal que é o foco da foto. +• Nunca retorne apenas um item se mais de um alimento estiver visível. +• Não agrupe alimentos diferentes em um único item. +• Cada alimento identificado deve gerar um objeto separado dentro de items. +• Se algum alimento estiver parcialmente visível ou gerar dúvida, inclua mesmo assim e marque em flags (ex.: "parcial", "porcao_duvidosa"). +• Não repita o mesmo item duas vezes. +• Se houver mais de uma unidade do MESMO alimento e isso estiver claramente visível, use um único item com portion no formato: + “X unidades (Y g)”. +• Se a quantidade NÃO estiver clara, assuma 1 unidade e marque flags com "porcao_duvidosa". + +⸻ +REGRAS CRÍTICAS DE PORÇÃO (MUITO IMPORTANTE) + +ALIMENTOS PREPARADOS, COZIDOS OU MISTURADOS: +(ex.: ovos mexidos, arroz, feijão, carne moída, frango desfiado, massas, purês, refogados, preparações caseiras) + +• NUNCA use número de unidades. +• NUNCA use termos como: + “2 ovos”, “1 filé”, “3 colheres”, “200 g”, “1 pedaço”. +• NUNCA tente converter visualmente em quantidade de ingredientes crus. + +• Para esses alimentos, o campo portion DEVE: + – descrever o preparo + – usar apenas referência visual + +Exemplos CORRETOS: +• “Ovos mexidos – porção média no prato” +• “Arroz branco cozido – porção média” +• “Feijão carioca – porção pequena” +• “Carne moída refogada – porção média” +• “Macarrão cozido – porção grande” + +Exemplos PROIBIDOS: +• “2 ovos mexidos” +• “1 concha de feijão”\n• “3 colheres de arroz” +• “150 g de macarrão” + +SE ESTA REGRA FOR VIOLADA, CONSIDERE A RESPOSTA INVÁLIDA E REFAÇA INTERNAMENTE ANTES DE RESPONDER. + +⸻ +ALIMENTOS INTEIROS E SEPARÁVEIS (ÚNICO CASO EM QUE UNIDADES SÃO PERMITIDAS) + +Use unidades APENAS quando o alimento estiver: +• inteiro +• claramente separável +• não misturado + +Exemplos permitidos: +• frutas inteiras (banana, maçã, laranja) +• ovos cozidos inteiros +• pães inteiros +• itens embalados individuais visíveis + +Para frutas inteiras, use limites conservadores: +• Banana: 1 a 2 unidades (a menos que a imagem mostre claramente mais) +• Maçã / Laranja: 1 unidade cada (a menos que apareçam múltiplas claramente) + +⸻ +REGRAS DE CÁLCULO + +• O objeto total DEVE ser a soma exata de todos os itens listados: + – calories + – protein + – carbs + – fat + – fiber + – sugar\n – sodium_mg + +• Use valores coerentes com bases nutricionais reais. +• category deve refletir o tipo do prato (ex.: “Almoço”, “Jantar”, “Café da manhã”, “Lanche”, “Refeição caseira”). + +⸻ +QUALIDADE E CONSISTÊNCIA + +• Se houver mais de um alimento identificado e apenas um item for retornado, considere a resposta inválida e refaça internamente. +• confidence deve refletir a clareza da imagem. +• assumptions deve listar de 1 a 3 suposições feitas (tamanho visual, preparo, quantidade). +• insights: no máximo 3 frases curtas, sem moralismo. + +⸻ +CASO NÃO SEJA COMIDA + +• Se a imagem não contiver alimento: + – retorne items vazio + – explique o motivo em confidence + – tip.title e tip.text devem orientar o usuário a enviar uma foto de alimento + +⸻ +FORMATO DE RESPOSTA (OBRIGATÓRIO) + +{ + "items":[ + { + "name":"", + "portion":"", + "calories":0, + "protein":0, + "carbs":0, + "fat":0, + "fiber":0, + "sugar":0, + "sodium_mg":0, + "flags":[] + } + ], + "total":{ + "calories":0, + "protein":0, + "carbs":0, + "fat":0, + "fiber":0, + "sugar":0, + "sodium_mg":0 + }, + "category":"", + "health_score":0, + "confidence":"", + "assumptions":[], + "questions":[], + "insights":[], + "tip":{ + "title":"", + "text":"", + "reason":"" + }, + "swap_suggestions":[], + "next_best_actions":[] +} +`; + +export const COACH_SYSTEM_PROMPT = ` +Você é o "Titan Coach", um treinador olímpico de elite e nutricionista esportivo PhD. +Sua missão é analisar o físico de um usuário através de 3 fotos (Frente, Lado, Costas) e criar um **Protocolo de Transformação** completo, rico e detalhado. + +RETORNE APENAS JSON. +NÃO use Markdown. +Formato de Resposta (Siga estritamente esta estrutura): + +{ + "analysis": { + "body_fat_percentage": 0, + "somatotype": "Ectomorfo" | "Mesomorfo" | "Endomorfo", + "muscle_mass_level": "Baixo" | "Médio" | "Alto", + "posture_analysis": "Texto detalhado sobre postura (ex: leve cifose, lordose, desvios laterais)", + "strengths": ["Ombros largos", "Cintura fina", "Bons quadríceps"], + "weaknesses": ["Panturrilhas pouco desenvolvidas", "Peitoral superior fraco"] + }, + "diet": { + "total_calories": 0, + "macros": { + "protein_g": 0, + "carbs_g": 0, + "fats_g": 0 + }, + "hydration_liters": 0, + "supplements": [ + { "name": "Creatina", "dosage": "5g pós-treino", "reason": "Aumento de força e recuperação" }, + { "name": "Whey Protein", "dosage": "30g se não bater a meta", "reason": "Praticidade para bater proteínas" }, + { "name": "Multivitamínico", "dosage": "1 caps almoço", "reason": "Micro-nutrientes essenciais" } + ], + "meal_plan_example": [ + { + "name": "Café da Manhã", + "time_range": "07:00 - 08:00", + "options": [ + "Opção 1: 3 Ovos mexidos + 1 Banana + 40g Aveia", + "Opção 2: 2 Fatias Pão Integral + 100g Frango Desfiado + Queijo Cottage" + ], + "substitution_suggestion": "Para vegetarianos: Trocar frango por Tofu ou ovos por Shake proteico vegano." + }, + { + "name": "Almoço", + "time_range": "12:00 - 13:00", + "options": [ + "Opção 1: 150g Frango Grelhado + 120g Arroz Branco + Vegetais Verdes à vontade", + "Opção 2: 150g Patinho Moído + 150g Batata Inglesa + Salada Mista" + ], + "substitution_suggestion": "Se enjoar de arroz, use Macarrão Integral (mesmo peso) ou Batata Doce (peso x1.3)." + }, + { + "name": "Lanche da Tarde", + "time_range": "16:00 - 16:30", + "options": [ + "Opção 1: 1 Iogurte Grego Zero + 20g Nozes", + "Opção 2: 1 Fruta + 1 Dose de Whey" + ], + "substitution_suggestion": "Pode trocar as gorduras (nozes) por Pasta de Amendoim." + }, + { + "name": "Jantar", + "time_range": "20:00 - 21:00", + "options": [ + "Opção 1: 150g Peixe Branco (Tilápia) + Salada Completa + Azeite de Oliva", + "Opção 2: Omelete de 3 Ovos com Espinafre e Tomate" + ], + "substitution_suggestion": "Evite carboidratos pesados a noite se o objetivo for secar." + } + ] + }, + "workout": { + "split": "ABC" | "ABCD" | "ABCDE" | "Fullbody", + "focus": "Hipertrofia" | "Força" | "Perda de Gordura", + "frequency_days": 0, + "injury_adaptations": { + "knee_pain": "Substituir Agachamento por Leg Press 45 com pés altos", + "shoulder_pain": "Fazer Supino com Halteres pegada neutra ao invés de barra", + "back_pain": "Evitar Terra e Remada Curvada, preferir máquinas apoiadas" + }, + "routine": [ + { + "day": "Segunda", + "muscle_group": "Peito + Tríceps", + "exercises": [ + { "name": "Supino Inclinado com Halteres", "sets": 4, "reps": "8-12", "technique": "Focar na parte superior, descida controlada" }, + { "name": "Crucifixo Máquina", "sets": 3, "reps": "12-15", "technique": "Pico de contração de 1s" } + ] + } + ] + }, + "motivation_quote": "Uma frase curta de impacto." +} + +Regras IMPORTANTES: +1. Seja MUITO DETALHADO na dieta. Dê SEMPRE pelo menos 2 opções para CADA refeição ("options"). +2. Inclua o horário sugerido ("time_range") para cada refeição. +3. O campo "substitution_suggestion" deve dar uma alternativa clara de troca de alimentos (ex: trocar carbo X por Y). +4. Adapte o treino ao biotipo (ex: Ectomorfo menos volume, Endomorfo mais cardio). +5. Nos suplementos, especifique COMO tomar e PORQUE. +6. A resposta DEVE ser um JSON válido. +`; diff --git a/supabase/migrations/20240105000001_whatsapp_state.sql b/supabase/migrations/20240105000001_whatsapp_state.sql new file mode 100644 index 0000000..5e00d98 --- /dev/null +++ b/supabase/migrations/20240105000001_whatsapp_state.sql @@ -0,0 +1,31 @@ +-- Create table to store conversation state +create table if not exists public.whatsapp_conversations ( + phone_number text primary key, + state text not null default 'IDLE', -- IDLE, COACH_FRONT, COACH_SIDE, COACH_BACK, COACH_GOAL + temp_data jsonb default '{}'::jsonb, + updated_at timestamp with time zone default now() +); + +-- Turn on RLS +alter table public.whatsapp_conversations enable row level security; + +-- Allow service role full access +create policy "Service role full access" + on public.whatsapp_conversations + for all + to service_role + using (true) + with check (true); + +-- Create a bucket for temporary coach uploads if it doesn't exist +insert into storage.buckets (id, name, public) +values ('coach-uploads', 'coach-uploads', true) +on conflict (id) do nothing; + +create policy "Public Access" + on storage.objects for select + using ( bucket_id = 'coach-uploads' ); + +create policy "Service Role Upload" + on storage.objects for insert + with check ( bucket_id = 'coach-uploads' ); diff --git a/supabase/migrations/20260120_create_coach_analyses.sql b/supabase/migrations/20260120_create_coach_analyses.sql new file mode 100644 index 0000000..942ec85 --- /dev/null +++ b/supabase/migrations/20260120_create_coach_analyses.sql @@ -0,0 +1,39 @@ +-- Create table for Coach AI analyses +create table if not exists public.coach_analyses ( + id uuid default gen_random_uuid() primary key, + user_id uuid references auth.users(id) on delete set null, + created_at timestamptz default now(), + + -- Metadata + source text default 'whatsapp', -- 'web', 'whatsapp' + image_url text, + + -- AI Data + ai_raw_response text, + ai_structured jsonb, -- Full JSON response + + -- Structured Fields for Analytics + biotype text, -- 'Ectomorph', 'Mesomorph', 'Endomorph' + estimated_body_fat numeric, + muscle_mass_level text, -- 'Low', 'Medium', 'High' + goal_suggestion text, -- 'Cut', 'Bulk', 'Recomp' + + -- Plan Usage + used_free_quota boolean default false +); + +-- Enable RLS +alter table public.coach_analyses enable row level security; + +-- Policies +create policy "Users can view their own coach analyses" + on public.coach_analyses for select + using (auth.uid() = user_id); + +create policy "Service role insert coach analyses" + on public.coach_analyses for insert + with check (true); + +create policy "Service role updates" + on public.coach_analyses for update + using (true); diff --git a/supabase/migrations/20260120_professional_schema.sql b/supabase/migrations/20260120_professional_schema.sql new file mode 100644 index 0000000..fe5e8d0 --- /dev/null +++ b/supabase/migrations/20260120_professional_schema.sql @@ -0,0 +1,190 @@ +-- ============================================= +-- MIGRATION: PROFESSIONAL SAAS MODULE +-- Description: Creates tables for Professionals, Students, Assessments, and Workouts. +-- ============================================= + +-- 1. PROFESSIONALS TABLE (Extends Profile for Pro Users) +CREATE TABLE IF NOT EXISTS public.professionals ( + id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY, + business_name TEXT, + cref_crn TEXT, -- License number + bio TEXT, + specialties TEXT[], + logo_url TEXT, + primary_color TEXT DEFAULT '#059669', -- Brand Color + + contacts JSONB DEFAULT '{}'::jsonb, -- { "whatsapp": "...", "instagram": "..." } + + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Enable RLS +ALTER TABLE public.professionals ENABLE ROW LEVEL SECURITY; + +-- Policies for Professionals +CREATE POLICY "Professionals can view/edit own profile" +ON public.professionals +FOR ALL +USING (auth.uid() = id) +WITH CHECK (auth.uid() = id); + +CREATE POLICY "Public can view professionals (optional, for directory)" +ON public.professionals +FOR SELECT +USING (true); + + +-- 2. PRO_STUDENTS TABLE (The Professional's CRM) +CREATE TABLE IF NOT EXISTS public.pro_students ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + professional_id UUID REFERENCES public.professionals(id) ON DELETE CASCADE NOT NULL, + + name TEXT NOT NULL, + email TEXT, + phone TEXT, + status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'pending')), + + -- Optional: Link to a real app user if they convert + linked_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + + goals TEXT, + notes TEXT, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Enable RLS +ALTER TABLE public.pro_students ENABLE ROW LEVEL SECURITY; + +-- Policies for Pro Students +CREATE POLICY "Professionals can manage own students" +ON public.pro_students +FOR ALL +USING (auth.uid() = professional_id) +WITH CHECK (auth.uid() = professional_id); + +CREATE POLICY "Students can view their own record" +ON public.pro_students +FOR SELECT +USING (auth.uid() = linked_user_id); + + +-- 3. PRO_ASSESSMENTS TABLE (Physical Evaluations) +CREATE TABLE IF NOT EXISTS public.pro_assessments ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + professional_id UUID REFERENCES public.professionals(id) ON DELETE CASCADE NOT NULL, + student_id UUID REFERENCES public.pro_students(id) ON DELETE CASCADE NOT NULL, + + date DATE DEFAULT CURRENT_DATE, + + -- Basic Metrics + weight DECIMAL(5,2), -- kg + height DECIMAL(3,2), -- meters + age INTEGER, + + -- Calculated + bf_percent DECIMAL(4,1), -- Body Fat % + muscle_percent DECIMAL(4,1), + bmi DECIMAL(4,1), + + -- JSON Data for flexibility (skinfolds, circumferences, photos) + -- Structure: { "chest": 90, "waist": 80, ... } + measurements JSONB DEFAULT '{}'::jsonb, + + -- Structure: { "method": "pollock7", "folds": { ... } } + methodology JSONB DEFAULT '{}'::jsonb, + + -- Structure: ["url1", "url2"] + photos TEXT[], + + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Enable RLS +ALTER TABLE public.pro_assessments ENABLE ROW LEVEL SECURITY; + +-- Policies for Assessments +CREATE POLICY "Professionals can manage assessments" +ON public.pro_assessments +FOR ALL +USING (auth.uid() = professional_id) +WITH CHECK (auth.uid() = professional_id); + +CREATE POLICY "Students can view their own assessments" +ON public.pro_assessments +FOR SELECT +USING ( + EXISTS ( + SELECT 1 FROM public.pro_students + WHERE id = pro_assessments.student_id + AND linked_user_id = auth.uid() + ) +); + + +-- 4. PRO_WORKOUTS TABLE (Workout Library) +CREATE TABLE IF NOT EXISTS public.pro_workouts ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + professional_id UUID REFERENCES public.professionals(id) ON DELETE CASCADE NOT NULL, + + title TEXT NOT NULL, + description TEXT, + difficulty TEXT CHECK (difficulty IN ('beginner', 'intermediate', 'advanced')), + + -- Structure: [{ "name": "Supino", "sets": 3, "reps": "10-12", "video": "..." }] + exercises JSONB DEFAULT '[]'::jsonb, + + tags TEXT[], -- ['hipertrofia', 'emagrecimento'] + + is_template BOOLEAN DEFAULT false, -- If true, it's a library item. If false, assigned to a specific student? + -- Actually, let's keep it simple: Workouts are templates or assigned. + + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Enable RLS +ALTER TABLE public.pro_workouts ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Professionals can manage own workouts" +ON public.pro_workouts +FOR ALL +USING (auth.uid() = professional_id) +WITH CHECK (auth.uid() = professional_id); + + +-- 5. PRO_ASSIGNMENTS (Assigning Workouts to Students) +CREATE TABLE IF NOT EXISTS public.pro_assignments ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + professional_id UUID REFERENCES public.professionals(id) ON DELETE CASCADE NOT NULL, + student_id UUID REFERENCES public.pro_students(id) ON DELETE CASCADE NOT NULL, + workout_id UUID REFERENCES public.pro_workouts(id) ON DELETE CASCADE NOT NULL, + + start_date DATE DEFAULT CURRENT_DATE, + end_date DATE, + + notes TEXT, + + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Enable RLS +ALTER TABLE public.pro_assignments ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Professionals can manage assignments" +ON public.pro_assignments +FOR ALL +USING (auth.uid() = professional_id) +WITH CHECK (auth.uid() = professional_id); + +CREATE POLICY "Students can view their assignments" +ON public.pro_assignments +FOR SELECT +USING ( + EXISTS ( + SELECT 1 FROM public.pro_students + WHERE id = pro_assignments.student_id + AND linked_user_id = auth.uid() + ) +); diff --git a/supabase/migrations/20260217_create_payments.sql b/supabase/migrations/20260217_create_payments.sql new file mode 100644 index 0000000..0c82fc9 --- /dev/null +++ b/supabase/migrations/20260217_create_payments.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS payments ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES auth.users(id) NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + status TEXT NOT NULL DEFAULT 'completed', -- completed, pending, failed + plan_type TEXT NOT NULL, -- monthly, yearly, lifetime + payment_method TEXT, -- credit_card, pix, etc + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +ALTER TABLE payments ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own payments" +ON payments FOR SELECT +USING (auth.uid() = user_id); diff --git a/supabase_introspection.sql b/supabase_introspection.sql new file mode 100644 index 0000000..f3d0e83 --- /dev/null +++ b/supabase_introspection.sql @@ -0,0 +1,208 @@ +-- ============================================================ +-- FOODSNAP - SUPABASE FULL INTROSPECTION (SINGLE JSON OUTPUT) +-- Execute no SQL Editor do Supabase (Dashboard > SQL Editor) +-- Retorna TUDO em um único JSON +-- ============================================================ + +SELECT jsonb_build_object( + + -- 1. TABLES & COLUMNS + 'tables', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', t.table_name, + 'column', c.column_name, + 'type', c.data_type, + 'udt', c.udt_name, + 'default', c.column_default, + 'nullable', c.is_nullable + ) ORDER BY t.table_name, c.ordinal_position) + FROM information_schema.tables t + JOIN information_schema.columns c + ON t.table_name = c.table_name AND t.table_schema = c.table_schema + WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE' + ), + + -- 2. VIEWS + 'views', ( + SELECT jsonb_agg(jsonb_build_object( + 'name', table_name, + 'definition', view_definition + )) + FROM information_schema.views + WHERE table_schema = 'public' + ), + + -- 3. FOREIGN KEYS + 'foreign_keys', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', tc.table_name, + 'column', kcu.column_name, + 'ref_table', ccu.table_name, + 'ref_column', ccu.column_name, + 'constraint', tc.constraint_name + )) + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage ccu + ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema + WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = 'public' + ), + + -- 4. PRIMARY KEYS & UNIQUE + 'primary_keys', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', tc.table_name, + 'constraint', tc.constraint_name, + 'type', tc.constraint_type, + 'column', kcu.column_name + )) + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema + WHERE tc.table_schema = 'public' AND tc.constraint_type IN ('PRIMARY KEY', 'UNIQUE') + ), + + -- 5. CHECK CONSTRAINTS + 'check_constraints', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', tc.table_name, + 'constraint', tc.constraint_name, + 'check', cc.check_clause + )) + FROM information_schema.table_constraints tc + JOIN information_schema.check_constraints cc + ON tc.constraint_name = cc.constraint_name AND tc.constraint_schema = cc.constraint_schema + WHERE tc.table_schema = 'public' AND tc.constraint_type = 'CHECK' + ), + + -- 6. INDEXES + 'indexes', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', tablename, + 'index', indexname, + 'def', indexdef + )) + FROM pg_indexes + WHERE schemaname = 'public' + ), + + -- 7. RLS POLICIES + 'rls_policies', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', tablename, + 'policy', policyname, + 'permissive', permissive, + 'roles', roles, + 'cmd', cmd, + 'qual', qual, + 'with_check', with_check + )) + FROM pg_policies + WHERE schemaname = 'public' + ), + + -- 8. FUNCTIONS + 'functions', ( + SELECT jsonb_agg(jsonb_build_object( + 'name', p.proname, + 'args', pg_get_function_arguments(p.oid), + 'returns', pg_get_function_result(p.oid), + 'definition', pg_get_functiondef(p.oid) + )) + FROM pg_proc p + JOIN pg_namespace n ON n.oid = p.pronamespace + WHERE n.nspname = 'public' + ), + + -- 9. TRIGGERS + 'triggers', ( + SELECT jsonb_agg(jsonb_build_object( + 'name', trigger_name, + 'event', event_manipulation, + 'table', event_object_table, + 'action', action_statement, + 'timing', action_timing + )) + FROM information_schema.triggers + WHERE trigger_schema = 'public' + ), + + -- 10. STORAGE BUCKETS + 'storage_buckets', ( + SELECT jsonb_agg(jsonb_build_object( + 'id', id, + 'name', name, + 'public', public, + 'size_limit', file_size_limit, + 'mime_types', allowed_mime_types + )) + FROM storage.buckets + ), + + -- 11. STORAGE POLICIES + 'storage_policies', ( + SELECT jsonb_agg(jsonb_build_object( + 'policy', policyname, + 'table', tablename, + 'cmd', cmd, + 'qual', qual, + 'with_check', with_check + )) + FROM pg_policies + WHERE schemaname = 'storage' + ), + + -- 12. AUTH STATS + 'auth_stats', ( + SELECT jsonb_build_object( + 'total_users', count(*), + 'confirmed', count(*) FILTER (WHERE email_confirmed_at IS NOT NULL), + 'active_30d', count(*) FILTER (WHERE last_sign_in_at > now() - interval '30 days') + ) + FROM auth.users + ), + + -- 13. ROW COUNTS + 'row_counts', ( + SELECT jsonb_agg(jsonb_build_object( + 'table', relname, + 'rows', n_live_tup + ) ORDER BY n_live_tup DESC) + FROM pg_stat_user_tables + WHERE schemaname = 'public' + ), + + -- 14. EXTENSIONS + 'extensions', ( + SELECT jsonb_agg(jsonb_build_object( + 'name', extname, + 'version', extversion + )) + FROM pg_extension + ), + + -- 15. REALTIME + 'realtime_tables', ( + SELECT jsonb_agg(jsonb_build_object( + 'pub', pubname, + 'schema', schemaname, + 'table', tablename + )) + FROM pg_publication_tables + WHERE pubname = 'supabase_realtime' + ), + + -- 16. ENUMS + 'enums', ( + SELECT jsonb_agg(jsonb_build_object( + 'type', t.typname, + 'value', e.enumlabel + )) + FROM pg_type t + JOIN pg_enum e ON t.oid = e.enumtypid + JOIN pg_namespace n ON t.typnamespace = n.oid + WHERE n.nspname = 'public' + ) + +) AS full_introspection; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d8a27ae --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f1abd1f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + } + } + }; +});