Commit Graph

477 Commits

Author SHA1 Message Date
Arnas Donauskas
8309b2a95b
Merge pull request #65 from ArnasDon/fix/webhook-fail-closed
fix(webhook): require META_APP_SECRET, fail closed
2026-04-20 18:56:28 +03:00
Arnas Donauskas
be7edfd180
fix(webhook): require META_APP_SECRET, fail closed
The webhook signature verifier used to fall open with a warning log
when META_APP_SECRET was unset — a deliberate soft-rollout choice that
is unsafe for a public template. A forker who forgets the env var
would be running a fully spoofable webhook: anyone knowing the URL
could POST fabricated status updates and drift broadcast counts.

Now:
- `verifyMetaWebhookSignature` returns false when the secret is missing
  and logs at error level, so the route responds 401 to every POST
  until the operator configures it.
- Docs mark META_APP_SECRET as **required**:
  - docs/environment-variables.md lists it under Required, not
    Recommended.
  - docs/whatsapp-setup.md §6 is now "Signature verification (required)".
  - docs/troubleshooting.md updated accordingly.
- .env.local.example lists the var with guidance on where to find it.

Also fixes a pre-existing gitignore bug: `.env*` was swallowing
`.env.local.example`, which meant forkers running
`cp .env.local.example .env.local` from the getting-started guide
hit "no such file". Added a negation for `*.example`.

