Imported from ak-softwares/wa-api (
AGENTS.md). Install upstream withnpx skills add ak-softwares/wa-api. Copyright stays with the author.
AGENTS.md
WhatsApp Business API SaaS (wa-api.me). Next.js 16 App Router, React 19, MongoDB/Mongoose, NextAuth JWT, Tailwind 4 + shadcn/Radix.
There is no Prisma. Path alias is @/ → src/. The folder src/utiles/ is an existing misspelling — use it; do not rename.
Run
npm run dev # turbopack
npm run build
npm run lint
Env lives in .env.local (local) and .env.production (deploy). Never commit secrets.
Source layout
src/
app/
(app)/ # public site + user dashboard (root layout with html/body)
(public)/ # landing, auth, docs, pricing, legal
dashboard/ # authenticated app
(admin)/ # separate root layout; /admin
api/ # REST route handlers
components/
admin/ # admin UI
dashboard/ # user dashboard UI
global/ # marketing header/footer
ui/ # shadcn primitives
common/ emails/ providers/
models/ # Mongoose schemas
services/ # business logic (WhatsApp, AI, webhooks, billing)
hooks/ # client data hooks
lib/ # db, auth helpers, redis, queues, crypto
types/ schemas/ store/ config/ context/ utiles/
App routes
| URL | Role |
|---|---|
/ |
Marketing |
/auth/login |
Phone OTP + Google (primary) |
/auth/signin |
Email/password |
/dashboard |
User home |
/dashboard/chats, /contacts, /templates, /ai, /settings, /profile |
Split panes via parallel routes @list / @details |
/dashboard/billing |
Subscriptions (Razorpay) |
/dashboard/setup |
WhatsApp Embedded Signup |
/admin |
Overview (admins only) |
/admin/users, /admin/users/[id], /admin/wa-accounts, /admin/billing |
Admin |
(app) and (admin) are two root layouts. Do not share html/body between them.
API groups (src/app/api/)
auth/— NextAuth, OTP, signup, password reset, temp setup tokenswa-accounts/— chats, messages, contacts, templates, media, broadcast, analyticsfacebook/— WABA onboardingsubscription/+razorpay/— plans and Razorpay subscriptions (create-subscription, not one-off orders)admin/— gated byrequireAdminApi().admin/statsis the all-time snapshot;admin/analytics?range=returns date-bucketed series plus growth ratesai/,tools/,api-token/,oauth/,users/me,webhooks/
API JSON shape: { success, message, data?, pagination? } (src/types/apiResponse.ts).
Data (MongoDB)
Models in src/models/: User, WaAccount, Chat, Message, Contact, Template, Subscription, SubscriptionUses, PaymentHistory, MonthlyUsage, AIAssistant, AiUsage, Tool, ApiToken, PushDevice.
Auth
- NextAuth JWT:
src/app/api/auth/[...nextauth]/authOptions.ts - Session:
id,email,role - User:
role(user|admin),status(active|suspended) - Admins:
ADMIN_EMAILS(comma-separated) only. Bootstrap on login viasrc/lib/auth/resolveAuthUser.ts. The admin panel cannot grant or revokerole—PATCH /api/admin/users/[id]acceptsstatusonly - Pages:
requireAdminPage()in(admin)/layout.tsx(redirect) - APIs:
requireAdminApi()insrc/lib/auth/requireAdmin.ts - Most user APIs:
fetchAuthenticatedUser()insrc/services/apiHelper/getDefaultWaAccount.ts(Bearer JWT, API token, NextAuth session, or setup temp token) - Suspended users cannot sign in
Building a feature
Create layers in this order. Do not skip to the page.
- Model — Mongoose schema in
src/models/. - Types — client-safe TypeScript types in
src/types/(no Mongoose). Mirror them in API responses. - API — thin
src/app/api/.../route.ts. Auth withfetchAuthenticatedUser()orrequireAdminApi(). ReturnApiResponse. - Hook —
src/hooks/...fetches the API, holdsloading/ data / errors, toasts viashowToast. - Page / component —
src/components/...plus a thinsrc/app/.../page.tsx. Pages stay presentational.
Loading
While data is loading, show skeletons (Skeleton from @/components/ui/skeleton), not a blank screen or a spinner-only page. Match the layout of the loaded UI (cards, table rows, list items). See MetricCardSkeleton, StatCard, admin tables.
Charts
Charts use recharts. Wrap every chart in ChartFrame (src/components/admin/charts/), which handles the card, skeleton, and empty state. Colors and axis defaults live in chartConfig.ts.
Date ranges on admin screens go through AdminRangeFilter + useAdminAnalytics, default preset lifetime. Range parsing and bucketing (daily up to 100 days, monthly beyond) live in src/lib/admin/analytics.ts.
Money
price on Subscription and PaymentHistory is stored in the smallest currency unit (paise/cents), as Razorpay returns it. Render it with formatMinorMoney(); formatMoney() expects major units.
Pagination
If a list can grow (users, chats, contacts, payments, etc.), paginate it. Do not dump unbounded arrays to the client.
- API:
page,perPage(cap, typically 20–100). Returnpagination: { page, perPage, total, totalPages }onApiResponse. - Admin helpers:
parsePagination()insrc/lib/admin/query.ts. UI:AdminPagination. - Dashboard lists:
ITEMS_PER_PAGEfromsrc/utiles/constans/apiConstans.ts; hooks often use page +hasMore(load more). - Facebook-style APIs may use cursor pagination (
pagination.cursors/next) instead.
Conventions
- Keep business logic in
services/; route handlers stay thin. - Plan quotas are per month (
messagesPerMonth). A yearly subscription's billing period is split into monthly usage windows byresolveUsagePeriod()insrc/services/subscription/usageService.ts— never usesubscription.currentStart/currentEnddirectly as the quota window. - Dashboard UI in
src/components/dashboard/; admin UI insrc/components/admin/. - Client toasts:
showToastfrom@/components/ui/sonner. - Do not import Mongoose models into
"use client"files. - Encrypted WaAccount fields (
permanent_token,business_id) must never be returned to admin or client UIs. - Razorpay subscription webhooks:
/api/webhooks/razorpay-subscription./api/webhooks/razorpayis a signature-only stub (old wallet payments). - Prefer existing patterns (hooks +
fetchto/api/...) over new frameworks.
Stack notes
MongoDB, Redis, BullMQ, Pusher, Razorpay, Resend, OpenAI / Vercel AI SDK, Meta Cloud API. UI: Tailwind, DaisyUI leftovers, Radix/shadcn, Framer Motion, Zustand.