Adds a profile tab to /settings with the five things every B2B SaaS
user expects:
- Display name — edits profiles.full_name.
- Avatar — upload / replace / remove, stored in a new `avatars`
Supabase Storage bucket with RLS scoped to the owner's folder.
Migration 008_profile_avatars_storage.sql creates the bucket (public
read, 2 MB cap, image mimes only) and the four storage.objects
policies.
- Email — goes through supabase.auth.updateUser({ email }), which
confirms to both the old and new addresses. UI surfaces a "check
both inboxes" banner after save.
- Password — three-field form with a current-password gate implemented
via a throwaway signInWithPassword() before calling updateUser.
- Active sessions — one-click "sign out of all devices" using
supabase.auth.signOut({ scope: 'global' }), with a confirm dialog.
Plus read-only role, joined date, and user id for operator debugging.
The header avatar and sidebar user row are now both dropdown menus
linking to /settings?tab=profile and /settings?tab=whatsapp; the
settings page reads ?tab= to land on the right tab. useAuth gets a
refreshProfile() callback so the header/sidebar update immediately
after a save without a full reload.
Deferred (separate work): account deletion, 2FA, phone/timezone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
layout.tsx still had `icons.shortcut: ["/favicon.ico"]`, which kept
emitting the default Next.js favicon link in <head> even after the
file was deleted. icon.tsx is the single source now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
We already have src/app/icon.tsx generating the WaCRM emerald mark,
but the default create-next-app favicon.ico was still in src/app/
so Next.js kept emitting <link rel="shortcut icon" href="/favicon.ico">
in <head>. Browsers prefer the ICO, so tabs still showed the default
Vercel triangle on the landing page.
Removing favicon.ico leaves icon.tsx as the sole source, which is
what the comment in that file already claimed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a CODEOWNERS file mapping every path to @ArnasDon so GitHub
auto-requests his review on every PR. Pair with a branch-protection
rule on main that requires Code Owner approval to make review
mandatory before merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders every file in docs/*.md as a proper documentation site styled
like Stripe / Vercel / Linear docs: sticky header, grouped left sidebar
(Setup / Deploy / Operate), dark prose with inline code pills, syntax
-ready code blocks, responsive table styling, and prev/next pagination.
- src/lib/docs/content.ts — parses md with marked and decorates the
HTML with Tailwind utility classes via post-processing regex. Also
rewrites inter-doc `./other.md` links to `/docs/other` and extracts
an h2/h3 TOC for future use.
- src/app/docs/layout.tsx + src/components/docs/shell.tsx — shared
frame with docs-scoped header (GitHub, Home, Get started), collapsing
sidebar on mobile.
- src/app/docs/page.tsx — grouped index cards per section.
- src/app/docs/[slug]/page.tsx — individual doc + prev/next nav.
- Adds "Docs" to landing nav, footer, and a "Read the full
documentation" CTA under the open-source cards.
- Adds `marked` dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swaps the Hostinger recommendation on the landing page and throughout the
docs from VPS to Managed Node.js Hosting
(https://www.hostinger.com/web-apps-hosting).
The deploy walkthrough is rewritten around hPanel's Node.js app flow
(Git-based deploy, env vars panel, AutoSSL, cron via Advanced → Cron Jobs)
instead of a raw Ubuntu VPS with nginx + PM2 + certbot. VPS stays
mentioned at the bottom of the deploy page as an alternative when a
user actually needs one (background workers, custom binaries, etc.).
Also touched:
- docs/README.md, environment-variables.md, automations-and-cron.md,
troubleshooting.md to drop VPS-specific assumptions.
- footer link URL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds docs/ with a reading-order flow that covers:
- getting started (fork, install, run locally)
- supabase setup + migrations
- whatsapp business api + webhook wiring
- environment variable reference
- hostinger vps deploy walkthrough
- automations cron scheduling
- troubleshooting for the common issues
Rewrites the top-level README as a pointer into docs/ instead of the
create-next-app boilerplate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a #self-host landing section with two cards — one linking to
the GitHub repo (fork / clone) and one recommending Hostinger VPS
for deploying the template. Also surfaces the GitHub link from the
nav (desktop icon + mobile drawer) and the footer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every column rendered at every width, relying on horizontal scroll.
A 375px phone had to swipe sideways just to read a name. Matches
the tiering contacts already uses:
- Mobile (<sm): Name + Status only.
- sm+: adds Recipients, Date.
- md+: adds Template.
- lg+: adds Delivery + Read rate bars.
Desktop (≥lg) unchanged — all seven columns visible as before.
Salvaged from the closed PR #52 — the OG-image portion of that PR
is already on main via PR #55, so only the broadcasts-table change
is shipped here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First of a series to make the app mobile-friendly. The sidebar used
to be a fixed 240px column always visible, eating most of a 375px
phone screen and leaving ~135px for content. Now:
- Sidebar becomes a slide-in drawer on <lg, static on ≥lg. Controlled
via a drawer-open prop wired from the shell; transform + duration
feels native on touch devices.
- Backdrop overlay closes it on tap; Escape closes it; body scroll
locks while open; route changes auto-close it.
- Header gains a 44×44 hamburger button on <lg that opens the drawer,
hidden on ≥lg. Title shrinks slightly on narrow widths so the
hamburger + avatar + sign-out row never wraps.
- Nav rows are taller (py-2.5) on mobile for comfortable touch
targets, slim (py-2) back on desktop.
- Main content padding is p-4 on mobile, p-6 on desktop so cards
have room to breathe without shouldering into the viewport edge.
No desktop changes visible beyond tighter padding on very narrow
lg breakpoints (main content width is unchanged).
Next PRs in this series will adapt:
- Inbox (3-pane → single-pane with back)
- Pipelines (kanban snap-scroll)
- Automation builder (card widths + bottom-sheet edit)
- Tables + forms polish
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-applies the original SEO work from PR #47 that was reverted in
PR #54 because its opengraph-image.tsx crashed `next build` during
static prerender of /opengraph-image. Satori (next/og's renderer)
errored with:
Error: Expected <div> to have explicit "display: flex",
"display: contents", or "display: none" if it has more
than one child node.
Failed to load dynamic font for ✓ . Status: 400
Root cause in the OG image JSX:
- Several <div>s held text + a sibling element (e.g. "Run your
WhatsApp business from <span>one inbox.</span>") without an
explicit display value. Satori requires it whenever there is
more than one child.
- The ✓ Unicode glyph isn't covered by the default font Satori
loads, so next/og tried to download a dynamic font and failed.
Fix:
- Every <div> with more than one child now has display: flex.
- Multi-span headline sits in a flex-wrap row of two <span>s.
- ✓ replaced with plain text dot separators in the feature row.
Verified end-to-end: `npm run build` now completes with 27/27
pages prerendered, /opengraph-image among them.
All the other SEO-pass content is identical to the originally
merged-and-reverted work: metadataBase + title template, OpenGraph
+ Twitter Cards defaults, JSON-LD (WebSite + Organization +
SoftwareApplication + FAQPage), sitemap.ts, robots.ts, noindex on
auth + dashboard route-group layouts, shared faq-data + site-config
modules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The automation flow builder had fixed-width cards (320px for steps,
400px for conditions) and a crammed top bar. On a 375px phone the
condition cards overflowed horizontally and the top bar's Active
switch label left the name input with almost no room.
- Step cards: w-full max-w-[320px] sm:w-80 — full-width on mobile,
original 320px on sm+.
- Condition cards: w-full max-w-[400px] sm:w-[400px].
- Trigger card matches the step cards.
- Condition branches (Yes/No) stack vertically on mobile — two
columns at 375 would cram each branch to ~170px. Two columns
return on sm+.
- Top bar tightens on mobile: "Active" label hidden under sm so
the switch + save button + name input all fit; back-arrow grows
from h-8 w-8 → h-9 w-9 for a comfortable touch target; name input
is text-sm on mobile, text-base on sm+.
Desktop unchanged — every mobile-specific class is overridden by
its sm: counterpart.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pipelines board was already horizontally scrollable, but on a
phone it felt imprecise — swipes landed mid-column, there was no
visual hint that more columns existed past the viewport, and 260px
columns felt cramped at 375px width.
- Each StageColumn is now w-[85vw] min/max-clamped on mobile, so the
next column's edge peeks in as a "more here" affordance. On lg+
the columns revert to flex-1 basis-[260px] (share the row).
- Scroll container gets snap-x + snap-mandatory on mobile, snap-none
on lg+. Each column has snap-start so swipes always land cleanly
at the left edge of the next column.
- shrink-0 on mobile prevents flex from compressing the columns when
space is tight.
No desktop visual change — all the mobile-specific classes are
overridden back by their lg: counterparts, and the original
flex-1 basis-[260px] behavior is preserved above lg.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The inbox was a 3-pane flex row (list + thread + contact sidebar).
On a 375px phone both list and thread tried to share ~187px each
and the whole UI was unusable. Now:
- On <lg the inbox shows ONE pane at a time: list when nothing's
selected, thread when a conversation is open.
- Thread gains a back-arrow in its header (mobile only) that pops
back to the list and clears the ?c= URL param so refreshing lands
on the list, not on the thread the user just backed out of.
- Conversation list grows to w-full on mobile (was fixed w-80)
so each row uses the whole viewport width.
- Thread header tightens: name/phone truncate with min-w-0, session
timer badge is hidden under sm to save room for back arrow + name.
- Dashboard shell's p-6 is already p-4 on mobile (PR #48); matched
the -m-6 offset in the inbox to -m-4 sm:-m-6.
Desktop layout (lg+) is unchanged: list + thread + contact sidebar
side-by-side exactly as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First of a series to make the app mobile-friendly. The sidebar used
to be a fixed 240px column always visible, eating most of a 375px
phone screen and leaving ~135px for content. Now:
- Sidebar becomes a slide-in drawer on <lg, static on ≥lg. Controlled
via a drawer-open prop wired from the shell; transform + duration
feels native on touch devices.
- Backdrop overlay closes it on tap; Escape closes it; body scroll
locks while open; route changes auto-close it.
- Header gains a 44×44 hamburger button on <lg that opens the drawer,
hidden on ≥lg. Title shrinks slightly on narrow widths so the
hamburger + avatar + sign-out row never wraps.
- Nav rows are taller (py-2.5) on mobile for comfortable touch
targets, slim (py-2) back on desktop.
- Main content padding is p-4 on mobile, p-6 on desktop so cards
have room to breathe without shouldering into the viewport edge.
No desktop changes visible beyond tighter padding on very narrow
lg breakpoints (main content width is unchanged).
Next PRs in this series will adapt:
- Inbox (3-pane → single-pane with back)
- Pipelines (kanban snap-scroll)
- Automation builder (card widths + bottom-sheet edit)
- Tables + forms polish
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the site from bare-bones metadata to production-ready SEO.
Covers technical crawlability, rich-result eligibility, social
sharing, and duplicate-content prevention.
Added:
- src/lib/seo/site-config.ts — single source of truth for base URL,
brand, description, keywords. Referenced by every consumer so a
domain / rename is a one-file edit.
- src/lib/seo/faq-data.ts — FAQ Q&A extracted from the component so
the UI and the FAQPage JSON-LD render the same content.
- src/lib/seo/structured-data.ts — builders for WebSite,
Organization, SoftwareApplication, and FAQPage JSON-LD nodes.
FAQPage is eligible for Google's FAQ rich result; SoftwareApplication
with a free Offer can earn a "Free" badge on SERP.
- src/components/seo/json-ld.tsx — emits one <script
type="application/ld+json"> per node (Google's preferred shape).
- src/app/opengraph-image.tsx — Next auto-generates a 1200×630 PNG
for the landing page's OG / Twitter previews.
- src/app/sitemap.ts — lists only public, indexable pages.
- src/app/robots.ts — Allow /, Disallow every app / auth route,
references the sitemap.
Updated:
- src/app/layout.tsx — metadataBase, title template (%s — WaCRM),
default OpenGraph + Twitter Cards (inherited everywhere), canonical,
icons config, viewport (themeColor + colorScheme=dark for browser
chrome on mobile).
- src/app/page.tsx — injects the 4 JSON-LD blocks, sets explicit
canonical for /, overrides the title.
- src/app/(auth)/layout.tsx — noindex+nofollow for login / signup /
forgot-password (no SEO value; would compete with landing page).
- src/app/(dashboard)/layout.tsx — split into thin server layout
(exports noindex metadata) + DashboardShell client component. App
routes won't be indexed even if a URL leaks.
- src/components/landing/faq.tsx — consumes the shared FAQ data
module (no more drift between UI and JSON-LD).
Verified end-to-end against the running dev server:
- GET / HTML contains: canonical, description, keywords, robots,
og:title/url/image/type, twitter:card/title, theme-color, + 4
JSON-LD blocks (WebSite, Organization, SoftwareApplication,
FAQPage).
- GET /opengraph-image → 200 OK, content-type image/png.
- GET /login HTML contains <meta name="robots" content="noindex,
nofollow, nocache">.
- GET /sitemap.xml serves valid XML with /.
- GET /robots.txt serves Allow /, Disallow all app + auth paths,
sitemap reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the root redirect to /login with a real marketing landing so
first-time visitors can learn about WaCRM and convert. Authed users
still see it and get a "Go to Dashboard" CTA instead of "Sign in" /
"Get started".
Sections, in order:
- Sticky nav with smooth scroll to Features / How it works / FAQ.
Auth-aware CTA: pending/signed-in/signed-out states render the
right buttons without layout shift.
- Hero with clear one-liner, benefit subhead, primary + secondary
CTA, trust cues, and a miniature inbox preview (pure CSS/SVG
recreation of the real UI — no screenshot to age out).
- Features grid: 6 benefit-first cards.
- 4x feature spotlights (alternating): shared inbox, automations,
pipelines, analytics. Each with a bespoke product miniature.
- How It Works: 3 steps framed around "live in 30 minutes".
- FAQ: 6 questions answering the real objections (BSP access, team
sharing, setup speed, data ownership, templates, broadcasts).
- Final CTA banner + footer with nav + product/account link columns.
Implementation notes:
- All visuals are CSS/SVG miniatures in src/components/landing/mock —
intentionally not real screenshots so the page doesn't drift from
the product over time.
- Dark theme throughout to preserve the app's brand (per the repo's
dark-only convention). No light sections.
- Per-viewport verified: desktop (1440x900) two-column hero with
mock; mobile (375x812) stacks hero and reveals a hamburger menu.
- Middleware untouched — the page itself did the /login redirect;
replacing page.tsx is enough.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hover was off by ~one letterbox width on wide layouts — users had to
drag the cursor far right of the last data point to see its tooltip.
Root cause: the SVG uses a fixed 760×240 viewBox with preserveAspect-
Ratio's default "xMidYMid meet", which centers the content and leaves
empty letterbox bars on either side when the container is wider than
the viewBox aspect. The old hover math mapped clientX linearly onto
the full SVG DOM rect, so every pixel of letterbox shifted the snap
further from the real data point.
Switched to SVGSVGElement.getScreenCTM().inverse() to map screen
coords back to viewBox units correctly regardless of letterboxing,
scaling, or future transforms. The tooltip's horizontal offset also
now comes from the same CTM (mapping the snapped data point's viewBox
x back to a pixel offset inside the wrapper div), so the tooltip and
crosshair line up exactly on top of each other.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With real data the feed showed 20 rows by default and filled most of
the viewport. Switched to a 5-row default with an inline size picker
(5 / 10 / 20 / 50) so users choose how much history is visible.
- Dashboard page bumps the fetch from 20 → 50 so every size option is
already in memory. Switching sizes is a pure client-side slice, no
round trip.
- ActivityFeed owns the pageSize state, renders "Showing N of M"
plus a button group in the footer. The smallest option is always
enabled; larger options unlock only once we have enough rows to
meaningfully reveal more (e.g. "10" enables once loaded > 5).
- Existing "View all" header link left alone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With grid items-stretch (the default) the two cards in the charts
row try to share the tallest sibling's height — but the Pipeline
Value card was rendering at its natural (shorter) height because
neither its col-wrapper nor its inner section had h-full. So the
line chart's taller content drove the row but the pipeline card
hung at the top with trailing whitespace.
Added h-full on both col wrappers and flex h-full flex-col on
each inner section so they actually fill the row. The existing
flex-1 wrapper inside PipelineDonut now has a defined height to
stretch against, so the empty state (and the populated donut +
legend) span the card cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites the dashboard page as a data-driven analytics surface using
existing Supabase tables only — no schema changes, no other pages
touched.
Widgets (all render independent skeletons while their data fetches,
and show a shared empty state when there's not enough data to be
useful):
- Metric cards (Active Conversations, New Contacts Today, Open Deals
Value, Messages Sent Today) with up/down/no-change delta indicator.
- Quick actions row (New Contact / Deal / Broadcast / Automation).
- Conversations Over Time: SVG line chart, 7/30/90-day tabs, per-day
hover crosshair + tooltip. Cached per range so tab switches are
instant.
- Pipeline Value: SVG donut with per-stage arc segments, total in the
center, legend with deal count + value.
- Average First Response Time: SVG bar chart by weekday with a
configurable dashed target line and this-week / last-week summary.
- Activity feed: latest 20 items merged across messages, contacts,
deals, broadcasts, and automation logs — click-through deep-links
where applicable.
Deviations from spec, noted up-front:
- Strictly dark theme — spec's #f9fafb alternating rows translate to
slate-900 / slate-900/40 stripes.
- Kept Inter (tabular-nums, bold 28px) for metric values instead of
Space Grotesk to avoid touching root layout.
- Quick actions link to the relevant list pages (/contacts, etc.)
rather than auto-opening their create modals — that would require
modifying those pages.
All aggregation is client-side via RLS-scoped queries. If a tenant's
dataset outgrows a few thousand messages per range window, the
response-time and line-chart queries should migrate to SQL RPCs —
noted as follow-up, not shipped here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>