Upgrade note for existing deploys: set META_APP_SECRET (Meta → App
Settings → Basic → App Secret) before updating, or inbound webhooks
will 401 until it's configured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:54:52 +03:00
Arnas Donauskas
0b8377d2f1
Merge pull request #64 from ArnasDon/feat/profile-management
feat(settings): user profile management
2026-04-20 17:43:27 +03:00
Arnas Donauskas
72a5e4cf70
feat(settings): user profile management
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>
2026-04-20 17:40:39 +03:00
Arnas Donauskas
c2839e6305
Merge pull request #63 from ArnasDon/fix/drop-default-favicon
fix(landing): use WaCRM favicon, drop Next.js default
2026-04-20 09:18:30 +03:00
Arnas Donauskas
fe14d4894f
fix(landing): drop hardcoded shortcut-icon pointing at removed favicon
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>
2026-04-20 08:59:24 +03:00
Arnas Donauskas
27479e3ebd
fix(landing): drop leftover next.js favicon.ico
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>
2026-04-20 08:58:02 +03:00
Arnas Donauskas
f7d576190a
Merge pull request #62 from ArnasDon/chore/require-code-owner-review
chore: require Arnas as reviewer on every PR
2026-04-19 22:22:46 +03:00
Arnas Donauskas
5f3486bb36
chore: require Arnas as reviewer on every PR (CODEOWNERS)
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>
2026-04-19 22:09:10 +03:00
Arnas Donauskas
c28e4caa32
Merge pull request #61 from ArnasDon/feat/docs-viewer
feat(docs): in-app docs viewer at /docs
2026-04-19 21:52:27 +03:00
Arnas Donauskas
45fd2a9845
feat(docs): in-app docs viewer at /docs
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>
2026-04-19 21:28:36 +03:00
Arnas Donauskas
9fc3eb772b
Merge pull request #60 from ArnasDon/fix/hostinger-managed-nodejs
fix(hostinger): recommend Managed Node.js Hosting, not VPS
2026-04-19 21:03:44 +03:00
Arnas Donauskas
029a20e407
fix(hostinger): recommend Managed Node.js Hosting, not VPS
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>
2026-04-19 21:02:10 +03:00
Arnas Donauskas
f18adf3b47
Merge pull request #59 from ArnasDon/docs/full-setup-guide
docs: end-to-end self-host setup guide
2026-04-19 20:55:45 +03:00
Arnas Donauskas
02d008b51b
Merge pull request #58 from ArnasDon/feat/landing-open-source-section
feat(landing): open-source + Hostinger deploy section
2026-04-19 20:53:24 +03:00
Arnas Donauskas
7148c209a0
docs: end-to-end self-host setup guide
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>
2026-04-19 20:52:08 +03:00
Arnas Donauskas
dd65e532a1
feat(landing): open-source + Hostinger deploy section
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>
2026-04-19 20:45:00 +03:00
Arnas Donauskas
5a2d11ea64
Merge pull request #57 from ArnasDon/fix/broadcasts-table-responsive
feat(mobile): tiered columns on the broadcasts table
2026-04-19 12:59:34 +03:00
Arnas Donauskas
9173e8e4aa
Merge pull request #51 from ArnasDon/feat/mobile-automation-builder
feat(mobile): automation builder fits + flows on narrow screens
2026-04-19 12:58:11 +03:00
Arnas Donauskas
9f4c34e945
Merge pull request #50 from ArnasDon/feat/mobile-pipelines-kanban
feat(mobile): snap-scroll kanban with viewport-sized columns
2026-04-19 12:56:59 +03:00
Arnas Donauskas
a7dec71589
Merge pull request #49 from ArnasDon/feat/mobile-inbox-single-pane
feat(mobile): inbox single-pane flow on phone, unchanged on desktop
2026-04-19 12:55:21 +03:00
Arnas Donauskas
6a30858341
Merge pull request #56 from ArnasDon/feat/mobile-app-shell-reapply
feat(mobile): app shell drawer + responsive header (re-apply)
2026-04-19 12:53:12 +03:00
Arnas Donauskas
8a43cc6e2f
feat(mobile): tiered columns on the broadcasts table
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>
2026-04-19 12:51:49 +03:00
Arnas Donauskas
08eb6e192d
feat(mobile): app shell drawer + responsive header
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>
2026-04-19 12:44:31 +03:00
Arnas Donauskas
6a38fce6f6
Merge pull request #55 from ArnasDon/fix/opengraph-image-build-crash
feat(seo): full SEO pass (reapply with OG-image build fix)
2026-04-19 12:29:57 +03:00
Arnas Donauskas
15e4b5366d
feat(seo): full SEO pass (reapply with OG-image build fix)
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>
2026-04-19 12:25:59 +03:00
Arnas Donauskas
3d90cc17dd
Merge pull request #54 from ArnasDon/revert-47-feat/seo-pass
Revert "feat(seo): full SEO pass — metadata, structured data, sitemap, robots"
2026-04-19 12:15:54 +03:00
Arnas Donauskas
adf78bb2ee
Revert "feat(seo): full SEO pass — metadata, structured data, sitemap, robots" 2026-04-19 12:15:42 +03:00
Arnas Donauskas
ad6c5eba75
Merge pull request #53 from ArnasDon/revert-48-feat/mobile-app-shell
Revert "feat(mobile): app shell drawer + responsive header"
2026-04-19 12:14:35 +03:00
Arnas Donauskas
aca0d3be8b
Revert "feat(mobile): app shell drawer + responsive header" 2026-04-19 12:14:24 +03:00
Arnas Donauskas
d913e6c8c3
Merge pull request #48 from ArnasDon/feat/mobile-app-shell
feat(mobile): app shell drawer + responsive header
2026-04-19 12:12:51 +03:00
Arnas Donauskas
150b3832d3
feat(mobile): automation builder fits + flows on narrow screens
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>
2026-04-19 11:57:25 +03:00
Arnas Donauskas
4d901e16bd
feat(mobile): snap-scroll kanban board with viewport-sized columns
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>
2026-04-19 11:53:10 +03:00
Arnas Donauskas
7612e6c781
feat(mobile): inbox single-pane flow on phone, unchanged on desktop
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>
2026-04-19 11:47:35 +03:00
Arnas Donauskas
73ae399a4d
feat(mobile): app shell drawer + responsive header
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>
2026-04-19 11:39:08 +03:00
Arnas Donauskas
f223d5b86f
Merge pull request #47 from ArnasDon/feat/seo-pass
feat(seo): full SEO pass — metadata, structured data, sitemap, robots
2026-04-19 11:24:50 +03:00
Arnas Donauskas
c2587f7466
feat(seo): full SEO pass — metadata, structured data, sitemap, robots
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>
2026-04-19 11:17:48 +03:00
Arnas Donauskas
ba08d0b45f
Merge pull request #46 from ArnasDon/feat/marketing-landing-page
feat(marketing): full public landing page at /
2026-04-19 02:35:08 +03:00
Arnas Donauskas
d5d406fe8e
feat(marketing): full public landing page at /
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>
2026-04-18 14:51:30 +03:00
Arnas Donauskas
221afcefc4
Merge pull request #45 from ArnasDon/fix/dashboard-chart-hover-precision
fix(dashboard): precise hover on the conversations-over-time chart
2026-04-17 23:24:43 +03:00
Arnas Donauskas
2aa3c50878
fix(dashboard): precise hover on the conversations-over-time chart
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>
2026-04-17 23:19:13 +03:00
Arnas Donauskas
eb8d0b3ac6
Merge pull request #44 from ArnasDon/fix/dashboard-activity-feed-pagination
fix(dashboard): paginate recent activity feed with a size picker
2026-04-17 22:39:18 +03:00
Arnas Donauskas
b1eb02f48e
Merge pull request #43 from ArnasDon/fix/dashboard-pipeline-card-height
fix(dashboard): pipeline card stretches to match the chart row height
2026-04-17 22:37:26 +03:00
Arnas Donauskas
96debd193b
fix(dashboard): paginate recent activity feed with a size picker
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>
2026-04-17 22:35:42 +03:00
Arnas Donauskas
2954123d9b
fix(dashboard): pipeline card stretches to match the chart row height
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>
2026-04-17 22:29:54 +03:00
Arnas Donauskas
3e02098579
Merge pull request #42 from ArnasDon/feat/dashboard-analytics
feat(dashboard): replace placeholder with real analytics
2026-04-17 22:20:50 +03:00
Arnas Donauskas
ed669424b6
feat(dashboard): replace placeholder with real analytics
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>
2026-04-17 22:16:00 +03:00
Arnas Donauskas
5be2bad51d
Merge pull request #41 from ArnasDon/fix/automations-validate-step-config-on-activate
fix(automations): reject activation of invalid step/trigger config
2026-04-17 21:58:46 +03:00
Arnas Donauskas
348dbb7d6c
Merge pull request #40 from ArnasDon/fix/automations-atomic-execution-counter
fix(automations): atomic execution_count via SQL function (migration 007)
2026-04-17 21:56:37 +03:00
Arnas Donauskas
98751b8fe6
Merge pull request #39 from ArnasDon/fix/automations-welcome-template-uses-first-inbound
fix(automations): Welcome Message template uses first_inbound_message
2026-04-17 21:54:28 +03:00