A focused cleanup PR that addresses the high-signal findings from a self-review of PRs #148-153. Every change is local to the templates surface; no schema or wire-format changes. Safety ------ * Delete now goes through a confirmation Dialog (mirrors the existing tag-manager pattern). Copy explicitly tells the user when Meta will be hit, so a slip on the trash icon doesn't take the template off Meta as well as locally. * AUTHENTICATION submit is now blocked by a persistent in-dialog banner + disabled submit button. The disappearing toast that used to fire on submit is gone — defensive `return` remains in handleSubmit as a second line of defense. * updateButton uses a per-variant switch + ButtonPatch union instead of the partial-spread + `as TemplateButton` cast. Each variant's spread keeps its discriminant pinned; TS enforces exhaustiveness. * New template-row-guard.ts surfaces malformed Supabase rows at the route boundary instead of letting buildSendComponents crash deep in the stack with an opaque TypeError. * UUID regex check on /[id] PATCH+DELETE so non-UUID payloads return a clean 400 before hitting Supabase. Submit-route cleanup -------------------- * Two near-identical upsert blocks (error path + success path) collapsed into a buildUpsertRow + upsertTemplateRow helper. Adding a column now touches one place, not two. UX copy + feedback ------------------ * Submit success toast sets the right expectation: "typical review time is 24 hours. Status updates automatically." Old copy just said "status will update once approved." * Header media URL helper text is now actually useful: HTTPS requirement, ~24h lifetime, per-format size/format recommendations (JPEG/PNG ≥800×418 ≤5 MB for images, MP4 ≤16 MB for video, etc). * Truncated-sync toast switches from `warning` (which auto-dismisses fast) to `error` with `duration: 10000` and concrete copy: "Synced the first 2000 templates only — your account has more. Sync again to continue." * Edit and Resubmit buttons gain visible text labels alongside their icons + better aria-label/title pairs. A lone rotate icon wasn't self-explanatory. * Broadcast picker drops the redundant "Approved" chip — every template in that list is APPROVED by filter, so the chip carried zero information. Unused templateStatusConfig import removed. State + a11y ------------ * handleSubmit now awaits fetchTemplates BEFORE closing the dialog so re-opening immediately doesn't see a stale list. * Header content / media URL / sample are preserved across format switches in the New Template form. The payload builder is already format-aware, so orphan values on hidden fields are harmless — and keeping them lets users switch formats to compare without losing typing. * Button-type Select gains the same null-guard the header Select got in PR #148 (per @base-ui semantics, onValueChange can fire with null on deselect). * Dynamic body-sample inputs get id + aria-label so each one is individually addressable (WCAG A — screen-reader announcement beat "edit text"). Header text + header sample inputs too. Deliberately NOT here --------------------- * The 940-line template-manager component is not split. It's cohesive (one feature surface) and splitting for size alone would be over-engineering. * extractVariableIndices in the buttons loop is not memoized. Regex on a short URL string is sub-microsecond; a real profiler would never flag it. * No JS heap secret-clearing for access tokens. Strings are immutable in JS — there's no way to actually clear one. Tests: 256/256 pass (no new tests required — pure refactor + copy + new UI elements). Lint 0 errors. Typecheck clean. Build succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .github | ||
| public | ||
| src | ||
| supabase/migrations | ||
| .editorconfig | ||
| .env.local.example | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| components.json | ||
| CONTRIBUTING.md | ||
| eslint.config.mjs | ||
| LICENSE | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
wacrm — CRM Template for WhatsApp
Self-hostable CRM template for WhatsApp® — shared inbox, contacts, sales pipelines, broadcasts, and no-code automations. Fork it, brand it, host it.
The marketing site and self-host docs live in a separate repo: ArnasDon/wacrm-site (wacrm.tech). This repo is the product — clone or fork it to run your own CRM.
What you get out of the box
- Shared inbox on the official WhatsApp Business API — multiple agents working one number, per-conversation assignment, status, and notes.
- Contacts + tags + custom fields, CSV import, deduplication.
- Sales pipelines (Kanban) with deals linked to conversations.
- Broadcasts with Meta-approved templates, delivery + read tracking, per-recipient variable substitution.
- No-code automations — triggers on inbound messages, new contacts, keywords, or schedule; conditional branches, waits, tags, webhooks. Visual builder.
- Real-time dashboard — response times, daily volume, pipeline value, cross-module activity feed.
- Account management — email, password, avatar, global sign-out.
Why fork this?
This is a template, not a product. Forking means you get:
- Full ownership — your code, your Supabase project, your domain, your data. No SaaS lock-in, no seat pricing, no trust dance.
- Full customisation — add the fields your team needs, remove the modules you don't, redesign anything. The stack is boring on purpose (Next.js + Supabase + Tailwind) so the learning curve is short.
- Zero ops to start — Hostinger Managed Node.js deploys a fork in a few clicks. No Docker, no Kubernetes, no infra team needed.
- Real security primitives — token encryption (AES-256-GCM), RLS on every table, HMAC-verified webhooks, CSP, rate limiting, CI typecheck/build on every PR.
Not a framework. Not an SDK. A concrete, working CRM you can stand up in an afternoon and make yours.
Quick start
# Fork on GitHub first: https://github.com/ArnasDon/wacrm → Fork
git clone https://github.com/<your-username>/wacrm.git
cd wacrm
npm install
cp .env.local.example .env.local # fill in Supabase + Meta creds
npm run dev
Open http://localhost:3000. You'll be redirected to /login (or
/dashboard if already signed in).
Documentation
Full self-host documentation — Supabase migrations, WhatsApp Business API config, and production deploy — lives at wacrm.tech/docs (source: ArnasDon/wacrm-site).
Key pages:
- Getting started
- Supabase setup
- WhatsApp setup
- Environment variables
- Deploy on Hostinger
- Architecture
- Troubleshooting
Stack
- App — Next.js 16 (App Router), React 19, TypeScript, Tailwind v4.
- Data — Supabase (Postgres + Auth + Storage + RLS).
- WhatsApp — Meta Cloud API (official WhatsApp Business API).
Contributing
This is a template, not a collaborative product — the expected flow is
fork → customise → deploy, not upstream contribution. Bug reports
and security issues are welcome; feature PRs often belong in your fork
rather than here. Details in
CONTRIBUTING.md and
.github/SECURITY.md.
License
MIT. Fork it, brand it, host it.