Merge remote-tracking branch 'origin/main' into chore/whatsapp-trademark-symbol

# Conflicts:
#	README.md
#	docs/README.md
#	docs/automations-and-cron.md
#	docs/supabase-setup.md
#	docs/whatsapp-setup.md
#	src/app/opengraph-image.tsx
#	src/components/landing/hero.tsx
#	src/lib/seo/site-config.ts
This commit is contained in:
Arnas Donauskas 2026-05-06 13:50:03 +03:00
commit 1f2849df75
No known key found for this signature in database
90 changed files with 370 additions and 342 deletions

View File

@ -11,8 +11,8 @@ that standard.
If you witness or experience behaviour that violates the Code, please
report it privately to the project maintainer:
- Email: **a.donauskas@hostinger.com** with `[WaCRM conduct]` in the
subject.
- Email: **a.donauskas@hostinger.com** with `[CRM template conduct]` in
the subject.
Reports are handled confidentially. Expect an acknowledgement within
72 hours and a decision on next steps within a week.

View File

@ -1,5 +1,5 @@
name: Bug report
description: Something in WaCRM isn't working the way the docs say it should.
description: Something in the template isn't working the way the docs say it should.
title: "[bug] "
labels: ["bug", "triage"]
body:

View File

@ -6,6 +6,6 @@ contact_links:
- name: Setup / "how do I..." questions
url: https://github.com/ArnasDon/wacrm/blob/main/docs/README.md
about: Check the docs first — setup, deploy, troubleshooting are all covered.
- name: Using WaCRM as a template (forking)
- name: Using this as a template (forking)
url: https://github.com/ArnasDon/wacrm/blob/main/CONTRIBUTING.md
about: WaCRM is a template. Most changes belong in your fork, not an upstream issue — here's how that works.
about: This is a template. Most changes belong in your fork, not an upstream issue — here's how that works.

View File

@ -8,7 +8,7 @@ body:
value: |
Thanks for the idea — but read this first.
WaCRM is a **template**, not a collaborative product. The
This is a **template**, not a collaborative product. The
upstream scope is intentionally narrow, so most feature
requests end up as *"build this in your fork"* rather than
landing here. That's the point of a template.
@ -29,7 +29,7 @@ body:
id: problem
attributes:
label: What's the problem?
description: What are you trying to do today that WaCRM makes harder than it should?
description: What are you trying to do today that the template makes harder than it should?
placeholder: |
When a broadcast ends I have no way to see which recipients
didn't reply so I can follow up manually.

6
.github/SECURITY.md vendored
View File

