diff --git a/.cursor/commands/dashboard.md b/.cursor/commands/dashboard.md
new file mode 100644
index 0000000..e69de29
diff --git a/.github/workflows/db-and-i18n.yml b/.github/workflows/db-and-i18n.yml
new file mode 100644
index 0000000..63bf4e0
--- /dev/null
+++ b/.github/workflows/db-and-i18n.yml
@@ -0,0 +1,86 @@
+name: DB migrations & i18n (minimal)
+
+on:
+ push:
+ branches: [ dev, main ]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ preview:
+ if: github.ref == 'refs/heads/dev'
+ runs-on: ubuntu-latest
+ environment: preview
+ env:
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10.17.1
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+ - name: Ensure pnpm store exists (for caching)
+ run: mkdir -p "$(pnpm store path)"
+ - run: corepack enable
+ - run: pnpm install --no-frozen-lockfile
+ - name: Lint
+ run: pnpm lint
+ # pgcrypto is ensured inside setup.js using node-postgres
+ - name: Run migrations
+ run: node setup.js
+ - name: i18n check
+ run: pnpm run i18n:check
+ - name: Brand detection tests
+ run: pnpm run test:brand-detection
+ env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ - name: Trigger Vercel Deploy Hook
+ if: ${{ success() }}
+ run: curl -X POST "$VERCEL_DEPLOY_HOOK_URL"
+ env:
+ VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }}
+
+ production:
+ if: github.ref == 'refs/heads/main'
+ runs-on: ubuntu-latest
+ environment: production
+ env:
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 10.17.1
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+ - name: Ensure pnpm store exists (for caching)
+ run: mkdir -p "$(pnpm store path)"
+ - run: corepack enable
+ - run: pnpm install --no-frozen-lockfile
+ - name: Lint
+ run: pnpm lint
+ # pgcrypto is ensured inside setup.js using node-postgres
+ - name: Run migrations
+ run: node setup.js
+ - name: i18n check
+ run: pnpm run i18n:check
+ - name: Brand detection tests
+ run: pnpm run test:brand-detection
+ env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ - name: Trigger Vercel Deploy Hook
+ if: ${{ success() }}
+ run: curl -X POST "$VERCEL_DEPLOY_HOOK_URL"
+ env:
+ VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }}
+
+
diff --git a/.gitignore b/.gitignore
index c924c45..c779278 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,3 +64,6 @@ cookies.txt
# Generated files
repomix-output.txt
i18n.cache
+
+# lockfiles from other package managers
+package-lock.json
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..209e3ef
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..98eb310
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,49 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- `app/` hosts Next.js 15 routes, server actions, and layouts; keep features grouped by route segment.
+- `components/` contains shared UI; export reusable pieces via `index.ts` barrels where helpful and name files in PascalCase.
+- `lib/` centralizes utilities (`auth`, `clients`, `validators`); prefer the `@/` alias when importing.
+- `config/`, `hooks/`, `i18n/`, and `messages/` hold environment loaders, React hooks, and localization strings; sync locale changes with `pnpm i18n:check`.
+- `supabase/`, `migrations/`, and `better-auth_migrations/` track SQL; commit generated files and keep IDs aligned with drizzle migrations.
+- `public/` stores static assets; `scripts/` houses automation TSX scripts; `.github/workflows/` defines CI/CD.
+
+##
+
+## Build, Test, and Development Commands
+- `pnpm run setup` bootstraps the project (installs deps, seeds auth tables).
+- `pnpm dev` runs the Turbopack dev server on localhost.
+- `pnpm build` produces the production bundle; `pnpm start` serves the compiled app.
+- `pnpm lint` enforces ESLint+Next rules; run before every PR.
+- `pnpm db:generate`, `pnpm db:migrate`, and `pnpm db:push` manage drizzle migrations; `pnpm db:studio` opens the schema explorer.
+- `pnpm debug:env` prints resolved environment variables for troubleshooting.
+
+## Coding Style & Naming Conventions
+TypeScript is strict; enable editors to use the repo `tsconfig.json`. Rely on Prettier defaults (2-space indent, semicolons) and Next linting. Components and hooks use PascalCase and `useCamelCase` respectively; route folders stay lower-case kebab. Keep database files snake_case with numeric prefixes (`001_*`). Import with the `@/` alias instead of deep relative paths. Update localization keys consistently across `messages/` locales.
+
+## Development Guidelines
+1. **Document every change**
+ - Update relevant `README`, comments, or inline docs when modifying or creating features.
+ - When in doubt, explain *why* the code exists.
+
+2. **No hard-coded strings**
+ - Always use `next-intl` and the `i18n` system.
+ - Add/modify entries in `/messages` and ensure both locales are updated.
+ - Run `pnpm i18n:check` before committing.
+
+3. **Prefer reuse over duplication**
+ - Before writing new code, check if a utility, hook, or component already implements similar behavior.
+ - Extend existing logic rather than duplicating functionality.
+
+4. **Avoid rigid static logic**
+ - Do not over-rely on long `if/else` or `switch` blocks with hard-coded cases.
+ - Prefer flexible patterns, data-driven configs, or leverage an **LLM-powered function** when appropriate for broader coverage and adaptability.
+
+## Testing Guidelines
+Automated tests are not yet wired; guard changes with `pnpm lint`, manual verification in `pnpm dev`, and database smoke tests via `pnpm db:studio`. When adding tests, colocate `*.test.ts(x)` beside the feature and stub network calls to keep runs deterministic. Extend CI to run new test suites before merging.
+
+## Commit & Pull Request Guidelines
+Write imperative, concise commit messages (~60 chars). Follow the current history by prefacing fixes with `fix:` when appropriate and omitting prefixes for general additions. Each PR should include: a short summary, linked issue or ticket, screenshots/GIFs for UI changes, notes on env vars or migrations, and a checklist of commands run (`pnpm lint`, relevant db tasks). Request review once CI passes and docs/config updates land in the same branch.
+
+## Environment & Secrets
+Copy `.env.example` to `.env.local` and fill the required keys: `DATABASE_URL`, `BETTER_AUTH_SECRET`, email providers, and AI API keys as needed. Use `pnpm debug:env` to confirm values. Never commit `.env.local`; rely on Vercel project settings for deployment.
diff --git a/README.md b/README.md
index e942f3f..e7a3125 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# FireGEO Open-Source SaaS Starter
+# VOXUM
-
+
Get your SaaS running in minutes with authentication, billing, AI chat, and brand monitoring. Zero-config setup with Next.js 15, TypeScript, and PostgreSQL.
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
new file mode 100644
index 0000000..a6068c4
--- /dev/null
+++ b/TROUBLESHOOTING.md
@@ -0,0 +1,140 @@
+# Guide de Dépannage - Voxum
+
+## Problème : Divergence entre ordinateurs
+
+Si vous obtenez des réponses sur un ordinateur mais pas sur l'autre, voici les causes les plus probables et leurs solutions :
+
+### 1. Variables d'environnement manquantes
+
+**Symptôme :** Aucune donnée n'est extraite, erreurs dans la console
+
+**Solution :**
+```bash
+# Exécuter le diagnostic
+npm run debug:env
+```
+
+**Variables critiques à vérifier :**
+- `FIRECRAWL_API_KEY` - **ESSENTIEL** pour le scraping
+- `OPENAI_API_KEY` ou `ANTHROPIC_API_KEY` - **ESSENTIEL** pour l'extraction de données
+- `AUTUMN_SECRET_KEY` - **ESSENTIEL** pour la vérification des crédits
+
+### 2. Configuration des providers AI
+
+**Symptôme :** Erreur "No AI providers configured"
+
+**Solution :**
+1. Vérifiez que au moins une clé API AI est configurée
+2. Redémarrez le serveur après avoir ajouté les variables
+3. Vérifiez que les clés sont valides
+
+### 3. Problèmes de réseau
+
+**Symptôme :** Timeouts, erreurs de connexion
+
+**Solutions :**
+- Vérifiez votre connexion internet
+- Vérifiez les paramètres de firewall/proxy
+- Essayez avec une URL différente (plus simple)
+
+### 4. Problèmes de base de données
+
+**Symptôme :** Erreurs d'authentification, problèmes de crédits
+
+**Solution :**
+```bash
+# Vérifier la connexion à la base de données
+npm run db:push
+```
+
+### 5. Problèmes de cache
+
+**Symptôme :** Données obsolètes ou incohérentes
+
+**Solution :**
+```bash
+# Nettoyer le cache et redémarrer
+rm -rf .next
+npm run dev
+```
+
+## Diagnostic Automatique
+
+### Exécuter le diagnostic complet
+
+```bash
+npm run debug:env
+```
+
+Ce script vérifie :
+- ✅ Variables d'environnement requises
+- ✅ Configuration des providers AI
+- ✅ Connexion à la base de données
+- ✅ Connexion à Firecrawl
+- ✅ Configuration des clés API
+
+### Logs de debug
+
+Les logs détaillés sont maintenant activés. Regardez la console pour :
+- `🔍 [Scraper]` - Logs de scraping
+- `🔍 [Processor]` - Logs d'extraction de données
+- `🔍 [Scrape API]` - Logs de l'API
+- `❌` - Erreurs détaillées
+
+## Solutions par Type d'Erreur
+
+### Erreur : "FIRECRAWL_API_KEY not configured"
+```bash
+# Ajouter dans .env.local
+FIRECRAWL_API_KEY=your_key_here
+```
+
+### Erreur : "No AI providers configured"
+```bash
+# Ajouter au moins une clé API dans .env.local
+OPENAI_API_KEY=your_key_here
+# OU
+ANTHROPIC_API_KEY=your_key_here
+```
+
+### Erreur : "Insufficient credits"
+- Vérifiez votre compte Autumn
+- Assurez-vous que les produits sont correctement configurés
+- Vérifiez que `AUTUMN_SECRET_KEY` est correct
+
+### Erreur : "Connection timeout"
+- Vérifiez votre connexion internet
+- Essayez avec une URL plus simple
+- Vérifiez les paramètres de proxy/firewall
+
+## Vérification Rapide
+
+1. **Variables d'environnement :**
+ ```bash
+ npm run debug:env
+ ```
+
+2. **Redémarrer le serveur :**
+ ```bash
+ npm run dev
+ ```
+
+3. **Tester avec une URL simple :**
+ - Essayez `https://example.com` d'abord
+ - Puis testez avec votre URL cible
+
+4. **Vérifier les logs :**
+ - Ouvrez la console du navigateur
+ - Regardez les logs du serveur
+ - Identifiez le point de défaillance
+
+## Support
+
+Si le problème persiste :
+1. Exécutez `npm run debug:env` et partagez le résultat
+2. Partagez les logs de la console
+3. Indiquez l'URL que vous essayez de scraper
+4. Précisez les différences entre les deux ordinateurs
+
+
+
diff --git a/app/autumn-verify/page.tsx b/app/[locale]/autumn-verify/page.tsx
similarity index 94%
rename from app/autumn-verify/page.tsx
rename to app/[locale]/autumn-verify/page.tsx
index 27fd328..cb95d67 100644
--- a/app/autumn-verify/page.tsx
+++ b/app/[locale]/autumn-verify/page.tsx
@@ -3,9 +3,10 @@
import { useCustomer } from '@/hooks/useAutumnCustomer';
import { useSession } from '@/lib/auth-client';
import { useEffect } from 'react';
+import type { Session } from 'better-auth';
// Separate component that uses Autumn hooks
-function AutumnVerifyContent({ session }: { session: any }) {
+function AutumnVerifyContent({ session }: { session: Session | null }) {
const { customer, isLoading, error } = useCustomer();
useEffect(() => {
@@ -73,7 +74,7 @@ function AutumnVerifyContent({ session }: { session: any }) {
{session?.user?.id || 'Not logged in'}+ {item.companyName || t('brandMonitor.untitledAnalysis')} +
++ {item.url} +
++ {item.createdAt && format(new Date(item.createdAt), 'MMM d, yyyy')} +
+{t('brandMonitor.pleaseLogIn')}
+- {conversation.title || 'Untitled Conversation'} + {conversation.title || t('chat.untitledConversation')}
- {conversation.lastMessageAt && format(new Date(conversation.lastMessageAt), 'MMM d, h:mm a')} + {conversation.lastMessageAt ? format(new Date(conversation.lastMessageAt as unknown as string | number), 'MMM d, h:mm a') : ''}
Loading your account data...
+{t('chat.loadingAccountData')}
- This is a demonstration of the credit-based messaging system. Each message consumes credits from your account balance. + {t('chat.creditBasedDesc')}
- You currently have {remainingMessages} message credits available. + {t('chat.youCurrentlyHave')} {remainingMessages} {t('chat.messageCreditsAvailable')}
- {format(new Date(message.createdAt), 'h:mm a')} + {message.createdAt ? format(new Date(message.createdAt as unknown as string | number), 'h:mm a') : ''}
@@ -232,9 +237,9 @@ function ChatContent({ session }: { session: any }) {- Send a message to begin chatting with AI + {t('chat.sendMessageToBegin')}
{session.user?.email}
- {profileData?.profile?.displayName || 'Not set'} + {profileData?.profile?.displayName || t('dashboard.notSet')}
)}- {profileData?.profile?.phone || 'Not set'} + {profileData?.profile?.phone || t('dashboard.notSet')}
)}+ {t('dashboard.resetsOn')}: {new Date(feature.next_reset_at).toLocaleDateString()} +
+ ) : null} +{t('dashboard.noUsageData')}
+ )} +Email Notifications
-Receive email notifications for important updates
+{t('dashboard.emailNotifications')}
+{t('dashboard.emailNotificationsDesc')}
{session.user?.email}
-Current Plan
-
- {activeProduct ? (
- <>
-
- Resets on: {new Date(feature.next_reset_at).toLocaleDateString()} -
- )} -No usage data available
- )} -{product.display.description}
- )} -Loading...
+{t('dashboard.loading')}
- We've sent you instructions to reset your password. Check your email to continue. + We've sent you instructions to reset your password. Check your email to continue.
- We've sent a password reset link to + We've sent a password reset link to
{email}
- Didn't receive the email? Check your spam folder or try again. + Didn't receive the email? Check your spam folder or try again.
- No worries! We'll help you reset it and get back to building amazing things. + No worries! We'll help you reset it and get back to building amazing things.
- Enter your email and we'll send you a reset link + Enter your email and we'll send you a reset link
- Sign in to continue building amazing things with our powerful API. + {t('auth.welcomeBackDescription')}
- Or{' '} - - create a new account + {t('auth.or')}{' '} + + {t('auth.createNewAccount')}
+ {t('home.hero.description')} +
++ {t('home.hero.features')} +
++ {t('home.cta1.description')} +
+ + {t('home.cta1.button')} + ++ {t('home.faq.description')} +
++ {t('home.faq.questions.howItWorks.answer')} +
++ {t('home.faq.questions.providers.answer')} +
++ {t('home.faq.questions.updates.answer')} +
++ {t('home.faq.questions.insights.answer')} +
++ {t('home.faq.questions.credits.answer')} +
++ {t('home.finalCta.description')} +
++ {t('pricing.choosePerfectPlan')} +
+{t('errors.generic')}
++ {t('home.pricing.description')} +
++ {tMain('meta.version', { date: '22 septembre 2025' })} +
+{t('intro.p1')}
+{t('intro.p2')}
+{t('intro.p3')}
+ +{t('overview.p1')}
+{t('overview.p2')}
+ +{t('data.note')}
+ +{t('bases.p1')}
+ +{t('sharing.p1')}
+{t('sharing.p2')}
+ +{t('transfers.p1')}
+ +{t('retention.p1')}
+ +{t('cookies.p1')}
+ +{t('rights.p1')}
+ +{t('security.p1')}
+ +{t('roles.p1')}
+ +{t('children.p1')}
+ ++ {t.rich('contact.p1', { + strong: (chunks) => {chunks}, + em: (chunks) => {chunks} + })} +
+ +| {t('appendix.th1')} | +{t('appendix.th2')} | +{t('appendix.th3')} | +{t('appendix.th4')} | +{t('appendix.th5')} | +
|---|---|---|---|---|
| {t('appendix.neon.name')} | +{t('appendix.neon.role')} | +{t('appendix.neon.purpose')} | +{t('appendix.neon.loc')} | +{t('appendix.neon.data')} | +
| {t('appendix.openai.name')} | +{t('appendix.openai.role')} | +{t('appendix.openai.purpose')} | +{t('appendix.openai.loc')} | +{t('appendix.openai.data')} | +
| {t('appendix.anthropic.name')} | +{t('appendix.anthropic.role')} | +{t('appendix.anthropic.purpose')} | +{t('appendix.anthropic.loc')} | +{t('appendix.anthropic.data')} | +
| {t('appendix.perplexity.name')} | +{t('appendix.perplexity.role')} | +{t('appendix.perplexity.purpose')} | +{t('appendix.perplexity.loc')} | +{t('appendix.perplexity.data')} | +
| {t('appendix.firecrawl.name')} | +{t('appendix.firecrawl.role')} | +{t('appendix.firecrawl.purpose')} | +{t('appendix.firecrawl.loc')} | +{t('appendix.firecrawl.data')} | +
| {t('appendix.google.name')} | +{t('appendix.google.role')} | +{t('appendix.google.purpose')} | +{t('appendix.google.loc')} | +{t('appendix.google.data')} | +
| {t('appendix.stripe.name')} | +{t('appendix.stripe.role')} | +{t('appendix.stripe.purpose')} | +{t('appendix.stripe.loc')} | +{t('appendix.stripe.data')} | +
| {t('appendix.autumn.name')} | +{t('appendix.autumn.role')} | +{t('appendix.autumn.purpose')} | +{t('appendix.autumn.loc')} | +{t('appendix.autumn.data')} | +
| {t('appendix.vercel.name')} | +{t('appendix.vercel.role')} | +{t('appendix.vercel.purpose')} | +{t('appendix.vercel.loc')} | +{t('appendix.vercel.data')} | +
- Start building with our powerful API and unlock new possibilities for your applications. + {t('auth.joinDevelopersDescription')}
- Or{' '} - - sign in to existing account + {t('auth.or')}{' '} + + {t('auth.signInExisting')}
Must be at least 8 characters long
+{t('auth.passwordRequirement')}
- It looks like you already have an account with this email address. + {t('auth.existingAccountDetected')}
- Create a new password for your account. Make sure it's strong and unique. + Create a new password for your account. Make sure it's strong and unique.
+ {tMain('meta.version', { date: '22 septembre 2025' })} +
+{t('content.general.p1')}
+{t('content.general.p2')}
+{t('content.general.p3')}
+{t('content.general.p4')}
+ +{t('content.personal.p1')}
+ +{t('content.access.p1')}
+{t('content.access.p2')}
+ +{t('content.features.p1')}
+{t('content.features.p2')}
+{t('content.features.p3')}
+ +{t('content.ip.p1')}
+{t('content.ip.p2')}
+{t('content.ip.p3')}
+ +{t('content.restrictions.p1')}
+ +{t('content.privacy.p1')}
+ +{t('content.changes.p1')}
+ +{t('content.warranty.p1')}
+ +{t('content.liability.p1')}
+ +{t('content.indemnification.p1')}
+ +{t('content.misc.p1')}
+ +{t('content.law.p1')}
+ +{t('content.contact.p1')}
+Loading...
+{t('subtitle')}
+ ++ {t('hint')} +
+- Track how AI models rank your brand against competitors -
-- {analysis.companyName || 'Untitled Analysis'} -
-- {analysis.url} -
-- {analysis.createdAt && format(new Date(analysis.createdAt), 'MMM d, yyyy')} -
-Please log in to access the brand monitor
-- Track how AI models rank your brand against competitors -
-- Powered by AI • Real-time Analysis • Competitor Tracking • SEO Insights -
-- Choose the plan that fits your monitoring needs -
-Perfect for personal brands
-For growing businesses
-For agencies & large brands
-- Monitor your brand visibility across ChatGPT, Claude, Perplexity and more -
- - Start Free Analysis - -- Everything you need to know about FireGEO Monitor -
-- FireGEO Monitor analyzes your brand's visibility across major AI platforms like ChatGPT, Claude, and Perplexity. Simply enter your website URL, and we'll show you how AI models rank your brand against competitors, what prompts trigger your appearance, and provide actionable insights to improve your AI visibility. -
-- We monitor all major AI platforms including OpenAI's ChatGPT, Anthropic's Claude, Perplexity, Google's Gemini, and more. Our system continuously updates as new AI providers emerge, ensuring you always have comprehensive visibility across the AI landscape. -
-- Our monitoring runs in real-time. When you request an analysis, we query all AI providers simultaneously to get the most current results. You can run new analyses anytime to track changes in your brand visibility and see how your optimization efforts are performing. -
-- You'll see your brand's visibility score, competitor rankings, which prompts trigger your appearance, response quality analysis, and specific recommendations to improve your AI presence. The platform also tracks trends over time and alerts you to significant changes. -
-- Each brand analysis uses 10 credits (1 credit for initial URL analysis, 9 credits for the full AI provider scan). The free tier includes 100 credits monthly, enough for 10 complete analyses. Pro plans include unlimited analyses for comprehensive monitoring. -
-- Take control of how AI models present your brand -
-- Choose the perfect plan for your needs. Always flexible to scale up or down. -
- {session && ( -- Logged in as: {session.user?.email} -
- )} -Error loading pricing
-Select the perfect plan for your needs
-