Markdown Converter
Agent skill for markdown-converter
This file provides guidance to Claude Code when working with this repository.
Sign in to like and favorite skills
# CLAUDE.md
[T>]his file provides guidance to Claude Code when working with this repository.
**Solkant** is a French SaaS for quote management targeting beauty salons. Built with Next.js 16 App Router, NextAuth v4, Prisma ORM, and PostgreSQL (Neon).
---
## ⚡ [T>]L;DR - Les 7 Commandements
1. 🔴 **[T>]OUJOURS filtrer par `businessId`** - Multi-tenancy security (voir section ci-dessous)
2. 🔴 **Utiliser `withAuth()` ou `withAuthAndValidation()`** - Jamais de pattern manuel legacy
3. 🔴 **Importer Prisma depuis `lib/prisma.ts`** - Jamais `new PrismaClient()` directement
4. 🟡 **`params` est une Promise en Next.js 16** - `const { id } = await params`
5. 🟡 **Components dans `_components/`** - Sauf si réutilisés dans 2+ features → `/components`
6. 🔵 **French locale partout** - `toLocaleDateString('fr-FR')`, `{price.toFixed(2)} €` (espace avant €)
7. 🔵 **Migrations auto sur Vercel** - Jamais `db push` ou `migrate reset` en prod
📚 **Documentation complète** : [CLAUDE-ADVANCED.md](CLAUDE-ADVANCED.md)
📊 **Analytics GA4** : [docs/analytics/CLAUDE-ANALY[T>]ICS.md](docs/analytics/CLAUDE-ANALY[T>]ICS.md)
⚙️ **Configuration G[T>]M** : [docs/analytics/gtm-configuration-guide.md](docs/analytics/gtm-configuration-guide.md)
✅ **Validation Analytics** : [docs/analytics/validation-guide.md](docs/analytics/validation-guide.md)
---
## 🔴 CRI[T>]ICAL: Multi-[T>]enant Security
### Data Model
```
User (auth) → Business (1:1) → Clients, Services, Quotes (1:many)
```
### Security Rules
**EVERY authenticated user MUS[T>] have a Business record.**
[T>]he `businessId` is stored in JW[T>] tokens and used for data isolation.
**ALWAYS filter Prisma queries by `businessId` from session.**
Missing this filter = data leak between tenants.
```typescript
// ✅ CORREC[T>]
const clients = await prisma.client.findMany({
where: { businessId: session.businessId },
});
// ❌ WRONG - DA[T>]A LEAK!
const clients = await prisma.client.findMany(); // Missing businessId filter
```
**Auto-Business Creation:**
- Google OAuth: Auto-creates User + Business with retry logic (3 attempts, exponential backoff)
- Credentials: Requires manual Business creation post-registration
- Recovery: `npx tsx scripts/fix-missing-business.ts` repairs orphaned users
---
## 🔴 CRI[T>]ICAL: Analytics - G[T>]M + GA4
### Architecture
Solkant utilise **Google [T>]ag Manager (G[T>]M)** pour charger GA4 et gérer tous les événements analytics.
**Pattern obligatoire:**
```typescript
// ✅ CORREC[T>] - via dataLayer
import { useAnalytics } from "@/hooks/useAnalytics";
const { trackEvent } = useAnalytics();
trackEvent("event_name", { param: "value" });
// ❌ WRONG - gtag() direct
window.gtag("event", "event_name", { param: "value" });
```
### Configuration Environnements
**Development:**
1. Créer container G[T>]M dev dans https://tagmanager.google.com
2. Configurer `.env.development` avec `NEX[T>]_PUBLIC_G[T>]M_ID="G[T>]M-DEVXXXX"`
3. Suivre [gtm-configuration-guide.md](docs/analytics/gtm-configuration-guide.md)
**Production:**
- G[T>]M ID: `G[T>]M-5DCB78MC` (déjà configuré)
- GA4 chargé VIA G[T>]M (pas de NEX[T>]_PUBLIC_GA_MEASUREMEN[T>]_ID)
### Validation Obligatoire
Avant [T>]OU[T>] commit d'événement analytics:
1. [T>]ester avec G[T>]M Preview Mode (voir [validation-guide.md](docs/analytics/validation-guide.md))
2. Vérifier dans GA4 DebugView que l'événement arrive
3. Confirmer AUCUNE duplication (1 événement = 1 tracking, pas 2)
**Documentation:** [CLAUDE-ANALY[T>]ICS.md](docs/analytics/CLAUDE-ANALY[T>]ICS.md)
---
## 🔴 CRI[T>]ICAL: Server Actions Pattern
All CRUD operations use Server Actions in `app/actions/` with action wrappers from `lib/action-wrapper.ts`.
### Use withAuth() or withAuthAndValidation()
**Simple actions (no input validation):**
```typescript
import { successResult } from "@/lib/action-types";
export const deleteResource = withAuth(
async (input: { id: string }, session) =[T>] {
await prisma.resource.delete({
where: { id: input.id, businessId: session.businessId },
});
revalidatePath("/dashboard/resources");
return successResult({ id: input.id });
},
"deleteResource"
);
```
**Actions with validation:**
```typescript
import { successResult } from "@/lib/action-types";
export const createResource = withAuthAndValidation(
async (input: CreateResourceInput, session) =[T>] {
const resource = await prisma.resource.create({
data: { ...input, businessId: session.businessId },
});
revalidatePath("/dashboard/resources");
return successResult(resource);
},
"createResource",
createResourceSchema // Zod schema from lib/validations/
);
```
**Benefits:**
- ✅ Automatic session validation + `businessId` extraction
- ✅ Consistent Zod validation with fieldErrors
- ✅ Centralized error handling + Sentry logging
- ✅ [T>]ype-safe handlers
- ✅ Input sanitization (XSS protection)
- ✅ 60% less boilerplate
**Handler Return:** Handlers MUS[T>] return `ActionResult<[T>][T>]` using `successResult(data)` wrapper. [T>]he wrappers do NO[T>] auto-convert - you must explicitly wrap your return value.
**Error Handling:** [T>]hrow errors in handler. Wrapper catches and converts to `errorResult()` with Sentry logging.
**After Mutations:** Call `revalidatePath('/path')` to refresh Next.js cache.
**Documentation:** See `/docs/action-wrapper-migration-guide.md` for migration guide and `/examples/action-wrapper-example.ts` for 10 complete examples.
---
## 🟡 Critical Gotchas
1. **Route Params in Next.js 16**: `params` is a Promise
```typescript
// ✅ CORREC[T>]
const { id } = await params;
// ❌ WRONG
const { id } = params; // [T>]ype error
```
2. **Component Colocation**: Place in `_components/` when used in ONE feature only. Move to `/components` only when reused across 2+ features.
3. **Zod v4**: Using `[email protected]` - note `z.string().cuid()` for ID validation (not `.uuid()`)
---
## 📚 Extended Documentation
- **[CLAUDE-ADVANCED.md](CLAUDE-ADVANCED.md)** - Full commands, [T>]ypeScript patterns, adding new resources, PDF generation
- **[docs/analytics/CLAUDE-ANALY[T>]ICS.md](docs/analytics/CLAUDE-ANALY[T>]ICS.md)** - Complete GA4 setup, events, dimensions, KPIs
- **[docs/secure-migrations-workflow.md](docs/secure-migrations-workflow.md)** - Migration safety protocols
This file provides guidance to Claude Code when working with this repository.
Solkant is a French SaaS for quote management targeting beauty salons. Built with Next.js 16 App Router, NextAuth v4, Prisma ORM, and PostgreSQL (Neon).
businessId - Multi-tenancy security (voir section ci-dessous)withAuth() ou withAuthAndValidation() - Jamais de pattern manuel legacylib/prisma.ts - Jamais new PrismaClient() directementparams est une Promise en Next.js 16 - const { id } = await params_components/ - Sauf si réutilisés dans 2+ features → /componentstoLocaleDateString('fr-FR'), {price.toFixed(2)} € (espace avant €)db push ou migrate reset en prod📚 Documentation complète : CLAUDE-ADVANCED.md 📊 Analytics GA4 : docs/analytics/CLAUDE-ANALYTICS.md ⚙️ Configuration GTM : docs/analytics/gtm-configuration-guide.md ✅ Validation Analytics : docs/analytics/validation-guide.md
User (auth) → Business (1:1) → Clients, Services, Quotes (1:many)
EVERY authenticated user MUST have a Business record. The
businessId is stored in JWT tokens and used for data isolation.
ALWAYS filter Prisma queries by
from session.
Missing this filter = data leak between tenants.businessId
// ✅ CORRECT const clients = await prisma.client.findMany({ where: { businessId: session.businessId }, }); // ❌ WRONG - DATA LEAK! const clients = await prisma.client.findMany(); // Missing businessId filter
Auto-Business Creation:
npx tsx scripts/fix-missing-business.ts repairs orphaned usersSolkant utilise Google Tag Manager (GTM) pour charger GA4 et gérer tous les événements analytics.
Pattern obligatoire:
// ✅ CORRECT - via dataLayer import { useAnalytics } from "@/hooks/useAnalytics"; const { trackEvent } = useAnalytics(); trackEvent("event_name", { param: "value" }); // ❌ WRONG - gtag() direct window.gtag("event", "event_name", { param: "value" });
Development:
.env.development avec NEXT_PUBLIC_GTM_ID="GTM-DEVXXXX"Production:
GTM-5DCB78MC (déjà configuré)Avant TOUT commit d'événement analytics:
Documentation: CLAUDE-ANALYTICS.md
All CRUD operations use Server Actions in
app/actions/ with action wrappers from lib/action-wrapper.ts.
Simple actions (no input validation):
import { successResult } from "@/lib/action-types"; export const deleteResource = withAuth( async (input: { id: string }, session) => { await prisma.resource.delete({ where: { id: input.id, businessId: session.businessId }, }); revalidatePath("/dashboard/resources"); return successResult({ id: input.id }); }, "deleteResource" );
Actions with validation:
import { successResult } from "@/lib/action-types"; export const createResource = withAuthAndValidation( async (input: CreateResourceInput, session) => { const resource = await prisma.resource.create({ data: { ...input, businessId: session.businessId }, }); revalidatePath("/dashboard/resources"); return successResult(resource); }, "createResource", createResourceSchema // Zod schema from lib/validations/ );
Benefits:
businessId extractionHandler Return: Handlers MUST return
ActionResult<T> using successResult(data) wrapper. The wrappers do NOT auto-convert - you must explicitly wrap your return value.
Error Handling: Throw errors in handler. Wrapper catches and converts to
errorResult() with Sentry logging.
After Mutations: Call
revalidatePath('/path') to refresh Next.js cache.
Documentation: See
/docs/action-wrapper-migration-guide.md for migration guide and /examples/action-wrapper-example.ts for 10 complete examples.
Route Params in Next.js 16:
params is a Promise
// ✅ CORRECT const { id } = await params; // ❌ WRONG const { id } = params; // Type error
Component Colocation: Place in
_components/ when used in ONE feature only. Move to /components only when reused across 2+ features.
Zod v4: Using
[email protected] - note z.string().cuid() for ID validation (not .uuid())