@ -1,6 +1,6 @@
# Security Policy
Thanks for taking the time to look into WaCRM's security.
Thanks for taking the time to look into the security of this template.
## Reporting a vulnerability
@ -12,7 +12,7 @@ Instead, please report privately via one of:
- [GitHub Security Advisories](https://github.com/ArnasDon/wacrm/security/advisories/new)
(preferred — keeps the disclosure, fix, and CVE all in one place).
- Email: `a.donauskas@hostinger.com` with `[WaCRM security]` in the subject.
- Email: `a.donauskas@hostinger.com` with `[CRM template security]` in the subject.
Include, if you can:
@ -59,4 +59,4 @@ action against anyone who:
- Gives us reasonable time to respond before any public disclosure.
- Doesn't exploit the issue beyond what's necessary to demonstrate it.
Thanks for helping keep WaCRM (and its forks) safe.
Thanks for helping keep this template (and its forks) safe.

View File

@ -1,5 +1,5 @@
<!--
Heads up: WaCRM is a template, not a collaborative product. Most
Heads up: this is a template, not a collaborative product. Most
changes belong in your fork. See CONTRIBUTING.md for which kinds of
upstream PRs tend to land (security, correctness, docs) vs. which
belong in a fork (new features, stack swaps, opinionated refactors).

View File

@ -1,6 +1,6 @@
# Using WaCRM
# Using this template
WaCRM is a **template repository**, not a collaborative product. The
This is a **template repository**, not a collaborative product. The
expected flow is:
1. **Fork** it to your own GitHub account or organisation.
@ -95,9 +95,9 @@ closed — open the issue first to align.
## If you maintain a public fork
- Rebrand. The WaCRM name, favicon, and `wacrm.tech` URL belong to the
upstream project; please swap them for your own before putting your
deployment in front of users.
- Rebrand. The "CRM Template for WhatsApp" name, favicon, and
`wacrm.tech` URL belong to the upstream project; please swap them
for your own before putting your deployment in front of users.
- Keep the MIT [`LICENSE`](./LICENSE) file — that's how the template's
permissions travel with the code. Attribution in a `README` section
is appreciated but not required.

View File

@ -1,10 +1,10 @@
# WaCRM
# CRM Template for WhatsApp
> Self-hostable WhatsApp® CRM template — shared inbox, contacts, sales
> pipelines, broadcasts, and no-code automations. Fork it, brand it,
> host it.
> Self-hostable CRM template for WhatsApp® — shared inbox, contacts,
> sales pipelines, broadcasts, and no-code automations. Fork it, brand
> it, host it.
[![License: MIT](https://img.shields.io/badge/License-MIT-emerald.svg)](./LICENSE)
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](./LICENSE)
[![CI](https://github.com/ArnasDon/wacrm/actions/workflows/ci.yml/badge.svg)](https://github.com/ArnasDon/wacrm/actions/workflows/ci.yml)
[![Next.js 16](https://img.shields.io/badge/Next.js-16-black?logo=nextdotjs)](https://nextjs.org)
[![Supabase](https://img.shields.io/badge/Supabase-Postgres%20%2B%20Auth-3ecf8e?logo=supabase)](https://supabase.com)
@ -28,7 +28,7 @@
## Why fork this?
WaCRM is a **template**, not a product. Forking means you get:
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.

View File

@ -1,7 +1,7 @@
# WaCRM documentation
# Documentation
WaCRM is a self-hostable WhatsApp® CRM built on Next.js and Supabase. These
docs walk you from a freshly forked repo to a production deploy.
A self-hostable CRM template for WhatsApp® built on Next.js and Supabase.
These docs walk you from a freshly forked repo to a production deploy.
## Reading order

View File

@ -1,6 +1,6 @@
# Automations cron
WaCRM's **Automations** module lets you build flows that react to WhatsApp®
The **Automations** module lets you build flows that react to WhatsApp®
events (new message, new contact, keyword match, schedule, etc.). Most
steps run inline — the exception is the **Wait** step, which parks the
execution in `automation_pending_executions` until its due time.

View File

@ -1,7 +1,7 @@
# Deploy on Hostinger
Hostinger's **[Managed Node.js Hosting](https://www.hostinger.com/web-apps-hosting)**
is the recommended way to run WaCRM in production: Hostinger patches the
is the recommended way to run this template in production: Hostinger patches the
OS and the Node runtime, handles SSL, and gives you a Git-based deploy
flow from hPanel. You deploy from your fork without ever opening an SSH
session if you don't want to.
@ -93,7 +93,7 @@ baked into the client bundle.
From the app page, click **Restart application** (or run the equivalent
from the terminal). hPanel boots `npm start` behind its own reverse proxy
on the domain you configured. Hit the URL in a browser — you should land
on the WaCRM marketing page.
on the marketing page.
SSL is provisioned automatically. If you used a subdomain, Hostinger's
AutoSSL usually takes a minute or two; until then the site may serve the
@ -139,7 +139,7 @@ idempotent.
## When to reach for a VPS instead
Managed Node.js covers most WaCRM deploys. Consider
Managed Node.js covers most deploys of this template. Consider
[Hostinger VPS](https://www.hostinger.com/vps-hosting) if you need:
- Long-running background workers beyond what the single Next.js process

View File

@ -1,6 +1,6 @@
# Getting started
Run WaCRM locally in about 15 minutes. This guide assumes Node.js 20+ and
Run the template locally in about 15 minutes. This guide assumes Node.js 20+ and
npm are already installed.
## 1. Fork and clone

View File

@ -1,8 +1,7 @@
# Supabase setup
WaCRM uses Supabase for Postgres, authentication, row-level security (RLS),
and (optionally) storage. You need one Supabase project per WaCRM
deployment.
This template uses Supabase for Postgres, authentication, row-level security (RLS),
and (optionally) storage. You need one Supabase project per deployment.
## 1. Create the project
@ -75,7 +74,7 @@ emails link back correctly.
## 6. (Optional) Storage
WaCRM downloads WhatsApp® media through Meta's `/download` endpoint and
The app downloads WhatsApp® media through Meta's `/download` endpoint and
currently relays it on demand rather than caching it in Supabase Storage.
No bucket setup is required for a default install.

View File

@ -46,7 +46,7 @@ Set `NEXT_PUBLIC_SITE_URL` **and** add the same origin to Supabase →
### Webhook verification fails in Meta
- The **verify token** in Meta must match exactly what you saved in
**Settings → WhatsApp** inside WaCRM.
**Settings → WhatsApp** inside the app.
- The callback URL must be reachable without auth. Test it yourself:
`curl 'https://crm.example.com/api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=test'`
should return `test`.
@ -91,7 +91,7 @@ endpoint is fine and the problem is just the schedule.
### An automation fires twice for the same message
Most likely two WaCRM instances share one Supabase project and both
Most likely two app instances share one Supabase project and both
receive the same webhook. The webhook handler deduplicates by Meta's
`wamid`, but only within a single deploy — split-brain setups can double
up. Either consolidate to one deploy or route the webhook to exactly one.

View File

@ -1,6 +1,6 @@
# WhatsApp setup
WaCRM talks to the official **WhatsApp® Business Cloud API** (Meta). Each
This template talks to the official **WhatsApp® Business Cloud API** (Meta). Each
account stores its own `phone_number_id`, `waba_id`, and encrypted
`access_token` — there is no shared app-level token. This page walks
through getting those values and wiring the webhook.
@ -45,9 +45,9 @@ generate a **System User** access token:
- `whatsapp_business_messaging`
4. Copy the token. **This is shown only once.**
## 4. Connect it inside WaCRM
## 4. Connect it inside the app
1. Run WaCRM locally (or open your deployed instance).
1. Run the app locally (or open your deployed instance).
2. Sign in, then go to **Settings → WhatsApp**.
3. Fill in:
- **Phone number ID** — from step 3.
@ -55,7 +55,7 @@ generate a **System User** access token:
- **Access token** — the System User token from step 3.
- **Verify token** — any random string you make up; you will paste the
same value into Meta in the next step.
4. Save. WaCRM encrypts the access token and verify token with your
4. Save. The app encrypts the access token and verify token with your
`ENCRYPTION_KEY` before writing them to Supabase.
## 5. Configure the webhook in Meta
@ -68,7 +68,7 @@ Under **WhatsApp → Configuration → Webhook**:
- Production: `https://<your-domain>/api/whatsapp/webhook`.
- **Verify token**: the same string you entered in step 4.
- Click **Verify and save**. Meta sends a GET with
`hub.challenge` — WaCRM's webhook route decrypts stored verify tokens
`hub.challenge` — the app's webhook route decrypts stored verify tokens
and echoes the challenge back. If verification fails, double-check the
verify token matches and the callback URL is publicly reachable.
@ -86,7 +86,7 @@ sync on the broadcast UI.
## 6. Signature verification (required)
Set `META_APP_SECRET` to your app's **App Secret** (Meta for Developers →
App Settings → Basic). WaCRM validates the `X-Hub-Signature-256` HMAC
App Settings → Basic). The app validates the `X-Hub-Signature-256` HMAC
on every inbound webhook and **rejects every request if the env var is
not set** — anyone who knew the webhook URL could otherwise spoof
messages and status updates. Configure it before pointing the webhook
@ -96,9 +96,8 @@ at a deployed instance.
1. From Meta's **API Setup** page, send a test message to a number that is
a registered tester.
2. Watch the WaCRM Inbox — the message should appear within a second or
two.
3. Reply from WaCRM; the recipient gets a real WhatsApp message.
2. Watch the Inbox — the message should appear within a second or two.
3. Reply from the app; the recipient gets a real WhatsApp message.
If nothing shows up, see [troubleshooting.md](./troubleshooting.md).

View File

@ -2,7 +2,7 @@
"name": "wacrm",
"version": "0.1.0",
"private": true,
"description": "Self-hostable WhatsApp CRM built on Next.js and Supabase — shared inbox, contacts, sales pipelines, broadcasts, and no-code automations.",
"description": "Self-hostable CRM template for WhatsApp built on Next.js and Supabase — shared inbox, contacts, sales pipelines, broadcasts, and no-code automations.",
"license": "MIT",
"author": "Arnas Donauskas",
"homepage": "https://wacrm.tech",

View File

@ -46,8 +46,8 @@ export default function ForgotPasswordPage() {
<div className="flex min-h-screen items-center justify-center bg-slate-950 px-4">
<Card className="w-full max-w-md border-slate-800 bg-slate-900">
<CardHeader className="items-center text-center">
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<CheckCircle className="h-6 w-6 text-emerald-500" />
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<CheckCircle className="h-6 w-6 text-violet-500" />
</div>
<CardTitle className="text-xl text-white">
Check your email
@ -77,8 +77,8 @@ export default function ForgotPasswordPage() {
<div className="flex min-h-screen items-center justify-center bg-slate-950 px-4">
<Card className="w-full max-w-md border-slate-800 bg-slate-900">
<CardHeader className="items-center text-center">
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<MessageSquare className="h-6 w-6 text-emerald-500" />
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<MessageSquare className="h-6 w-6 text-violet-500" />
</div>
<CardTitle className="text-xl text-white">Reset password</CardTitle>
<CardDescription className="text-slate-400">
@ -104,14 +104,14 @@ export default function ForgotPasswordPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
<Button
type="submit"
disabled={loading}
className="mt-2 h-10 w-full bg-emerald-600 text-white hover:bg-emerald-500 disabled:opacity-50"
className="mt-2 h-10 w-full bg-violet-600 text-white hover:bg-violet-500 disabled:opacity-50"
>
{loading ? "Sending..." : "Send reset link"}
</Button>

View File

@ -47,12 +47,12 @@ export default function LoginPage() {
<div className="flex min-h-screen items-center justify-center bg-slate-950 px-4">
<Card className="w-full max-w-md border-slate-800 bg-slate-900">
<CardHeader className="items-center text-center">
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<MessageSquare className="h-6 w-6 text-emerald-500" />
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<MessageSquare className="h-6 w-6 text-violet-500" />
</div>
<CardTitle className="text-xl text-white">Welcome back</CardTitle>
<CardDescription className="text-slate-400">
Sign in to your WaCRM account
Sign in to your account
</CardDescription>
</CardHeader>
<CardContent>
@ -74,7 +74,7 @@ export default function LoginPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
@ -85,7 +85,7 @@ export default function LoginPage() {
</Label>
<Link
href="/forgot-password"
className="text-sm text-emerald-500 hover:text-emerald-400"
className="text-sm text-violet-500 hover:text-violet-400"
>
Forgot password?
</Link>
@ -97,14 +97,14 @@ export default function LoginPage() {
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
<Button
type="submit"
disabled={loading}
className="mt-2 h-10 w-full bg-emerald-600 text-white hover:bg-emerald-500 disabled:opacity-50"
className="mt-2 h-10 w-full bg-violet-600 text-white hover:bg-violet-500 disabled:opacity-50"
>
{loading ? "Signing in..." : "Sign in"}
</Button>
@ -114,7 +114,7 @@ export default function LoginPage() {
Don&apos;t have an account?{" "}
<Link
href="/signup"
className="text-emerald-500 hover:text-emerald-400"
className="text-violet-500 hover:text-violet-400"
>
Create account
</Link>

View File

@ -66,8 +66,8 @@ export default function SignupPage() {
<div className="flex min-h-screen items-center justify-center bg-slate-950 px-4">
<Card className="w-full max-w-md border-slate-800 bg-slate-900">
<CardHeader className="items-center text-center">
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<CheckCircle className="h-6 w-6 text-emerald-500" />
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<CheckCircle className="h-6 w-6 text-violet-500" />
</div>
<CardTitle className="text-xl text-white">
Check your email
@ -97,12 +97,12 @@ export default function SignupPage() {
<div className="flex min-h-screen items-center justify-center bg-slate-950 px-4">
<Card className="w-full max-w-md border-slate-800 bg-slate-900">
<CardHeader className="items-center text-center">
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<MessageSquare className="h-6 w-6 text-emerald-500" />
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<MessageSquare className="h-6 w-6 text-violet-500" />
</div>
<CardTitle className="text-xl text-white">Create account</CardTitle>
<CardDescription className="text-slate-400">
Get started with WaCRM
Get started with CRM Template for WhatsApp
</CardDescription>
</CardHeader>
<CardContent>
@ -124,7 +124,7 @@ export default function SignupPage() {
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
@ -139,7 +139,7 @@ export default function SignupPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
@ -154,7 +154,7 @@ export default function SignupPage() {
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
@ -169,14 +169,14 @@ export default function SignupPage() {
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-emerald-500 focus-visible:ring-emerald-500/20"
className="border-slate-700 bg-slate-800 text-white placeholder:text-slate-500 focus-visible:border-violet-500 focus-visible:ring-violet-500/20"
/>
</div>
<Button
type="submit"
disabled={loading}
className="mt-2 h-10 w-full bg-emerald-600 text-white hover:bg-emerald-500 disabled:opacity-50"
className="mt-2 h-10 w-full bg-violet-600 text-white hover:bg-violet-500 disabled:opacity-50"
>
{loading ? "Creating account..." : "Create account"}
</Button>
@ -186,7 +186,7 @@ export default function SignupPage() {
Already have an account?{" "}
<Link
href="/login"
className="text-emerald-500 hover:text-emerald-400"
className="text-violet-500 hover:text-violet-400"
>
Sign in
</Link>

View File

@ -54,7 +54,7 @@ export default function EditAutomationPage({
<p className="text-sm text-red-400">{error}</p>
<button
onClick={() => router.push("/automations")}
className="text-sm text-emerald-400 hover:text-emerald-300"
className="text-sm text-violet-400 hover:text-violet-300"
>
Back to Automations
</button>
@ -65,7 +65,7 @@ export default function EditAutomationPage({
if (!initial) {
return (
<div className="flex h-screen items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
)
}

View File

@ -76,7 +76,7 @@ export default function AutomationLogsPage({
if (!automation || logs === null) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
)
}
@ -167,7 +167,7 @@ export default function AutomationLogsPage({
function StatusBadge({ status }: { status: AutomationLog["status"] }) {
const classes =
status === "success"
? "border-emerald-500/30 bg-emerald-500/10 text-emerald-300"
? "border-violet-500/30 bg-violet-500/10 text-violet-300"
: status === "partial"
? "border-amber-500/30 bg-amber-500/10 text-amber-300"
: "border-red-500/30 bg-red-500/10 text-red-300"
@ -190,7 +190,7 @@ function StepRow({ result }: { result: AutomationLogStepResult }) {
<span
className={cn(
"mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full",
ok ? "bg-emerald-500/20 text-emerald-400" : "bg-red-500/20 text-red-400",
ok ? "bg-violet-500/20 text-violet-400" : "bg-red-500/20 text-red-400",
)}
aria-hidden
>

View File

@ -146,7 +146,7 @@ export default function AutomationsPage() {
if (automations === null) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
)
}
@ -164,7 +164,7 @@ export default function AutomationsPage() {
</div>
<Button
onClick={() => router.push("/automations/new")}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
<Plus className="h-4 w-4" />
Create Automation
@ -182,9 +182,9 @@ export default function AutomationsPage() {
<button
key={slug}
onClick={() => startFromTemplate(slug)}
className="group flex flex-col items-start rounded-xl border border-slate-800 bg-slate-900 p-4 text-left transition-colors hover:border-emerald-500/50 hover:bg-slate-900/80"
className="group flex flex-col items-start rounded-xl border border-slate-800 bg-slate-900 p-4 text-left transition-colors hover:border-violet-500/50 hover:bg-slate-900/80"
>
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-lg bg-emerald-500/10 text-emerald-400 group-hover:bg-emerald-500/15">
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-lg bg-violet-500/10 text-violet-400 group-hover:bg-violet-500/15">
<Icon className="h-5 w-5" />
</div>
<div className="text-sm font-semibold text-white">{t.name}</div>
@ -198,8 +198,8 @@ export default function AutomationsPage() {
{automations.length === 0 ? (
<div className="flex h-48 flex-col items-center justify-center rounded-xl border border-dashed border-slate-800 bg-slate-900/40">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-500/10">
<Zap className="h-6 w-6 text-emerald-500" />
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-violet-500/10">
<Zap className="h-6 w-6 text-violet-500" />
</div>
<p className="mt-3 text-sm font-medium text-white">No automations yet</p>
<p className="mt-1 text-xs text-slate-400">
@ -275,10 +275,10 @@ function AutomationCard({
<li className="rounded-xl border border-slate-800 bg-slate-900 transition-colors hover:border-slate-700">
<div className="flex items-center gap-4 p-4">
<div
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-emerald-500/10"
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-violet-500/10"
aria-hidden
>
<Zap className="h-5 w-5 text-emerald-400" />
<Zap className="h-5 w-5 text-violet-400" />
</div>
<button
@ -292,8 +292,8 @@ function AutomationCard({
</span>
{automation.is_active && (
<span className="relative flex h-2 w-2" aria-label="active">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-violet-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-violet-500" />
</span>
)}
</div>

View File

@ -246,7 +246,7 @@ export default function BroadcastDetailPage() {
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
);
}
@ -265,7 +265,7 @@ export default function BroadcastDetailPage() {
const status = getBroadcastStatus(broadcast.status);
const funnelSteps: FunnelStep[] = [
{ label: 'Sent', value: broadcast.sent_count, color: 'bg-emerald-500' },
{ label: 'Sent', value: broadcast.sent_count, color: 'bg-violet-500' },
{ label: 'Delivered', value: broadcast.delivered_count, color: 'bg-teal-500' },
{ label: 'Read', value: broadcast.read_count, color: 'bg-blue-500' },
{ label: 'Replied', value: broadcast.replied_count, color: 'bg-indigo-500' },
@ -361,7 +361,7 @@ export default function BroadcastDetailPage() {
value={broadcast.sent_count}
total={broadcast.total_recipients}
icon={<Send className="h-4 w-4" />}
color="bg-emerald-500/10 text-emerald-400"
color="bg-violet-500/10 text-violet-400"
/>
<StatCard
label="Delivered"
@ -423,7 +423,7 @@ export default function BroadcastDetailPage() {
<DropdownMenuItem
onClick={() => setStatusFilter('all')}
className={
statusFilter === 'all' ? 'text-emerald-400' : 'text-slate-300'
statusFilter === 'all' ? 'text-violet-400' : 'text-slate-300'
}
>
All statuses
@ -434,7 +434,7 @@ export default function BroadcastDetailPage() {
onClick={() => setStatusFilter(s)}
className={
statusFilter === s
? 'text-emerald-400'
? 'text-violet-400'
: 'text-slate-300'
}
>

View File

@ -140,9 +140,9 @@ export default function NewBroadcastPage() {
<div
className={`flex h-8 w-8 items-center justify-center rounded-full text-xs font-medium transition-all ${
isCompleted
? 'bg-emerald-500 text-white'
? 'bg-violet-500 text-white'
: isActive
? 'border-2 border-emerald-500 bg-emerald-500/10 text-emerald-400'
? 'border-2 border-violet-500 bg-violet-500/10 text-violet-400'
: 'border border-slate-700 bg-slate-800 text-slate-500'
}`}
>
@ -150,7 +150,7 @@ export default function NewBroadcastPage() {
</div>
<span
className={`hidden text-sm font-medium sm:block ${
isActive ? 'text-white' : isCompleted ? 'text-emerald-400' : 'text-slate-500'
isActive ? 'text-white' : isCompleted ? 'text-violet-400' : 'text-slate-500'
}`}
>
{step.label}
@ -159,7 +159,7 @@ export default function NewBroadcastPage() {
{index < steps.length - 1 && (
<div
className={`mx-3 h-px flex-1 ${
index < currentStep ? 'bg-emerald-500' : 'bg-slate-800'
index < currentStep ? 'bg-violet-500' : 'bg-slate-800'
}`}
/>
)}

View File

@ -35,7 +35,7 @@ function RateCell({
}: {
value: number;
total: number;
/** Tailwind bg class for the fill, e.g. "bg-emerald-500" */
/** Tailwind bg class for the fill, e.g. "bg-violet-500" */
color: string;
}) {
const pct = percent(value, total);
@ -128,7 +128,7 @@ export default function BroadcastsPage() {
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
);
}
@ -154,7 +154,7 @@ export default function BroadcastsPage() {
aria-label="Broadcast in progress"
className="broadcast-indeterminate fixed inset-x-0 top-0 z-40 h-0.5 overflow-hidden bg-slate-800"
>
<div className="broadcast-indeterminate-bar h-0.5 bg-emerald-500" />
<div className="broadcast-indeterminate-bar h-0.5 bg-violet-500" />
<style jsx>{`
.broadcast-indeterminate-bar {
width: 33%;
@ -183,7 +183,7 @@ export default function BroadcastsPage() {
</div>
<Button
onClick={() => router.push('/broadcasts/new')}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
<Plus className="h-4 w-4" />
New Broadcast
@ -199,7 +199,7 @@ export default function BroadcastsPage() {
</p>
<Button
onClick={() => router.push('/broadcasts/new')}
className="mt-4 bg-emerald-600 text-white hover:bg-emerald-700"
className="mt-4 bg-violet-600 text-white hover:bg-violet-700"
>
<Plus className="h-4 w-4" />
New Broadcast
@ -243,7 +243,7 @@ export default function BroadcastsPage() {
<RateCell
value={broadcast.delivered_count}
total={broadcast.total_recipients}
color="bg-emerald-500"
color="bg-violet-500"
/>
</TableCell>
<TableCell className="hidden lg:table-cell">

View File

@ -226,7 +226,7 @@ export default function ContactsPage() {
</Button>
<Button
onClick={openAddForm}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
<Plus className="size-4" />
Add Contact
@ -269,7 +269,7 @@ export default function ContactsPage() {
<TableRow className="border-slate-800">
<TableCell colSpan={7} className="text-center py-12">
<div className="flex flex-col items-center gap-2">
<Loader2 className="size-6 animate-spin text-emerald-500" />
<Loader2 className="size-6 animate-spin text-violet-500" />
<p className="text-sm text-slate-500">Loading contacts...</p>
</div>
</TableCell>

View File

@ -29,7 +29,7 @@ function DashboardShellInner({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen items-center justify-center bg-slate-950">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-2 border-violet-500 border-t-transparent" />
<p className="text-sm text-slate-400">Loading...</p>
</div>
</div>

View File

@ -302,7 +302,7 @@ export default function PipelinesPage() {
<DropdownMenuTrigger
className="inline-flex items-center gap-2 rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-white hover:bg-slate-800 transition-colors data-[popup-open]:bg-slate-800"
>
<GitBranch className="h-4 w-4 text-emerald-500" />
<GitBranch className="h-4 w-4 text-violet-500" />
<span className="font-semibold">
{selectedPipeline?.name ?? "Select Pipeline"}
</span>
@ -323,7 +323,7 @@ export default function PipelinesPage() {
onClick={() => setSelectedPipelineId(p.id)}
className={
p.id === selectedPipelineId
? "text-emerald-400"
? "text-violet-400"
: "text-slate-300"
}
>
@ -357,7 +357,7 @@ export default function PipelinesPage() {
<Button
onClick={() => handleAddDeal()}
disabled={!selectedPipelineId || stages.length === 0}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
<Plus className="mr-1 h-4 w-4" />
Add Deal
@ -377,7 +377,7 @@ export default function PipelinesPage() {
</p>
<Button
onClick={() => setNewPipelineOpen(true)}
className="mt-4 bg-emerald-600 text-white hover:bg-emerald-700"
className="mt-4 bg-violet-600 text-white hover:bg-violet-700"
>
<Plus className="mr-1 h-4 w-4" />
Create Pipeline
@ -428,7 +428,7 @@ export default function PipelinesPage() {
<Button
onClick={handleCreatePipeline}
disabled={creating || !newPipelineName.trim()}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
{creating ? "Creating..." : "Create Pipeline"}
</Button>

View File

@ -48,28 +48,28 @@ export default function SettingsPage() {
<TabsList className="bg-slate-900 border border-slate-700">
<TabsTrigger
value="profile"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
<User className="size-4" />
Profile
</TabsTrigger>
<TabsTrigger
value="whatsapp"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
<Settings className="size-4" />
WhatsApp Config
</TabsTrigger>
<TabsTrigger
value="templates"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
<MessageSquare className="size-4" />
Templates
</TabsTrigger>
<TabsTrigger
value="tags"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
<Tag className="size-4" />
Tags

View File

@ -11,11 +11,11 @@ import { decrypt } from '@/lib/whatsapp/encryption'
* template locally, try to broadcast with it, and hit Meta's error
* #132001 "Template name does not exist in the translation" — because
* Meta had never seen the template, or had it approved under a
* different language code than what WaCRM stored.
* different language code than what we stored locally.
*
* This route pulls the source of truth (Meta's approved templates)
* and upserts them into the local catalog by (user_id, name, language).
* After a sync, every template row in WaCRM is guaranteed to match
* After a sync, every local template row is guaranteed to match
* something Meta will actually accept on send.
*
* Scope:

View File

@ -37,7 +37,7 @@ export default async function DocPage({ params }: DocPageProps) {
return (
<article className="mx-auto w-full max-w-3xl">
<p className="text-xs font-semibold uppercase tracking-wider text-emerald-400">
<p className="text-xs font-semibold uppercase tracking-wider text-violet-400">
{doc.section}
</p>
<div

View File

@ -3,9 +3,12 @@ import { DocsShell } from '@/components/docs/shell'
import { listDocs } from '@/lib/docs/content'
export const metadata: Metadata = {
title: { template: '%s — WaCRM Docs', default: 'WaCRM Docs' },
title: {
template: '%s — CRM Template for WhatsApp Docs',
default: 'CRM Template for WhatsApp Docs',
},
description:
'Setup, configuration, and deployment docs for the WaCRM template.',
'Setup, configuration, and deployment docs for the CRM Template for WhatsApp.',
}
export default async function DocsLayout({

View File

@ -12,11 +12,11 @@ export default async function DocsIndexPage() {
return (
<article className="mx-auto w-full max-w-3xl">
<p className="text-xs font-semibold uppercase tracking-wider text-emerald-400">
<p className="text-xs font-semibold uppercase tracking-wider text-violet-400">
Documentation
</p>
<h1 className="mt-2 text-3xl font-bold tracking-tight text-white sm:text-4xl">
Self-host WaCRM end to end
Self-host the CRM template for WhatsApp end to end
</h1>
<p className="mt-4 text-base leading-relaxed text-slate-400">
Everything you need to take the template from a fresh fork to a
@ -45,7 +45,7 @@ export default async function DocsIndexPage() {
{p.description}
</p>
)}
<span className="mt-4 inline-flex items-center gap-1.5 text-xs font-medium text-emerald-400 transition-colors group-hover:text-emerald-300">
<span className="mt-4 inline-flex items-center gap-1.5 text-xs font-medium text-violet-400 transition-colors group-hover:text-violet-300">
Read
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
</span>

View File

@ -55,7 +55,7 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.18 0.01 260);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.696 0.17 162);
--primary: oklch(0.526 0.247 293);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.22 0.01 260);
--secondary-foreground: oklch(0.985 0 0);
@ -66,8 +66,8 @@
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.28 0.01 260);
--input: oklch(0.28 0.01 260);
--ring: oklch(0.696 0.17 162);
--chart-1: oklch(0.696 0.17 162);
--ring: oklch(0.526 0.247 293);
--chart-1: oklch(0.526 0.247 293);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
@ -75,12 +75,12 @@
--radius: 0.625rem;
--sidebar: oklch(0.16 0.01 260);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.696 0.17 162);
--sidebar-primary: oklch(0.526 0.247 293);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.22 0.01 260);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.28 0.01 260);
--sidebar-ring: oklch(0.696 0.17 162);
--sidebar-ring: oklch(0.526 0.247 293);
}
@layer base {

View File

@ -1,9 +1,9 @@
import { ImageResponse } from "next/og";
// Replaces the default Next.js favicon with the WaCRM mark — emerald
// rounded square + white chat-bubble glyph — matching the sidebar
// logo in `src/components/layout/sidebar.tsx`. Next.js renders this
// at build time and auto-injects <link rel="icon"> into <head>.
// Replaces the default Next.js favicon with the brand mark — Hostinger
// violet rounded square + white chat-square glyph — matching the
// sidebar logo in `src/components/layout/sidebar.tsx`. Next.js renders
// this at build time and auto-injects <link rel="icon"> into <head>.
//
// This route takes precedence over src/app/favicon.ico, which is the
// Next.js default and can stay on disk harmlessly (or be removed).
@ -22,7 +22,7 @@ export default function Icon() {
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#10b981", // emerald-500
background: "#7c3aed", // violet-600 (Hostinger-aligned purple)
borderRadius: 6,
}}
>

View File

@ -38,7 +38,7 @@ export default async function Image() {
justifyContent: 'center',
padding: 80,
background:
'linear-gradient(135deg, #020617 0%, #0f172a 55%, #064e3b 100%)',
'linear-gradient(135deg, #020617 0%, #0f172a 55%, #2e1065 100%)',
color: 'white',
fontFamily: 'sans-serif',
}}
@ -49,7 +49,7 @@ export default async function Image() {
width: 64,
height: 64,
borderRadius: 14,
background: '#10b981',
background: '#7c3aed',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@ -78,7 +78,7 @@ export default async function Image() {
}}
>
<span>Run your WhatsApp® business from&nbsp;</span>
<span style={{ color: '#34d399' }}>one inbox.</span>
<span style={{ color: '#a78bfa' }}>one inbox.</span>
</div>
<div

View File

@ -5,7 +5,7 @@ import { SITE_URL } from '@/lib/seo/site-config'
* robots.txt served at /robots.txt. We explicitly disallow every
* gated area so crawlers don't spend their budget on pages they'll
* bounce off. Auth pages are closed off too — no SEO value, and
* we don't want "Sign in to WaCRM" competing with the landing page
* we don't want a "Sign in" page competing with the landing page
* in SERPs.
*/
export default function robots(): MetadataRoute.Robots {

View File

@ -77,17 +77,17 @@ interface StepMeta {
}
const STEP_META: Record<AutomationStepType, StepMeta> = {
send_message: { label: "Send Message", icon: MessageSquare, border: "border-l-emerald-500" },
send_template: { label: "Send Template", icon: FileText, border: "border-l-emerald-500" },
add_tag: { label: "Add Tag", icon: Tag, border: "border-l-emerald-500" },
remove_tag: { label: "Remove Tag", icon: TagIcon, border: "border-l-emerald-500" },
assign_conversation: { label: "Assign Conversation", icon: UserCheck, border: "border-l-emerald-500" },
update_contact_field: { label: "Update Contact Field", icon: PencilLine, border: "border-l-emerald-500" },
create_deal: { label: "Create Deal", icon: Briefcase, border: "border-l-emerald-500" },
send_message: { label: "Send Message", icon: MessageSquare, border: "border-l-violet-500" },
send_template: { label: "Send Template", icon: FileText, border: "border-l-violet-500" },
add_tag: { label: "Add Tag", icon: Tag, border: "border-l-violet-500" },
remove_tag: { label: "Remove Tag", icon: TagIcon, border: "border-l-violet-500" },
assign_conversation: { label: "Assign Conversation", icon: UserCheck, border: "border-l-violet-500" },
update_contact_field: { label: "Update Contact Field", icon: PencilLine, border: "border-l-violet-500" },
create_deal: { label: "Create Deal", icon: Briefcase, border: "border-l-violet-500" },
wait: { label: "Wait", icon: Hourglass, border: "border-l-slate-500" },
condition: { label: "Condition (If/Else)", icon: GitBranch, border: "border-l-amber-500" },
send_webhook: { label: "Send Webhook", icon: Webhook, border: "border-l-emerald-500" },
close_conversation: { label: "Close Conversation", icon: CircleSlash, border: "border-l-emerald-500" },
send_webhook: { label: "Send Webhook", icon: Webhook, border: "border-l-violet-500" },
close_conversation: { label: "Close Conversation", icon: CircleSlash, border: "border-l-violet-500" },
}
const ADDABLE_STEPS: AutomationStepType[] = [
@ -275,7 +275,7 @@ export function AutomationBuilder({ initial }: { initial: BuilderInitial }) {
<Button
onClick={save}
disabled={saving}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
{isEditing ? "Save" : "Save Draft"}
@ -356,7 +356,7 @@ function TriggerCard({
<select
value={type}
onChange={(e) => onTypeChange(e.target.value as AutomationTriggerType)}
className="w-full rounded-md border border-slate-700 bg-slate-800 px-2 py-1.5 text-sm text-white focus:border-emerald-500 focus:outline-none"
className="w-full rounded-md border border-slate-700 bg-slate-800 px-2 py-1.5 text-sm text-white focus:border-violet-500 focus:outline-none"
>
{TRIGGER_OPTIONS.map((o) => (
<option key={o.value} value={o.value}>
@ -637,7 +637,7 @@ function ConditionBranches({
// cram each branch to ~170px which is too narrow for the nested
// cards. Two-column grid returns on sm+.
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
<BranchColumn label="Yes" color="text-emerald-400">
<BranchColumn label="Yes" color="text-violet-400">
<StepList {...props} steps={yes} parentPath={yesPath} />
</BranchColumn>
<BranchColumn label="No" color="text-rose-400">
@ -670,7 +670,7 @@ function AddButton({ onPick }: { onPick: (t: AutomationStepType) => void }) {
<div className="h-4 w-[2px] bg-slate-700" aria-hidden />
<DropdownMenu>
<DropdownMenuTrigger
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-dashed border-slate-700 bg-slate-950 text-slate-400 transition-colors hover:border-emerald-500 hover:bg-emerald-500/10 hover:text-emerald-400 data-[popup-open]:border-emerald-500 data-[popup-open]:bg-emerald-500/20 data-[popup-open]:text-emerald-400"
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-dashed border-slate-700 bg-slate-950 text-slate-400 transition-colors hover:border-violet-500 hover:bg-violet-500/10 hover:text-violet-400 data-[popup-open]:border-violet-500 data-[popup-open]:bg-violet-500/20 data-[popup-open]:text-violet-400"
aria-label="Add step"
>
<Plus className="h-4 w-4" />

View File

@ -48,7 +48,7 @@ export function Step1ChooseTemplate({ selectedTemplate, onSelect, onNext, onBack
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-emerald-500" />
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />
</div>
);
}
@ -88,7 +88,7 @@ export function Step1ChooseTemplate({ selectedTemplate, onSelect, onNext, onBack
onClick={() => onSelect(template)}
className={`flex flex-col gap-3 rounded-xl border p-4 text-left transition-all ${
isSelected
? 'border-emerald-500 bg-emerald-500/5 ring-1 ring-emerald-500/30'
? 'border-violet-500 bg-violet-500/5 ring-1 ring-violet-500/30'
: 'border-slate-800 bg-slate-900/50 hover:border-slate-700 hover:bg-slate-900'
}`}
>
@ -123,7 +123,7 @@ export function Step1ChooseTemplate({ selectedTemplate, onSelect, onNext, onBack
<Button
onClick={onNext}
disabled={!selectedTemplate}
className="bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
className="bg-violet-600 text-white hover:bg-violet-700 disabled:opacity-50"
>
Next
<ArrowRight className="h-4 w-4" />

View File

@ -279,14 +279,14 @@ export function Step2SelectAudience({
}
className={`flex items-start gap-3 rounded-xl border p-4 text-left transition-all ${
isSelected
? 'border-emerald-500 bg-emerald-500/5 ring-1 ring-emerald-500/30'
? 'border-violet-500 bg-violet-500/5 ring-1 ring-violet-500/30'
: 'border-slate-800 bg-slate-900/50 hover:border-slate-700'
}`}
>
<div
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg ${
isSelected
? 'bg-emerald-500/10 text-emerald-400'
? 'bg-violet-500/10 text-violet-400'
: 'bg-slate-800 text-slate-400'
}`}
>
@ -307,7 +307,7 @@ export function Step2SelectAudience({
<div className="rounded-xl border border-slate-800 bg-slate-900/50 p-4">
<p className="mb-3 text-sm font-medium text-white">Select Tags</p>
{loadingTags ? (
<Loader2 className="h-5 w-5 animate-spin text-emerald-500" />
<Loader2 className="h-5 w-5 animate-spin text-violet-500" />
) : tags.length === 0 ? (
<p className="text-xs text-slate-400">
No tags found. Create tags in Settings.
@ -322,7 +322,7 @@ export function Step2SelectAudience({
onClick={() => toggleTag(tag.id)}
className={`inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium transition-all ${
isSelected
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
? 'border-violet-500/30 bg-violet-500/10 text-violet-400'
: 'border-slate-700 bg-slate-800 text-slate-300 hover:border-slate-600'
}`}
>
@ -343,7 +343,7 @@ export function Step2SelectAudience({
<div className="space-y-3 rounded-xl border border-slate-800 bg-slate-900/50 p-4">
<p className="text-sm font-medium text-white">Custom Field Filter</p>
{loadingFields ? (
<Loader2 className="h-5 w-5 animate-spin text-emerald-500" />
<Loader2 className="h-5 w-5 animate-spin text-violet-500" />
) : customFields.length === 0 ? (
<p className="text-xs text-slate-400">
No custom fields defined. Create one in Settings → Custom Fields.
@ -353,7 +353,7 @@ export function Step2SelectAudience({
<select
value={audience.customField?.fieldId ?? ''}
onChange={(e) => updateCustomField({ fieldId: e.target.value })}
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500"
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500"
>
<option value="">Select field…</option>
{customFields.map((f) => (
@ -369,7 +369,7 @@ export function Step2SelectAudience({
operator: e.target.value as CustomFieldOperator,
})
}
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500"
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500"
>
{OPERATOR_OPTIONS.map((op) => (
<option key={op.value} value={op.value}>
@ -382,7 +382,7 @@ export function Step2SelectAudience({
value={audience.customField?.value ?? ''}
onChange={(e) => updateCustomField({ value: e.target.value })}
placeholder="Value"
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none placeholder:text-slate-500 focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500"
className="h-9 rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none placeholder:text-slate-500 focus:border-violet-500 focus:ring-1 focus:ring-violet-500"
/>
</div>
)}
@ -431,12 +431,12 @@ export function Step2SelectAudience({
<p className="mb-2 text-sm font-medium text-white">Audience Summary</p>
{loadingCount ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin text-emerald-500" />
<Loader2 className="h-4 w-4 animate-spin text-violet-500" />
<span className="text-xs text-slate-400">Calculating…</span>
</div>
) : estimatedCount !== null ? (
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-emerald-400" />
<Users className="h-4 w-4 text-violet-400" />
<span className="text-sm text-white">
{estimatedCount.toLocaleString()}
</span>
@ -461,7 +461,7 @@ export function Step2SelectAudience({
<Button
onClick={onNext}
disabled={!isValid}
className="bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
className="bg-violet-600 text-white hover:bg-violet-700 disabled:opacity-50"
>
Next
<ArrowRight className="h-4 w-4" />

View File

@ -211,7 +211,7 @@ export function Step3Personalize({
className="rounded-xl border border-slate-800 bg-slate-900/50 p-4"
>
<div className="mb-3 flex items-center gap-2">
<span className="inline-flex items-center rounded-md bg-emerald-500/10 px-2 py-0.5 text-xs font-mono font-medium text-emerald-400">
<span className="inline-flex items-center rounded-md bg-violet-500/10 px-2 py-0.5 text-xs font-mono font-medium text-violet-400">
{placeholder}
</span>
</div>
@ -313,16 +313,16 @@ export function Step3Personalize({
sees approximately what the recipient will see. */}
<div className="rounded-xl border border-slate-800 bg-slate-900/50 p-4">
<div className="mb-3 flex items-center gap-2">
<Eye className="h-4 w-4 text-emerald-400" />
<Eye className="h-4 w-4 text-violet-400" />
<p className="text-sm font-medium text-white">Live Preview</p>
<span className="text-xs text-slate-500">({previewLabel})</span>
{loadingPreview && (
<Loader2 className="h-3.5 w-3.5 animate-spin text-emerald-500" />
<Loader2 className="h-3.5 w-3.5 animate-spin text-violet-500" />
)}
</div>
<div className="rounded-lg bg-[#0e1a12] p-3">
<div className="ml-auto max-w-[85%] rounded-lg bg-emerald-700/30 px-3 py-2 shadow-sm">
<p className="whitespace-pre-wrap text-sm text-emerald-50">
<div className="ml-auto max-w-[85%] rounded-lg bg-violet-700/30 px-3 py-2 shadow-sm">
<p className="whitespace-pre-wrap text-sm text-violet-50">
{previewText}
</p>
</div>
@ -351,7 +351,7 @@ export function Step3Personalize({
<Button
onClick={onNext}
disabled={unmappedKeys.length > 0}
className="bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
className="bg-violet-600 text-white hover:bg-violet-700 disabled:opacity-50"
>
Next
<ArrowRight className="h-4 w-4" />

View File

@ -127,10 +127,10 @@ export function Step4ScheduleSend({
<p className="text-xs text-slate-400">Estimated Reach</p>
<div className="flex items-center gap-1.5">
{loadingReach ? (
<Loader2 className="h-3 w-3 animate-spin text-emerald-500" />
<Loader2 className="h-3 w-3 animate-spin text-violet-500" />
) : (
<>
<Users className="h-3.5 w-3.5 text-emerald-400" />
<Users className="h-3.5 w-3.5 text-violet-400" />
<p className="font-medium text-white">{estimatedReach.toLocaleString()}</p>
</>
)}
@ -151,11 +151,11 @@ export function Step4ScheduleSend({
onClick={() => setTiming('now')}
className={`flex items-start gap-3 rounded-xl border p-4 text-left transition-all ${
timing === 'now'
? 'border-emerald-500 bg-emerald-500/5 ring-1 ring-emerald-500/30'
? 'border-violet-500 bg-violet-500/5 ring-1 ring-violet-500/30'
: 'border-slate-800 bg-slate-900/50 hover:border-slate-700'
}`}
>
<Send className={`mt-0.5 h-4 w-4 ${timing === 'now' ? 'text-emerald-400' : 'text-slate-400'}`} />
<Send className={`mt-0.5 h-4 w-4 ${timing === 'now' ? 'text-violet-400' : 'text-slate-400'}`} />
<div>
<p className="text-sm font-medium text-white">Send Immediately</p>
<p className="mt-0.5 text-xs text-slate-400">Start sending right away</p>
@ -180,17 +180,17 @@ export function Step4ScheduleSend({
{/* Processing overlay */}
{isProcessing && (
<div className="rounded-xl border border-emerald-500/20 bg-emerald-500/5 p-4">
<div className="rounded-xl border border-violet-500/20 bg-violet-500/5 p-4">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin text-emerald-500" />
<Loader2 className="h-4 w-4 animate-spin text-violet-500" />
<p className="text-sm font-medium text-white">Sending broadcast...</p>
</div>
<span className="text-xs font-medium text-emerald-400">{progress}%</span>
<span className="text-xs font-medium text-violet-400">{progress}%</span>
</div>
<div className="h-1.5 w-full rounded-full bg-slate-800">
<div
className="h-1.5 rounded-full bg-emerald-500 transition-all duration-300"
className="h-1.5 rounded-full bg-violet-500 transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
@ -226,7 +226,7 @@ export function Step4ScheduleSend({
render={
<Button
disabled={!name.trim() || isProcessing}
className="bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
className="bg-violet-600 text-white hover:bg-violet-700 disabled:opacity-50"
/>
}
>
@ -257,7 +257,7 @@ export function Step4ScheduleSend({
setShowConfirm(false);
onSend();
}}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
<Send className="h-4 w-4" />
Confirm & Send

View File

@ -331,7 +331,7 @@ export function ContactDetailView({
>
{loading || !contact ? (
<div className="flex items-center justify-center h-full">
<Loader2 className="size-6 animate-spin text-emerald-500" />
<Loader2 className="size-6 animate-spin text-violet-500" />
</div>
) : (
<div className="flex flex-col h-full">
@ -339,7 +339,7 @@ export function ContactDetailView({
<SheetHeader className="p-4 border-b border-slate-700/50">
<div className="flex items-center gap-3">
<Avatar className="size-12 bg-slate-800 border border-slate-700">
<AvatarFallback className="bg-emerald-500/10 text-emerald-400 text-sm font-medium">
<AvatarFallback className="bg-violet-500/10 text-violet-400 text-sm font-medium">
{getInitials(contact.name)}
</AvatarFallback>
</Avatar>
@ -353,12 +353,12 @@ export function ContactDetailView({
<div className="flex flex-wrap items-center gap-3 mt-1.5 text-xs text-slate-400">
<button
onClick={copyPhone}
className="flex items-center gap-1 hover:text-emerald-400 transition-colors cursor-pointer"
className="flex items-center gap-1 hover:text-violet-400 transition-colors cursor-pointer"
>
<Phone className="size-3" />
{contact.phone}
{copiedPhone ? (
<Check className="size-3 text-emerald-400" />
<Check className="size-3 text-violet-400" />
) : (
<Copy className="size-3" />
)}
@ -385,31 +385,31 @@ export function ContactDetailView({
<TabsList className="bg-slate-800/50 border-b border-slate-700 mx-4 mt-3">
<TabsTrigger
value="details"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
Details
</TabsTrigger>
<TabsTrigger
value="tags"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
Tags
</TabsTrigger>
<TabsTrigger
value="notes"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
Notes
</TabsTrigger>
<TabsTrigger
value="custom"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
Custom Fields
</TabsTrigger>
<TabsTrigger
value="deals"
className="data-active:bg-slate-800 data-active:text-emerald-400 text-slate-400"
className="data-active:bg-slate-800 data-active:text-violet-400 text-slate-400"
>
Deals
</TabsTrigger>
@ -455,7 +455,7 @@ export function ContactDetailView({
<Button
onClick={saveDetails}
disabled={savingDetails}
className="bg-emerald-600 hover:bg-emerald-700 text-white w-full"
className="bg-violet-600 hover:bg-violet-700 text-white w-full"
size="sm"
>
{savingDetails ? (
@ -489,7 +489,7 @@ export function ContactDetailView({
disabled={savingTags}
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-medium transition-all cursor-pointer ${
selected
? 'ring-2 ring-emerald-500 ring-offset-1 ring-offset-slate-900'
? 'ring-2 ring-violet-500 ring-offset-1 ring-offset-slate-900'
: 'opacity-50 hover:opacity-80'
}`}
style={{
@ -519,7 +519,7 @@ export function ContactDetailView({
<Button
onClick={addNote}
disabled={!newNote.trim() || savingNote}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
size="sm"
>
{savingNote ? (
@ -605,7 +605,7 @@ export function ContactDetailView({
<Button
onClick={saveCustomFields}
disabled={savingCustom}
className="bg-emerald-600 hover:bg-emerald-700 text-white w-full"
className="bg-violet-600 hover:bg-violet-700 text-white w-full"
size="sm"
>
{savingCustom ? (
@ -623,7 +623,7 @@ export function ContactDetailView({
<TabsContent value="deals" className="flex-1 overflow-y-auto px-4 py-3">
{loadingDeals ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="size-5 animate-spin text-emerald-500" />
<Loader2 className="size-5 animate-spin text-violet-500" />
</div>
) : deals.length === 0 ? (
<p className="text-xs text-slate-500">No deals yet</p>
@ -663,7 +663,7 @@ export function ContactDetailView({
<span
className={
deal.status === 'won'
? 'text-emerald-400'
? 'text-violet-400'
: 'text-red-400'
}
>

View File

@ -245,7 +245,7 @@ export function ContactForm({
onClick={() => toggleTag(tag.id)}
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors cursor-pointer ${
selected
? 'ring-2 ring-emerald-500 ring-offset-1 ring-offset-slate-900'
? 'ring-2 ring-violet-500 ring-offset-1 ring-offset-slate-900'
: 'opacity-60 hover:opacity-100'
}`}
style={{
@ -274,7 +274,7 @@ export function ContactForm({
<Button
type="submit"
disabled={saving}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
{saving && <Loader2 className="size-4 animate-spin" />}
{isEdit ? 'Update' : 'Create'}

View File

@ -196,11 +196,11 @@ export function ImportModal({ open, onOpenChange, onImported }: ImportModalProps
{/* Upload area */}
<div
onClick={() => fileInputRef.current?.click()}
className="flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-slate-700 p-6 cursor-pointer hover:border-emerald-500/50 transition-colors"
className="flex flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed border-slate-700 p-6 cursor-pointer hover:border-violet-500/50 transition-colors"
>
{file ? (
<>
<FileText className="size-8 text-emerald-400" />
<FileText className="size-8 text-violet-400" />
<p className="text-sm text-slate-300">{file.name}</p>
<p className="text-xs text-slate-500">
{parsedRows.length} row{parsedRows.length !== 1 ? 's' : ''} detected
@ -269,7 +269,7 @@ export function ImportModal({ open, onOpenChange, onImported }: ImportModalProps
<p className="text-sm font-medium text-white">Import Complete</p>
<div className="flex items-center gap-4">
{result.imported > 0 && (
<div className="flex items-center gap-1.5 text-emerald-400 text-sm">
<div className="flex items-center gap-1.5 text-violet-400 text-sm">
<CheckCircle className="size-4" />
{result.imported} imported
</div>
@ -299,7 +299,7 @@ export function ImportModal({ open, onOpenChange, onImported }: ImportModalProps
type="button"
disabled={parsedRows.length === 0 || importing}
onClick={handleImport}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
{importing && <Loader2 className="size-4 animate-spin" />}
Import {parsedRows.length > 0 ? `${parsedRows.length} Contacts` : ''}

View File

@ -32,7 +32,7 @@ interface KindTheme {
const KIND_THEME: Record<ActivityKind, KindTheme> = {
message: { icon: MessageSquare, badge: 'bg-blue-500/10 text-blue-400' },
contact: { icon: UserPlus, badge: 'bg-emerald-500/10 text-emerald-400' },
contact: { icon: UserPlus, badge: 'bg-violet-500/10 text-violet-400' },
deal: { icon: Briefcase, badge: 'bg-violet-500/10 text-violet-400' },
broadcast: { icon: Radio, badge: 'bg-amber-500/10 text-amber-400' },
automation: { icon: Zap, badge: 'bg-rose-500/10 text-rose-400' },
@ -59,7 +59,7 @@ export function ActivityFeed({ items, loading }: ActivityFeedProps) {
<h2 className="text-sm font-semibold text-white">Recent Activity</h2>
<Link
href="/inbox"
className="text-xs font-medium text-emerald-400 hover:text-emerald-300"
className="text-xs font-medium text-violet-400 hover:text-violet-300"
>
View all →
</Link>

View File

@ -87,7 +87,7 @@ export function ConversationsChart({ series, loading, range, onRangeChange }: Co
<footer className="flex items-center gap-4 border-t border-slate-800 px-5 py-3 text-xs text-slate-400">
<LegendDot color="#3b82f6" label="Incoming" />
<LegendDot color="#10b981" label="Outgoing" />
<LegendDot color="#7c3aed" label="Outgoing" />
</footer>
</section>
)
@ -238,11 +238,11 @@ function LineSvg({
) : null,
)}
{/* Outgoing polyline (emerald) */}
{/* Outgoing polyline (violet) */}
<path
d={outgoingPath}
fill="none"
stroke="#10b981"
stroke="#7c3aed"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
@ -269,7 +269,7 @@ function LineSvg({
strokeDasharray="3 3"
/>
<circle cx={hoverX} cy={yFor(data[hover.idx].incoming)} r={3.5} fill="#3b82f6" />
<circle cx={hoverX} cy={yFor(data[hover.idx].outgoing)} r={3.5} fill="#10b981" />
<circle cx={hoverX} cy={yFor(data[hover.idx].outgoing)} r={3.5} fill="#7c3aed" />
</g>
)}
</svg>
@ -289,8 +289,8 @@ function LineSvg({
<span className="inline-block h-1.5 w-1.5 rounded-full bg-blue-500" />
{hovered.incoming} incoming
</span>
<span className="flex items-center gap-1.5 text-emerald-300">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span className="flex items-center gap-1.5 text-violet-300">
<span className="inline-block h-1.5 w-1.5 rounded-full bg-violet-500" />
{hovered.outgoing} outgoing
</span>
</div>

View File

@ -43,7 +43,7 @@ export function MetricCard({ title, value, icon: Icon, delta, subtitle }: Metric
function DeltaRow({ sign, label }: { sign: number; label: string }) {
const tone =
sign > 0
? 'text-emerald-400'
? 'text-violet-400'
: sign < 0
? 'text-red-400'
: 'text-slate-500'

View File

@ -16,7 +16,7 @@ interface Action {
}
const ACTIONS: Action[] = [
{ label: 'New Contact', href: '/contacts', icon: UserPlus, tint: 'text-emerald-400' },
{ label: 'New Contact', href: '/contacts', icon: UserPlus, tint: 'text-violet-400' },
{ label: 'New Deal', href: '/pipelines', icon: Briefcase, tint: 'text-blue-400' },
{ label: 'New Broadcast', href: '/broadcasts/new', icon: Radio, tint: 'text-amber-400' },
{ label: 'New Automation', href: '/automations/new', icon: Zap, tint: 'text-violet-400' },

View File

@ -158,7 +158,7 @@ function Bars({
width={barW}
height={muted ? 2 : Math.max(1, h)}
rx={4}
fill={muted ? 'rgb(51 65 85)' : '#10b981'}
fill={muted ? 'rgb(51 65 85)' : '#7c3aed'}
opacity={muted ? 0.6 : 1}
>
<title>

View File

@ -32,11 +32,17 @@ export function DocsShell({ pages, children }: DocsShellProps) {
>
{mobileOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</button>
<Link href="/" className="flex items-center gap-2" aria-label="WaCRM home">
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500">
<Link
href="/"
className="flex items-center gap-2"
aria-label="CRM Template for WhatsApp home"
>
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500">
<MessageSquare className="h-4 w-4 text-white" />
</span>
<span className="text-lg font-semibold text-white">WaCRM</span>
<span className="text-sm font-semibold text-white">
CRM Template for WhatsApp
</span>
<span className="hidden rounded-md border border-slate-700 bg-slate-900 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-slate-400 sm:inline-block">
Docs
</span>
@ -61,7 +67,7 @@ export function DocsShell({ pages, children }: DocsShellProps) {
</a>
<Link
href="/signup"
className="inline-flex items-center justify-center rounded-lg bg-emerald-500 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-emerald-400"
className="inline-flex items-center justify-center rounded-lg bg-violet-500 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-violet-400"
>
Get started
</Link>

View File

@ -51,7 +51,7 @@ export function DocsSidebar({ pages, onNavigate }: DocsSidebarProps) {
className={cn(
'block rounded-md px-3 py-1.5 transition-colors',
active
? 'bg-emerald-500/10 text-emerald-300'
? 'bg-violet-500/10 text-violet-300'
: 'text-slate-400 hover:bg-slate-800/60 hover:text-white',
)}
>

View File

@ -156,7 +156,7 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
<Phone className="h-4 w-4 text-slate-500" />
<span className="flex-1 text-left">{contact.phone}</span>
{copied ? (
<Check className="h-3 w-3 text-emerald-400" />
<Check className="h-3 w-3 text-violet-400" />
) : (
<Copy className="h-3 w-3 text-slate-600" />
)}
@ -259,11 +259,11 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
onChange={(e) => setNewNote(e.target.value)}
placeholder="Add a note..."
rows={2}
className="flex-1 resize-none rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs text-white placeholder-slate-500 outline-none focus:border-emerald-500/50"
className="flex-1 resize-none rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs text-white placeholder-slate-500 outline-none focus:border-violet-500/50"
/>
<Button
size="sm"
className="h-auto bg-emerald-600 px-2 hover:bg-emerald-500"
className="h-auto bg-violet-600 px-2 hover:bg-violet-500"
onClick={handleAddNote}
disabled={!newNote.trim() || addingNote}
>

View File

@ -24,7 +24,7 @@ interface ConversationListProps {
}
const STATUS_COLORS: Record<ConversationStatus, string> = {
open: "bg-emerald-500",
open: "bg-violet-500",
pending: "bg-amber-500",
closed: "bg-slate-500",
};
@ -145,7 +145,7 @@ export function ConversationList({
value={search}
onChange={handleSearchChange}
placeholder="Search conversations..."
className="border-slate-700 bg-slate-800 pl-9 text-sm text-white placeholder-slate-500 focus:border-emerald-500/50"
className="border-slate-700 bg-slate-800 pl-9 text-sm text-white placeholder-slate-500 focus:border-violet-500/50"
/>
</div>
@ -165,7 +165,7 @@ export function ConversationList({
className={cn(
"text-sm",
filter === opt.value
? "text-emerald-400"
? "text-violet-400"
: "text-slate-300"
)}
>
@ -180,7 +180,7 @@ export function ConversationList({
<ScrollArea className="flex-1">
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
<div className="h-5 w-5 animate-spin rounded-full border-2 border-violet-500 border-t-transparent" />
</div>
) : filtered.length === 0 ? (
<div className="px-4 py-12 text-center">
@ -233,7 +233,7 @@ function ConversationItem({
onClick={handleClick}
className={cn(
"flex w-full items-start gap-3 px-3 py-3 text-left transition-colors hover:bg-slate-800/50",
isActive && "border-l-2 border-emerald-500 bg-slate-800/70"
isActive && "border-l-2 border-violet-500 bg-slate-800/70"
)}
>
{/* Avatar */}
@ -263,7 +263,7 @@ function ConversationItem({
</p>
<div className="flex shrink-0 items-center gap-1.5">
{conversation.unread_count > 0 && (
<span className="flex h-4 min-w-4 items-center justify-center rounded-full bg-emerald-500 px-1 text-[10px] font-bold text-white">
<span className="flex h-4 min-w-4 items-center justify-center rounded-full bg-violet-500 px-1 text-[10px] font-bold text-white">
{conversation.unread_count}
</span>
)}

View File

@ -93,7 +93,7 @@ function MediaImage({ url, alt }: { url: string; alt: string }) {
if (loading) {
return (
<div className="flex h-40 w-60 items-center justify-center rounded-lg bg-slate-700">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
<div className="h-5 w-5 animate-spin rounded-full border-2 border-violet-500 border-t-transparent" />
</div>
);
}
@ -185,7 +185,7 @@ function MessageContent({ message }: { message: Message }) {
case "template":
return (
<div>
<span className="mb-1 inline-flex items-center gap-1 rounded bg-emerald-500/20 px-1.5 py-0.5 text-[10px] font-medium text-emerald-400">
<span className="mb-1 inline-flex items-center gap-1 rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-medium text-violet-400">
<LayoutTemplate className="h-3 w-3" />
Template
</span>
@ -226,7 +226,7 @@ export function MessageBubble({ message }: MessageBubbleProps) {
className={cn(
"relative max-w-[75%] rounded-2xl px-3 py-2",
isAgent
? "rounded-br-md bg-emerald-600 text-white"
? "rounded-br-md bg-violet-600 text-white"
: "rounded-bl-md bg-slate-800 text-slate-100"
)}
>

View File

@ -107,14 +107,14 @@ export function MessageComposer({
disabled={sessionExpired}
rows={1}
className={cn(
"flex-1 resize-none rounded-xl border border-slate-700 bg-slate-800 px-4 py-2.5 text-sm text-white placeholder-slate-500 outline-none transition-colors focus:border-emerald-500/50",
"flex-1 resize-none rounded-xl border border-slate-700 bg-slate-800 px-4 py-2.5 text-sm text-white placeholder-slate-500 outline-none transition-colors focus:border-violet-500/50",
sessionExpired && "cursor-not-allowed opacity-50"
)}
/>
<Button
size="sm"
className="h-9 w-9 shrink-0 bg-emerald-600 p-0 hover:bg-emerald-500 disabled:opacity-40"
className="h-9 w-9 shrink-0 bg-violet-600 p-0 hover:bg-violet-500 disabled:opacity-40"
disabled={!text.trim() || sessionExpired || sending}
onClick={handleSend}
>

View File

@ -67,7 +67,7 @@ function groupMessagesByDate(messages: Message[]) {
}
const STATUS_OPTIONS: { label: string; value: ConversationStatus; color: string }[] = [
{ label: "Open", value: "open", color: "text-emerald-400" },
{ label: "Open", value: "open", color: "text-violet-400" },
{ label: "Pending", value: "pending", color: "text-amber-400" },
{ label: "Closed", value: "closed", color: "text-slate-400" },
];
@ -319,7 +319,7 @@ export function MessageThread({
variant="outline"
className={cn(
"ml-1 hidden gap-1 border-slate-700 text-[10px] sm:inline-flex sm:ml-2",
sessionInfo.expired ? "text-red-400" : "text-emerald-400"
sessionInfo.expired ? "text-red-400" : "text-violet-400"
)}
>
<Clock className="h-3 w-3" />
@ -369,7 +369,7 @@ export function MessageThread({
<div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4">
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
<div className="h-5 w-5 animate-spin rounded-full border-2 border-violet-500 border-t-transparent" />
</div>
) : messages.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12">

View File

@ -10,10 +10,10 @@ export function CtaBanner() {
return (
<Section className="py-16 sm:py-20">
<div
className="relative overflow-hidden rounded-2xl border border-emerald-500/20 bg-slate-900/70 px-6 py-16 text-center sm:px-12 sm:py-20"
className="relative overflow-hidden rounded-2xl border border-violet-500/20 bg-slate-900/70 px-6 py-16 text-center sm:px-12 sm:py-20"
style={{
background:
'radial-gradient(800px circle at 50% -40%, rgb(16 185 129 / 0.18), transparent 60%), linear-gradient(to bottom, rgb(15 23 42 / 0.9), rgb(15 23 42 / 0.7))',
'radial-gradient(800px circle at 50% -40%, rgb(139 92 246 / 0.18), transparent 60%), linear-gradient(to bottom, rgb(15 23 42 / 0.9), rgb(15 23 42 / 0.7))',
}}
>
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
@ -25,7 +25,7 @@ export function CtaBanner() {
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link
href="/signup"
className="inline-flex items-center gap-2 rounded-lg bg-emerald-500 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-emerald-400"
className="inline-flex items-center gap-2 rounded-lg bg-violet-500 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-violet-400"
>
Get started free
<ArrowRight className="h-4 w-4" />

View File

@ -31,7 +31,7 @@ export function FAQ() {
<ChevronDown
className={cn(
'h-4 w-4 flex-shrink-0 text-slate-500 transition-transform',
isOpen && 'rotate-180 text-emerald-400',
isOpen && 'rotate-180 text-violet-400',
)}
/>
</button>

View File

@ -37,7 +37,7 @@ export function FeatureSpotlight({
)}
>
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-emerald-400">
<p className="text-xs font-semibold uppercase tracking-wider text-violet-400">
{eyebrow}
</p>
<h3 className="mt-3 text-2xl font-bold tracking-tight text-white sm:text-3xl">
@ -50,7 +50,7 @@ export function FeatureSpotlight({
<ul className="mt-5 space-y-2.5">
{bullets.map((b) => (
<li key={b} className="flex items-start gap-2.5 text-sm text-slate-300">
<Check className="mt-0.5 h-4 w-4 flex-shrink-0 text-emerald-400" />
<Check className="mt-0.5 h-4 w-4 flex-shrink-0 text-violet-400" />
<span>{b}</span>
</li>
))}

View File

@ -32,7 +32,7 @@ const FEATURES: Feature[] = [
description:
'Tags, custom fields, notes, and automatic deduplication. Import existing contacts from CSV.',
icon: Users,
tint: 'text-emerald-400 bg-emerald-500/10',
tint: 'text-violet-400 bg-violet-500/10',
},
{
title: 'Sales pipelines',
@ -70,7 +70,7 @@ export function FeaturesGrid() {
<SectionHeader
eyebrow="Everything you need"
title="One toolkit for your WhatsApp business"
description="Stop stitching together an inbox, a spreadsheet, and a broadcast tool. WaCRM brings them together — and talks to WhatsApp natively."
description="Stop stitching together an inbox, a spreadsheet, and a broadcast tool. This CRM template for WhatsApp brings them together — and talks to WhatsApp natively."
/>
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">

View File

@ -7,11 +7,17 @@ export function Footer() {
<footer className="border-t border-slate-800 bg-slate-950">
<div className="mx-auto grid w-full max-w-7xl grid-cols-2 gap-8 px-6 py-12 sm:grid-cols-5">
<div className="col-span-2 sm:col-span-2">
<Link href="/" className="flex items-center gap-2" aria-label="WaCRM home">
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500">
<Link
href="/"
className="flex items-center gap-2"
aria-label="CRM Template for WhatsApp home"
>
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500">
<MessageSquare className="h-4 w-4 text-white" />
</span>
<span className="text-lg font-semibold text-white">WaCRM</span>
<span className="text-sm font-semibold text-white">
CRM Template for WhatsApp
</span>
</Link>
<p className="mt-3 max-w-sm text-sm text-slate-500">
One toolkit for your WhatsApp business — shared inbox, pipelines,
@ -58,7 +64,7 @@ export function Footer() {
<div className="border-t border-slate-900">
<div className="mx-auto flex w-full max-w-7xl flex-col items-start justify-between gap-3 px-6 py-5 text-xs text-slate-500 sm:flex-row sm:items-center">
<span>© {year} WaCRM. All rights reserved.</span>
<span>© {year} CRM Template for WhatsApp. All rights reserved.</span>
<span>Built on the official WhatsApp Business API.</span>
</div>
</div>

View File

@ -17,20 +17,20 @@ export function Hero() {
className="pointer-events-none absolute inset-0 -z-10 opacity-60"
style={{
background:
'radial-gradient(800px circle at 70% -10%, rgb(16 185 129 / 0.18), transparent 60%), radial-gradient(600px circle at 10% 30%, rgb(59 130 246 / 0.08), transparent 60%)',
'radial-gradient(800px circle at 70% -10%, rgb(139 92 246 / 0.18), transparent 60%), radial-gradient(600px circle at 10% 30%, rgb(59 130 246 / 0.08), transparent 60%)',
}}
/>
<div className="mx-auto grid w-full max-w-7xl grid-cols-1 items-center gap-12 px-6 py-20 sm:py-28 lg:grid-cols-2 lg:gap-16">
<div>
<div className="inline-flex items-center gap-2 rounded-full border border-slate-800 bg-slate-900/60 px-3 py-1 text-xs text-slate-300">
<ShieldCheck className="h-3.5 w-3.5 text-emerald-400" />
<ShieldCheck className="h-3.5 w-3.5 text-violet-400" />
Built on the official WhatsApp® Business API
</div>
<h1 className="mt-5 text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">
Run your WhatsApp business from{' '}
<span className="text-emerald-400">one inbox.</span>
<span className="text-violet-400">one inbox.</span>
</h1>
<p className="mt-5 max-w-xl text-base text-slate-400 sm:text-lg">
@ -42,7 +42,7 @@ export function Hero() {
<div className="mt-8 flex flex-wrap items-center gap-3">
<Link
href="/signup"
className="inline-flex items-center gap-2 rounded-lg bg-emerald-500 px-5 py-3 text-sm font-semibold text-white transition-colors hover:bg-emerald-400"
className="inline-flex items-center gap-2 rounded-lg bg-violet-500 px-5 py-3 text-sm font-semibold text-white transition-colors hover:bg-violet-400"
>
Get started
<ArrowRight className="h-4 w-4" />
@ -57,15 +57,15 @@ export function Hero() {
<div className="mt-8 flex flex-wrap items-center gap-x-6 gap-y-2 text-xs text-slate-500">
<span className="flex items-center gap-1.5">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span className="h-1.5 w-1.5 rounded-full bg-violet-500" />
No credit card required
</span>
<span className="flex items-center gap-1.5">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span className="h-1.5 w-1.5 rounded-full bg-violet-500" />
Live in 30 minutes
</span>
<span className="flex items-center gap-1.5">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span className="h-1.5 w-1.5 rounded-full bg-violet-500" />
Your data, your Supabase
</span>
</div>

View File

@ -43,7 +43,7 @@ export function HowItWorks() {
className="relative rounded-xl border border-slate-800 bg-slate-900/40 p-6"
>
<div className="flex items-start justify-between">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-emerald-500/10 text-emerald-400">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-violet-500/10 text-violet-400">
<Icon className="h-5 w-5" />
</div>
<span

View File

@ -27,7 +27,9 @@ export function AnalyticsMock() {
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="ml-3 text-[10px] text-slate-500">Dashboard — WaCRM</span>
<span className="ml-3 text-[10px] text-slate-500">
Dashboard — CRM Template for WhatsApp
</span>
</div>
<div className="space-y-3 p-4">
@ -47,7 +49,7 @@ export function AnalyticsMock() {
</div>
<div
className={`text-[10px] tabular-nums ${
s.positive ? 'text-emerald-400' : 'text-red-400'
s.positive ? 'text-violet-400' : 'text-red-400'
}`}
>
{s.delta}
@ -67,7 +69,7 @@ export function AnalyticsMock() {
Incoming
</span>
<span className="inline-flex items-center gap-1 text-[9px] text-slate-500">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span className="h-1.5 w-1.5 rounded-full bg-violet-500" />
Outgoing
</span>
</div>
@ -89,7 +91,7 @@ export function AnalyticsMock() {
strokeDasharray="3 3"
/>
))}
<path d={path(outgoing)} fill="none" stroke="#10b981" strokeWidth={1.5} />
<path d={path(outgoing)} fill="none" stroke="#7c3aed" strokeWidth={1.5} />
<path d={path(incoming)} fill="none" stroke="#3b82f6" strokeWidth={1.5} />
</svg>
</div>

View File

@ -19,8 +19,8 @@ export function AutomationMock() {
kind: 'action',
label: 'Send "Hi! 👋 Thanks for reaching out."',
icon: MessageSquare,
accent: 'border-l-emerald-500',
badgeClass: 'bg-emerald-500/10 text-emerald-400',
accent: 'border-l-violet-500',
badgeClass: 'bg-violet-500/10 text-violet-400',
kindLabel: 'ACTION',
},
{
@ -47,7 +47,9 @@ export function AutomationMock() {
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="ml-3 text-[10px] text-slate-500">Automations — WaCRM</span>
<span className="ml-3 text-[10px] text-slate-500">
Automations — CRM Template for WhatsApp
</span>
</div>
{/* Dotted canvas — matches the real flow builder's background. */}

View File

@ -14,7 +14,9 @@ export function InboxMock() {
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="ml-3 text-[10px] text-slate-500">Inbox — WaCRM</span>
<span className="ml-3 text-[10px] text-slate-500">
Inbox — CRM Template for WhatsApp
</span>
</div>
<div className="grid grid-cols-[180px_1fr] lg:grid-cols-[220px_1fr]">
@ -33,16 +35,16 @@ export function InboxMock() {
<div
key={i}
className={`flex items-start gap-2 px-3 py-2.5 ${
c.active ? 'border-l-2 border-emerald-500 bg-slate-800/60' : 'border-l-2 border-transparent'
c.active ? 'border-l-2 border-violet-500 bg-slate-800/60' : 'border-l-2 border-transparent'
}`}
>
<span className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-[10px] font-medium text-emerald-400">
<span className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-violet-500/10 text-[10px] font-medium text-violet-400">
{c.name[0]}
</span>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2">
<span className="truncate text-xs font-medium text-white">{c.name}</span>
{c.unread && <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />}
{c.unread && <span className="h-1.5 w-1.5 rounded-full bg-violet-500" />}
</div>
<p className="truncate text-[10px] text-slate-500">{c.preview}</p>
</div>
@ -53,14 +55,14 @@ export function InboxMock() {
{/* Thread */}
<div className="flex min-h-[280px] flex-col bg-slate-950">
<div className="flex items-center gap-2 border-b border-slate-800 px-4 py-2.5">
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-emerald-500/10 text-[10px] font-medium text-emerald-400">
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-violet-500/10 text-[10px] font-medium text-violet-400">
A
</span>
<div>
<div className="text-xs font-medium text-white">Aisha</div>
<div className="text-[10px] text-slate-500">+44 7700 900123</div>
</div>
<div className="ml-auto inline-flex items-center gap-1 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-2 py-0.5 text-[10px] text-emerald-300">
<div className="ml-auto inline-flex items-center gap-1 rounded-full border border-violet-500/30 bg-violet-500/10 px-2 py-0.5 text-[10px] text-violet-300">
Open
</div>
</div>
@ -69,7 +71,7 @@ export function InboxMock() {
<div className="mr-auto max-w-[75%] rounded-2xl rounded-bl-sm bg-slate-800 px-3 py-2 text-xs text-slate-200">
Hi! Is the kit available in large?
</div>
<div className="ml-auto max-w-[75%] rounded-2xl rounded-br-sm bg-emerald-500 px-3 py-2 text-xs text-white">
<div className="ml-auto max-w-[75%] rounded-2xl rounded-br-sm bg-violet-500 px-3 py-2 text-xs text-white">
Yes — shipping today 📦
</div>
<div className="mr-auto max-w-[75%] rounded-2xl rounded-bl-sm bg-slate-800 px-3 py-2 text-xs text-slate-200">
@ -85,7 +87,7 @@ export function InboxMock() {
type="button"
tabIndex={-1}
aria-hidden
className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500 text-white"
className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500 text-white"
>
<MessageSquare className="h-3.5 w-3.5" />
</button>

View File

@ -23,7 +23,7 @@ export function PipelineMock() {
},
{
name: 'Won',
color: 'bg-emerald-500',
color: 'bg-violet-500',
deals: [{ title: 'Lagoon Spa', value: '$3,200' }],
},
]
@ -34,7 +34,9 @@ export function PipelineMock() {
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="h-2.5 w-2.5 rounded-full bg-slate-700" />
<span className="ml-3 text-[10px] text-slate-500">Pipelines — WaCRM</span>
<span className="ml-3 text-[10px] text-slate-500">
Pipelines — CRM Template for WhatsApp
</span>
</div>
<div className="grid grid-cols-3 gap-3 p-4">

View File

@ -60,11 +60,17 @@ export function LandingNav() {
)}
>
<div className="mx-auto flex h-16 w-full max-w-7xl items-center justify-between px-6">
<Link href="/" className="flex items-center gap-2" aria-label="WaCRM home">
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500">
<Link
href="/"
className="flex items-center gap-2"
aria-label="CRM Template for WhatsApp home"
>
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500">
<MessageSquare className="h-4 w-4 text-white" />
</span>
<span className="text-lg font-semibold text-white">WaCRM</span>
<span className="text-sm font-semibold text-white">
CRM Template for WhatsApp
</span>
</Link>
<nav className="hidden items-center gap-8 md:flex">
@ -145,7 +151,7 @@ function NavCtas({ auth, mobile = false }: { auth: AuthState; mobile?: boolean }
)
const primary = cn(
btnBase,
'bg-emerald-500 text-white hover:bg-emerald-400',
'bg-violet-500 text-white hover:bg-violet-400',
mobile && 'justify-center',
)

View File

@ -12,7 +12,7 @@ export function OpenSource() {
<SectionHeader
eyebrow="Open source"
title="Fork it, brand it, host it"
description="WaCRM is a template you can take and make your own. Grab the source on GitHub and self-host — we recommend Hostinger Managed Node.js Hosting for a zero-ops deploy."
description="This CRM template for WhatsApp is a template you can take and make your own. Grab the source on GitHub and self-host — we recommend Hostinger Managed Node.js Hosting for a zero-ops deploy."
/>
<div className="mx-auto grid max-w-5xl grid-cols-1 gap-5 md:grid-cols-2">
@ -32,7 +32,7 @@ export function OpenSource() {
Clone or fork the repository, tweak the code, ship your own build.
MIT-style freedom with the full CRM underneath.
</p>
<span className="mt-5 inline-flex items-center gap-1.5 text-sm font-medium text-emerald-400 transition-colors group-hover:text-emerald-300">
<span className="mt-5 inline-flex items-center gap-1.5 text-sm font-medium text-violet-400 transition-colors group-hover:text-violet-300">
ArnasDon/wacrm
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</span>
@ -42,23 +42,23 @@ export function OpenSource() {
href={HOSTINGER_URL}
target="_blank"
rel="noopener noreferrer"
className="group flex flex-col rounded-xl border border-emerald-500/20 bg-slate-900/40 p-6 transition-colors hover:border-emerald-500/40 hover:bg-slate-900/70"
className="group flex flex-col rounded-xl border border-violet-500/20 bg-slate-900/40 p-6 transition-colors hover:border-violet-500/40 hover:bg-slate-900/70"
>
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-emerald-500/10 text-emerald-400">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-violet-500/10 text-violet-400">
<Server className="h-5 w-5" />
</div>
<h3 className="mt-4 flex flex-wrap items-center gap-2 text-base font-semibold text-white">
Deploy on Hostinger
<span className="rounded-full bg-emerald-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-emerald-400">
<span className="rounded-full bg-violet-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-violet-400">
Recommended
</span>
</h3>
<p className="mt-1.5 text-sm leading-relaxed text-slate-400">
Best fit for this template — connect your forked repo to
Managed Node.js Hosting and WaCRM is live in a few minutes. No
Managed Node.js Hosting and your CRM is live in a few minutes. No
servers to patch.
</p>
<span className="mt-5 inline-flex items-center gap-1.5 text-sm font-medium text-emerald-400 transition-colors group-hover:text-emerald-300">
<span className="mt-5 inline-flex items-center gap-1.5 text-sm font-medium text-violet-400 transition-colors group-hover:text-violet-300">
Managed Node.js Hosting
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</span>
@ -70,7 +70,7 @@ export function OpenSource() {
href="/docs"
className="group inline-flex items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/40 px-5 py-3 text-sm font-medium text-slate-200 transition-colors hover:border-slate-700 hover:bg-slate-900/70 hover:text-white"
>
<BookOpen className="h-4 w-4 text-emerald-400" />
<BookOpen className="h-4 w-4 text-violet-400" />
Read the full documentation
<ArrowRight className="h-4 w-4 text-slate-400 transition-transform group-hover:translate-x-0.5 group-hover:text-slate-200" />
</Link>

View File

@ -54,7 +54,7 @@ export function SectionHeader({
return (
<div className={cn(base, max, 'mb-12')}>
{eyebrow && (
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-emerald-400">
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-violet-400">
{eyebrow}
</p>
)}

View File

@ -80,7 +80,7 @@ export function Header({ onOpenSidebar }: HeaderProps) {
alt={profile.full_name ?? "Avatar"}
/>
) : null}
<AvatarFallback className="bg-emerald-500/10 text-sm font-medium text-emerald-500">
<AvatarFallback className="bg-violet-500/10 text-sm font-medium text-violet-500">
{initial}
</AvatarFallback>
</Avatar>

View File

@ -111,10 +111,12 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
close button is hidden since the sidebar is always-visible. */}
<div className="flex h-14 shrink-0 items-center justify-between gap-2 border-b border-slate-800 px-4">
<Link href="/dashboard" className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-500">
<MessageSquare className="h-4 w-4 text-white" />
</div>
<span className="text-lg font-semibold text-white">WaCRM</span>
<span className="text-sm font-semibold text-white">
CRM Template for WhatsApp
</span>
</Link>
<button
type="button"
@ -145,7 +147,7 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
// Taller on mobile so fingers can hit the row reliably (≥44px).
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors lg:py-2",
isActive
? "bg-emerald-500/10 text-emerald-500"
? "bg-violet-500/10 text-violet-500"
: "text-slate-400 hover:bg-slate-800 hover:text-white",
)}
>
@ -156,8 +158,8 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
aria-label={`${totalUnread} unread conversation${totalUnread === 1 ? "" : "s"}`}
className="relative flex h-2 w-2"
>
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-violet-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-violet-500" />
</span>
)}
</Link>
@ -178,7 +180,7 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors lg:py-2",
isActive
? "bg-emerald-500/10 text-emerald-500"
? "bg-violet-500/10 text-violet-500"
: "text-slate-400 hover:bg-slate-800 hover:text-white",
)}
>
@ -202,7 +204,7 @@ export function Sidebar({ open = false, onClose }: SidebarProps) {
alt={profile.full_name ?? "Avatar"}
/>
) : null}
<AvatarFallback className="bg-emerald-500/10 text-sm font-medium text-emerald-500">
<AvatarFallback className="bg-violet-500/10 text-sm font-medium text-violet-500">
{profile?.full_name?.charAt(0)?.toUpperCase() ??
profile?.email?.charAt(0)?.toUpperCase() ??
"U"}

View File

@ -65,7 +65,7 @@ export function DealCard({ deal, stage, onEdit, isOverlay }: DealCardProps) {
{deal.title}
</h4>
{deal.status === "won" && (
<span className="inline-flex shrink-0 items-center gap-1 rounded-full bg-emerald-500/15 px-2 py-0.5 text-[10px] font-semibold text-emerald-400">
<span className="inline-flex shrink-0 items-center gap-1 rounded-full bg-violet-500/15 px-2 py-0.5 text-[10px] font-semibold text-violet-400">
<Check className="h-3 w-3" />
Won
</span>
@ -87,7 +87,7 @@ export function DealCard({ deal, stage, onEdit, isOverlay }: DealCardProps) {
</div>
<div className="mt-2 flex items-center justify-between">
<span className="text-sm font-bold text-emerald-400">
<span className="text-sm font-bold text-violet-400">
{formatCurrency(deal.value, deal.currency)}
</span>
{deal.expected_close_date && (
@ -102,7 +102,7 @@ export function DealCard({ deal, stage, onEdit, isOverlay }: DealCardProps) {
<div className="mt-2 flex items-center justify-end">
<span
title={assigneeLabel}
className="flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500/15 text-[10px] font-semibold text-emerald-400"
className="flex h-5 w-5 items-center justify-center rounded-full bg-violet-500/15 text-[10px] font-semibold text-violet-400"
>
{initials(assigneeLabel)}
</span>

View File

@ -264,7 +264,7 @@ export function DealForm({
<select
value={contactId}
onChange={(e) => setContactId(e.target.value)}
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500"
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500"
>
<option value="">Select a contact</option>
{contacts.map((c) => (
@ -277,7 +277,7 @@ export function DealForm({
{linkedConversation && (
<Link
href="/inbox"
className="mt-1 inline-flex items-center gap-1.5 self-start rounded-md bg-emerald-500/10 px-2 py-1 text-xs text-emerald-400 hover:bg-emerald-500/20"
className="mt-1 inline-flex items-center gap-1.5 self-start rounded-md bg-violet-500/10 px-2 py-1 text-xs text-violet-400 hover:bg-violet-500/20"
>
<MessageSquare className="h-3 w-3" />
Link to Conversation
@ -304,7 +304,7 @@ export function DealForm({
<select
value={currency}
onChange={(e) => setCurrency(e.target.value)}
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500"
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500"
>
<option value="USD">USD</option>
<option value="EUR">EUR</option>
@ -328,7 +328,7 @@ export function DealForm({
<select
value={stageId}
onChange={(e) => setStageId(e.target.value)}
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500"
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500"
>
{stages.map((s) => (
<option key={s.id} value={s.id}>
@ -343,7 +343,7 @@ export function DealForm({
<select
value={assignedTo}
onChange={(e) => setAssignedTo(e.target.value)}
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-emerald-500"
className="h-9 w-full rounded-lg border border-slate-700 bg-slate-800 px-2.5 text-sm text-white outline-none focus:border-violet-500"
>
<option value="">Unassigned</option>
{profiles.map((p) => (
@ -374,7 +374,7 @@ export function DealForm({
type="button"
onClick={() => handleStatusChange("won")}
disabled={!!statusAction || deal.status === "won"}
className="flex-1 bg-emerald-600 text-white hover:bg-emerald-700 disabled:opacity-50"
className="flex-1 bg-violet-600 text-white hover:bg-violet-700 disabled:opacity-50"
>
{statusAction === "won" ? (
<Loader2 className="h-4 w-4 animate-spin" />
@ -428,7 +428,7 @@ export function DealForm({
<Button
onClick={handleSave}
disabled={saving || !title.trim() || !contactId || !stageId}
className="flex-1 bg-emerald-600 text-white hover:bg-emerald-700"
className="flex-1 bg-violet-600 text-white hover:bg-violet-700"
>
{saving ? "Saving..." : deal ? "Save Changes" : "Create Deal"}
</Button>

View File

@ -107,7 +107,7 @@ export function PipelineAnalytics({ stages, deals }: PipelineAnalyticsProps) {
tooltip="Count of every deal in this pipeline that isn't marked as Lost. Won deals are still included."
/>
<Metric
icon={<DollarSign className="h-4 w-4 text-emerald-400" />}
icon={<DollarSign className="h-4 w-4 text-violet-400" />}
label="Pipeline Value"
value={formatCurrency(stats.totalValue)}
tooltip="Sum of the dollar values of all deals in this pipeline, excluding deals marked as Lost."
@ -125,7 +125,7 @@ export function PipelineAnalytics({ stages, deals }: PipelineAnalyticsProps) {
tooltip="Expected revenue: each open deal's value × its stage probability. First stage ≈ 10%, stages progress up to 90%, Won = 100%. Lost deals are excluded."
/>
<Metric
icon={<Trophy className="h-4 w-4 text-emerald-400" />}
icon={<Trophy className="h-4 w-4 text-violet-400" />}
label="Won This Month"
value={String(stats.wonThisMonth)}
tooltip="Deals marked as Won since the first day of the current month."

View File

@ -206,7 +206,7 @@ function StageColumn({
ref={setNodeRef}
className={`mt-3 flex flex-1 flex-col gap-2 rounded-lg transition-all ${
isOver
? "bg-emerald-500/5 outline outline-2 outline-dashed outline-emerald-400 outline-offset-2"
? "bg-violet-500/5 outline outline-2 outline-dashed outline-violet-400 outline-offset-2"
: ""
}`}
>

View File

@ -349,7 +349,7 @@ export function PipelineSettings({
<Button
onClick={handleSave}
disabled={saving || !name.trim()}
className="bg-emerald-600 text-white hover:bg-emerald-700"
className="bg-violet-600 text-white hover:bg-violet-700"
>
{saving ? "Saving..." : "Save Changes"}
</Button>

View File

@ -84,7 +84,7 @@ export function PasswordForm() {
<Card className="bg-slate-900/40 border-slate-800">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-white">
<KeyRound className="size-4 text-emerald-400" />
<KeyRound className="size-4 text-violet-400" />
Password
</CardTitle>
<CardDescription className="text-slate-400">

View File

@ -214,7 +214,7 @@ export function ProfileForm() {
<CardHeader>
<CardTitle className="text-white">Profile</CardTitle>
<CardDescription className="text-slate-400">
How you show up across WaCRM. Your avatar and name appear in the
How you show up across the app. Your avatar and name appear in the
header, sidebar, and anywhere your teammates see you.
</CardDescription>
</CardHeader>
@ -227,7 +227,7 @@ export function ProfileForm() {
{currentAvatar ? (
<AvatarImage src={currentAvatar} alt={fullName || 'Avatar'} />
) : null}
<AvatarFallback className="bg-emerald-500/10 text-base text-emerald-400">
<AvatarFallback className="bg-violet-500/10 text-base text-violet-400">
{initial}
</AvatarFallback>
</Avatar>

View File

@ -52,13 +52,12 @@ export function SessionsCard() {
<Card className="bg-slate-900/40 border-slate-800">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-white">
<LogOut className="size-4 text-emerald-400" />
<LogOut className="size-4 text-violet-400" />
Active sessions
</CardTitle>
<CardDescription className="text-slate-400">
Sign out of every device where you&apos;re logged in to WaCRM —
including this one. Useful if you lost a laptop or shared your
password.
Sign out of every device where you&apos;re logged in — including
this one. Useful if you lost a laptop or shared your password.
</CardDescription>
</CardHeader>
<CardContent>

View File

@ -142,7 +142,7 @@ export function TagManager() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="size-6 animate-spin text-emerald-500" />
<Loader2 className="size-6 animate-spin text-violet-500" />
</div>
);
}
@ -160,7 +160,7 @@ export function TagManager() {
setSelectedColor(PRESET_COLORS[3].value);
setDialogOpen(true);
}}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
<Plus className="size-4" />
New Tag
@ -281,7 +281,7 @@ export function TagManager() {
<Button
onClick={handleCreate}
disabled={saving}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
{saving ? (
<>

View File

@ -40,7 +40,7 @@ const categoryColors: Record<string, string> = {
const statusColors: Record<string, string> = {
Draft: 'bg-slate-600/20 text-slate-400 border-slate-600/30',
Pending: 'bg-yellow-600/20 text-yellow-400 border-yellow-600/30',
Approved: 'bg-emerald-600/20 text-emerald-400 border-emerald-600/30',
Approved: 'bg-violet-600/20 text-violet-400 border-violet-600/30',
Rejected: 'bg-red-600/20 text-red-400 border-red-600/30',
};
@ -246,7 +246,7 @@ export function TemplateManager() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="size-6 animate-spin text-emerald-500" />
<Loader2 className="size-6 animate-spin text-violet-500" />
</div>
);
}
@ -280,7 +280,7 @@ export function TemplateManager() {
setForm(emptyForm);
setDialogOpen(true);
}}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
<Plus className="size-4" />
New Template
@ -456,7 +456,7 @@ export function TemplateManager() {
<Button
onClick={handleSave}
disabled={saving}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
{saving ? (
<>

View File

@ -265,7 +265,7 @@ export function WhatsAppConfig() {
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="size-6 animate-spin text-emerald-500" />
<Loader2 className="size-6 animate-spin text-violet-500" />
</div>
);
}
@ -315,7 +315,7 @@ export function WhatsAppConfig() {
<Alert className="bg-slate-900 border-slate-700">
<div className="flex items-center gap-2">
{connectionStatus === 'connected' ? (
<CheckCircle2 className="size-4 text-emerald-500" />
<CheckCircle2 className="size-4 text-violet-500" />
) : (
<XCircle className="size-4 text-red-500" />
)}
@ -444,7 +444,7 @@ export function WhatsAppConfig() {
<Button
onClick={handleSave}
disabled={saving}
className="bg-emerald-600 hover:bg-emerald-700 text-white"
className="bg-violet-600 hover:bg-violet-700 text-white"
>
{saving ? (
<>
@ -510,13 +510,13 @@ export function WhatsAppConfig() {
<AccordionItem className="border-slate-700">
<AccordionTrigger className="text-slate-300 hover:text-white hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-emerald-600 text-xs font-bold text-white">1</span>
<span className="flex size-5 items-center justify-center rounded-full bg-violet-600 text-xs font-bold text-white">1</span>
Create a Meta App
</span>
</AccordionTrigger>
<AccordionContent className="text-slate-400">
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Go to <span className="text-emerald-400">developers.facebook.com</span></li>
<li>Go to <span className="text-violet-400">developers.facebook.com</span></li>
<li>Click &quot;My Apps&quot; and then &quot;Create App&quot;</li>
<li>Select &quot;Business&quot; as the app type</li>
<li>Fill in app details and create</li>
@ -527,7 +527,7 @@ export function WhatsAppConfig() {
<AccordionItem className="border-slate-700">
<AccordionTrigger className="text-slate-300 hover:text-white hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-emerald-600 text-xs font-bold text-white">2</span>
<span className="flex size-5 items-center justify-center rounded-full bg-violet-600 text-xs font-bold text-white">2</span>
Add WhatsApp Product
</span>
</AccordionTrigger>
@ -543,7 +543,7 @@ export function WhatsAppConfig() {
<AccordionItem className="border-slate-700">
<AccordionTrigger className="text-slate-300 hover:text-white hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-emerald-600 text-xs font-bold text-white">3</span>
<span className="flex size-5 items-center justify-center rounded-full bg-violet-600 text-xs font-bold text-white">3</span>
Get API Credentials
</span>
</AccordionTrigger>
@ -560,7 +560,7 @@ export function WhatsAppConfig() {
<AccordionItem className="border-slate-700">
<AccordionTrigger className="text-slate-300 hover:text-white hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-emerald-600 text-xs font-bold text-white">4</span>
<span className="flex size-5 items-center justify-center rounded-full bg-violet-600 text-xs font-bold text-white">4</span>
Configure Webhooks
</span>
</AccordionTrigger>
@ -581,7 +581,7 @@ export function WhatsAppConfig() {
href="https://developers.facebook.com/docs/whatsapp/cloud-api/get-started"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-sm text-emerald-400 hover:text-emerald-300 transition-colors"
className="inline-flex items-center gap-1.5 text-sm text-violet-400 hover:text-violet-300 transition-colors"
>
<ExternalLink className="size-3.5" />
Meta WhatsApp API Documentation

View File

@ -15,9 +15,9 @@ function Switch({
data-slot="switch"
className={cn(
"inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950",
"disabled:cursor-not-allowed disabled:opacity-50",
"data-[checked]:bg-emerald-500 data-[unchecked]:bg-slate-700",
"data-[checked]:bg-violet-500 data-[unchecked]:bg-slate-700",
className,
)}
{...props}

View File

@ -21,7 +21,7 @@ export const TRIGGER_META: Record<AutomationTriggerType, TriggerMeta> = {
},
new_contact_created: {
label: 'New Contact',
pillClass: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-300',
pillClass: 'border-violet-500/30 bg-violet-500/10 text-violet-300',
},
conversation_assigned: {
label: 'Conversation Assigned',

View File

@ -36,7 +36,7 @@ export const broadcastStatusConfig: Record<BroadcastStatus, StatusDisplay> = {
},
sent: {
label: "Sent",
classes: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
classes: "bg-violet-500/10 text-violet-400 border-violet-500/20",
},
failed: {
label: "Failed",
@ -55,11 +55,11 @@ export const recipientStatusConfig: Record<RecipientStatus, StatusDisplay> = {
},
delivered: {
label: "Delivered",
classes: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
classes: "bg-violet-500/10 text-violet-400 border-violet-500/20",
},
read: {
label: "Read",
classes: "bg-emerald-500/10 text-emerald-300 border-emerald-500/20",
classes: "bg-violet-500/10 text-violet-300 border-violet-500/20",
},
replied: {
label: "Replied",

View File

@ -22,7 +22,7 @@ const ORDER: Record<string, { order: number; section: string; description: strin
'getting-started': {
order: 1,
section: 'Setup',
description: 'Fork, install, run WaCRM locally in about 15 minutes.',
description: 'Fork, install, run the template locally in about 15 minutes.',
},
'supabase-setup': {
order: 2,
@ -37,12 +37,12 @@ const ORDER: Record<string, { order: number; section: string; description: strin
'environment-variables': {
order: 4,
section: 'Setup',
description: 'Full reference for every env var WaCRM reads.',
description: 'Full reference for every env var the app reads.',
},
'deployment-hostinger': {
order: 5,
section: 'Deploy',
description: 'Ship WaCRM on Hostinger Managed Node.js Hosting.',
description: 'Ship the template on Hostinger Managed Node.js Hosting.',
},
'automations-and-cron': {
order: 6,
@ -71,9 +71,9 @@ const CLASSES = {
ul: 'my-3 pl-6 list-disc marker:text-slate-500 text-slate-300 leading-relaxed space-y-1.5',
ol: 'my-3 pl-6 list-decimal marker:text-slate-500 text-slate-300 leading-relaxed space-y-1.5',
li: 'pl-1',
blockquote: 'my-4 rounded-r-lg border-l-4 border-emerald-500 bg-emerald-500/5 px-4 py-3 text-slate-200',
blockquote: 'my-4 rounded-r-lg border-l-4 border-violet-500 bg-violet-500/5 px-4 py-3 text-slate-200',
hr: 'my-8 border-t border-slate-800',
a: 'text-emerald-400 underline decoration-transparent underline-offset-2 transition-colors hover:decoration-emerald-500/60',
a: 'text-violet-400 underline decoration-transparent underline-offset-2 transition-colors hover:decoration-violet-500/60',
codespan: 'rounded-md border border-slate-700 bg-slate-800 px-1.5 py-0.5 text-[0.85em] font-mono text-slate-100',
pre: 'my-5 overflow-x-auto rounded-lg border border-slate-800 bg-slate-900/80 p-4 text-[0.825rem] leading-relaxed font-mono text-slate-100',
table: 'my-5 block w-full overflow-x-auto rounded-lg border border-slate-800 text-sm',

View File

@ -11,7 +11,7 @@ export interface FaqItem {
export const FAQ_ITEMS: FaqItem[] = [
{
q: 'Do I need my own WhatsApp Business API access?',
a: 'Yes. WaCRM plugs into your existing Meta WhatsApp Business setup — you bring the phone number and access token, we provide the CRM tooling around it. Any Meta-approved BSP (Business Solution Provider) works.',
a: 'Yes. This CRM template for WhatsApp plugs into your existing Meta WhatsApp Business setup — you bring the phone number and access token, we provide the CRM tooling around it. Any Meta-approved BSP (Business Solution Provider) works.',
},
{
q: 'Can my whole team share one WhatsApp number?',

View File

@ -9,13 +9,13 @@
export const SITE_URL =
process.env.NEXT_PUBLIC_SITE_URL?.replace(/\/$/, '') || 'https://wacrm.tech'
export const SITE_NAME = 'WaCRM'
export const SITE_NAME = 'CRM Template for WhatsApp'
export const SITE_TAGLINE = 'Run your WhatsApp® business from one inbox'
/** Default description — reused by layout metadata and structured data. */
export const SITE_DESCRIPTION =
'WaCRM is a WhatsApp® CRM for small teams: shared inbox, contact hub, sales pipelines, broadcasts, and no-code automations — built on the official WhatsApp Business API.'
'A CRM template for WhatsApp® built for small teams: shared inbox, contact hub, sales pipelines, broadcasts, and no-code automations — built on the official WhatsApp Business API.'
/**
* Keyword targets. Search engines largely ignore the meta keywords
@ -40,7 +40,7 @@ export const OG_IMAGE_ALT = `${SITE_NAME} — ${SITE_TAGLINE}`
/** Organization info surfaced in JSON-LD. */
export const ORG_INFO = {
name: SITE_NAME,
legalName: 'WaCRM',
legalName: 'CRM Template for WhatsApp',
url: SITE_URL,
logo: `${SITE_URL}/icon`,
}