Commit Graph

537 Commits

Author SHA1 Message Date
Ivan Mercedes
9357fd9ec9 feat(i18n): complete settings module internationalization 2026-07-07 19:56:55 -04:00
Ivan Mercedes
caf0f3660e feat(i18n): complete internationalization of flow builder components 2026-07-07 19:43:26 -04:00
Ivan Mercedes
d518d54520 i18n: Localize AutomationBuilder component and fix typing 2026-07-07 17:51:09 -04:00
Ivan Mercedes
92e5789837 feat(i18n): localize Broadcasts module and wizard
- Add 'Broadcasts' namespace to en.json for all messaging flows
- Localize broadcast list and detail views (stats, recipient tables, funnel)
- Localize 4-step Broadcast creation wizard (Choose Template, Select Audience, Personalize, Schedule)
- Refactor status configuration to use translation keys instead of hardcoded labels
- Pass full typechecking across all wizard components
2026-07-07 17:29:45 -04:00
Ivan Mercedes
8b1d75456f feat(i18n): localize pipelines module and components
- Added Pipelines namespace to messages/en.json encompassing page, board, card, form, settings, and analytics.
- Migrated PipelinesPage empty states, dropdowns, and pipeline creation dialogues.
- Localized pipeline-board and deal-card UI strings and badges (Won/Lost).
- Translated deal-form inputs, dynamic tooltips, select options, and error toasts.
- Localized pipeline-settings including the sortable stage management rows and delete confirmations.
- Migrated pipeline-analytics metrics and detailed statistical tooltips.
- Resolved typecheck dependencies to ensure the t function is correctly passed to nested sortable components.
2026-07-07 17:14:48 -04:00
Ivan Mercedes
dcfd66792b feat: internationalize contact 2026-07-07 17:02:02 -04:00
Ivan Mercedes
57a914d547 feat(i18n): translate inbox module and components
- Add Inbox namespace to en.json dictionary
- Localize InboxPage and WhatsApp connection banners
- Translate ConversationList, search placeholders, and filters
- Localize MessageThread, session timers, and agent assignment dropdowns
- Translate MessageComposer, attachment menus, and AI draft hints
- Update MessageBubble, MessageActions, ReplyQuote, and TemplatePicker
- Verify strict typing for next-intl hooks across all inbox files
2026-07-07 15:09:24 -04:00
Ivan Mercedes
afa136245b feat(i18n): translate dashboard overview components
- Added `Dashboard` namespace to English dictionary.

- Translated metric cards, quick actions, and activity feed.

- Localized charts (conversations, pipeline donut, response time) and empty states.
2026-07-07 14:31:01 -04:00
Ivan Mercedes
738b0dde1f refactor: update translation key type assertions from any to string in header and sidebar components 2026-07-07 13:49:56 -04:00
Ivan Mercedes
07b20f42d4 feat(i18n): translate dashboard layout components
- Added `Sidebar`, `Header`, and `ModeToggle` namespaces to English dictionary.
- Refactored `sidebar.tsx` and `header.tsx` to use dynamic translations.
- Updated accessibility labels for navigation and theme switcher.
2026-07-07 13:43:06 -04:00
Arnas Donauskas
79565c1fbe
Merge pull request #328 from ivanmercedes/feat/i18n-support
feat: integrate next-intl for i18n support
2026-07-07 12:57:04 +03:00
Arnas Donauskas
4e2192d84f
Merge pull request #347 from ArnasDon/fix/flows-add-node-crash
fix(flows): wrap add-node menu groups in DropdownMenuGroup (#336)
2026-07-07 11:49:36 +03:00
Arnas Donauskas
f90563b35c fix(flows): wrap add-node menu groups in DropdownMenuGroup (#336)
Clicking "Add node" in the flow builder crashed the whole page to a
"This page couldn't load" error boundary, in both the list and canvas
views.

The add-node menus render a DropdownMenuLabel per category. That is
base-ui's Menu.GroupLabel, which calls useMenuGroupRootContext() during
render and THROWS ("MenuGroupContext is missing") when it has no
Menu.Group ancestor. Both menus wrapped each category in a plain <div>
instead of a DropdownMenuGroup, so the label had no group context. The
menu content only mounts when the menu opens, which is why initial
render was fine and the crash fired exactly on click.

Wrap each category's label + items in DropdownMenuGroup (base-ui
Menu.Group), which provides the context Menu.GroupLabel needs. The
between-group separator moves into a keyed Fragment so it stays a
sibling of the groups.

Adds a regression test pinning the contract: a bare DropdownMenuLabel
throws, a group-wrapped one does not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:45:01 +03:00
Arnas Donauskas
2ebdc2ebf9
Merge pull request #346 from ArnasDon/fix/ai-autoreply-slot-grant
fix(ai): grant EXECUTE on claim_ai_reply_slot so auto-reply works (#345)
2026-07-07 10:46:08 +03:00
Arnas Donauskas
95faf58e75 fix(ai): grant EXECUTE on claim_ai_reply_slot so auto-reply works (#345)
Migration 029 created claim_ai_reply_slot(uuid, integer) as SECURITY
DEFINER but never GRANTed EXECUTE — the only function in the schema
missing its grant (007/012/017/018/019/025/030 all grant theirs).

SECURITY DEFINER sets the privileges a function runs *with*, not who may
*call* it: the caller still needs EXECUTE. On instances where the default
PUBLIC execute privilege on public-schema functions has been revoked
(hardened / self-hosted Supabase), the service_role that PostgREST uses
cannot invoke it. The AI auto-reply path runs entirely under the
service-role client (the inbound webhook has no auth.uid()), so
db.rpc('claim_ai_reply_slot') fails permission-denied, the dispatch bails
before sending, and the bot silently never answers ANY inbound message.
The Playground works because it never claims a slot — matching the report.

- 031_ai_reply_slot_grant.sql: new migration granting EXECUTE to
  service_role (029 is already applied on existing installs, so it needs
  its own migration to reach them). service_role only — the sole caller —
  matching the 007/012 increment-counter precedent.
- 029_ai_reply.sql: add the same grant next to the definition so fresh
  installs are correct.
- auto-reply.ts: log a real claim RPC error instead of returning
  silently, so this class of deploy failure is diagnosable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 10:43:03 +03:00
Arnas Donauskas
274db1c7ce
Merge pull request #334 from ArnasDon/feat/ai-agents-sidebar
feat: AI Agents sidebar section (setup + playground)
2026-07-02 18:22:51 +03:00
Arnas Donauskas
10d31bcfc8 feat: AI Agents sidebar section (setup + playground)
Promotes the AI assistant out of Settings into a first-class "AI Agents"
sidebar area.

- Sidebar: new "AI Agents" item → /agents.
- /agents page with two tabs:
  - Playground — a test chat (POST /api/ai/playground) that runs the
    real auto-reply path (KB retrieval + provider, auto_reply prompt,
    multi-turn, shows handoff). loadAiConfig gains requireActive so you
    can test before enabling.
  - Setup — the existing provider/key + knowledge-base config, relocated.
- Removed the AI section from Settings (settings-sections + page); same
  account config, new home. No migration.

Tests +6 (query, loadAiConfig requireActive). 593 pass; typecheck, lint,
build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:20:18 +03:00
Arnas Donauskas
5dbc786fdc
Merge pull request #333 from ArnasDon/feat/ai-knowledge-base
feat: AI knowledge base (RAG grounding) — retarget to main
2026-07-02 17:50:18 +03:00
Arnas Donauskas
020dc2ae11 fix(ai-kb): address code-review findings
- Security: REVOKE ALL FROM PUBLIC + GRANT EXECUTE (authenticated,
  service_role) on the match RPCs so anon can't read a KB by supplying
  an account_id (mirrors migrations 018/025).
- Ingest: store chunks even when embedding fails (insert first, rethrow
  after) so a doc stays lexically searchable; the "lexical still works"
  warning is now truthful.
- Retrieval: short-circuit on an empty KB (cheap COUNT) to avoid a paid
  query embedding + 2 RPCs on every draft/auto-reply.
- Migration: HNSW instead of IVFFlat (robust on small/growing tables);
  language-neutral 'simple' FTS config (was 'english'); semantic RPC
  takes text and casts ::vector(1536) to remove PostgREST bind ambiguity.
- Embeddings key: distinguish corrupt-from-absent (loadEmbeddingsKey
  returns {key,corrupt}); log on decrypt failure; reindex now reports
  failure instead of false success; POST/PATCH warn on keyword-only save.
- embedTexts: fail loud when the provider omits result indices (was
  silently misaligning chunks with vectors).

Tests: +3 (empty-KB guard, embed-failure-still-indexes, missing-index).
587 pass; typecheck/lint/build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:20:38 +03:00
Arnas Donauskas
ce1fa4705d feat: AI knowledge base (RAG grounding)
Grounds the AI assistant (0.5.0) in the account's own content so it can
answer instead of handing off.

- Hybrid retrieval: Postgres full-text search for every account; optional
  semantic search (pgvector + OpenAI text-embedding-3-small) when an
  embeddings key is set. Semantic-primary, topped up with FTS.
- src/lib/ai: chunk, embeddings, knowledge (ingest + retrieve), query;
  buildSystemPrompt gains a knowledge block; config carries an
  (optional) embeddings key.
- Wired into /api/ai/draft and the auto-reply bot (best-effort — never
  blocks a reply).
- KB API (/api/ai/knowledge CRUD + /reindex) and a Settings → AI
  Assistant knowledge-base manager + embeddings-key field.
- Migration 030: pgvector, ai_knowledge_documents, ai_knowledge_chunks
  (fts + embedding), match RPCs, ai_configs.embeddings_api_key.

Migration required: supabase/migrations/030_ai_knowledge.sql.
Stacked on feat/ai-reply-assistant (#329).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:06:09 +03:00
Ivan Mercedes
260a7469a6 fix: sync package-lock.json to resolve npm ci error 2026-07-02 09:25:31 -04:00
Ivan Mercedes
50d71b1ed6 feat: add fallback logic for missing locale dictionaries and update NextIntlClientProvider configuration 2026-07-02 08:42:37 -04:00
Arnas Donauskas
1f24b1ff48
Merge pull request #329 from ArnasDon/feat/ai-reply-assistant
feat: AI reply assistant (bring-your-own-key)
2026-07-02 12:58:39 +03:00
Arnas Donauskas
2748028c1d feat: AI reply assistant (bring-your-own-key)
Adds an AI reply assistant powered by the account's own OpenAI or
Anthropic key (stored AES-256-GCM-encrypted; no per-seat AI fee).

- AI-drafted replies in the inbox composer (✨ button, agent+), via
  read-only POST /api/ai/draft.
- Optional auto-reply bot on inbound messages that no Flow consumed and
  no agent owns, with a per-conversation cap and clean [[HANDOFF]] to a
  human. Stands down when a message-level automation would also reply.
- Settings → AI Assistant (admin+): provider/model, key, business
  context, toggles, and a Test-key button that verifies before saving.
- Provider layer (src/lib/ai) with OpenAI + Anthropic adapters behind
  one interface; migration 029 (ai_configs + per-conversation columns +
  atomic claim_ai_reply_slot RPC).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:56:04 +03:00
Arnas Donauskas
041cdabe03
Merge pull request #327 from eldon922/fix/add-contact-gated-button
fix: gated button add your first contact
2026-07-02 09:08:54 +03:00
Ivan Mercedes
1ce9581117 chore: add NEXT_PUBLIC_APP_LOCALE to env example 2026-07-01 23:23:27 -04:00
Ivan Mercedes
8327c0906d feat(i18n): setup next-intl and migrate login page
This introduces the base i18n setup using next-intl without routing. It adds the default messages/en.json and sets up the app layout and next.config.ts to consume the locale from the environment. The login page has been migrated as a first example.
2026-07-01 17:46:00 -04:00
Chris Eldon
c5f2544ff9 fix: gated button add your first contact
(cherry picked from commit 90fd9bc4cc5b8270c44a8c8d1cbc864f38eb6e97)
2026-07-01 23:33:04 +07:00
Arnas Donauskas
54b8652852
Merge pull request #326 from ArnasDon/feat/public-api-webhooks
feat(api): outbound event webhooks — /api/v1/webhooks (#245)
2026-07-01 14:55:22 +03:00
Arnas Donauskas
c0f390c401 feat(api): outbound event webhooks — /api/v1/webhooks (#245)
Completes the public API roadmap (#245): register HTTPS endpoints to be
POSTed to when things happen in an account, so automations can react to
inbound activity instead of polling.

- Migration 028: account-scoped webhook_endpoints (secret stored
  AES-GCM-encrypted) + an atomic record_webhook_failure() function.
- Scope webhooks:manage; management API GET/POST /api/v1/webhooks and
  GET/PATCH/DELETE /api/v1/webhooks/{id} (secret returned once).
- Events message.received / conversation.created / message.status_updated
  dispatched from the inbound webhook handler's after() block.
- Signed delivery (X-Wacrm-Signature = HMAC-SHA256 over t.body), single
  best-effort attempt, auto-disable after consecutive failures.
- Versioned 0.4.0; docs/public-api.md Webhooks section; CHANGELOG.

Fixes from the adversarial code review of this diff:
- SSRF: block private/loopback/link-local (incl. cloud metadata) targets
  at delivery time + redirect:'manual' so a public URL can't bounce
  internal (src/lib/webhooks/ssrf.ts).
- Status mirror UPDATE no longer uses .select().maybeSingle() (message_id
  is non-unique); account resolved via a bounded limit(1) read.
- Atomic failure counter via SQL RPC (was a racy read-modify-write).
- conversation.created emitted before the reaction short-circuit.
- Per-delivery id added for receiver dedupe; docs state at-least-once /
  unordered delivery.
- Signature verify tolerates hex case / header whitespace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:53:18 +03:00
Arnas Donauskas
9f76468a41
Merge pull request #325 from ArnasDon/feat/public-api-rest
feat(api): public REST API v1 — messages, contacts, conversations, broadcasts (#245)
2026-07-01 14:17:46 +03:00
Arnas Donauskas
8cb5792823 feat(api): public REST API v1 — messages, contacts, conversations, broadcasts (#245)
Complete the public API roadmap on top of the existing key-auth layer
(#290). Re-lands POST /api/v1/messages (lost when the stacked #291 never
reached main) by extracting the dashboard send route's core into
`sendMessageToConversation`, then adds:

- Contacts: GET/POST /api/v1/contacts, GET/PATCH /api/v1/contacts/{id}
- Conversations: GET list + /{id} + /{id}/messages
- Broadcasts: POST /api/v1/broadcasts + GET /{id} (persist rows, fan out
  in after(), poll for status)

All list endpoints share one keyset-pagination contract
({ data, meta: { next_cursor } }). No migration required — scopes and
tables already exist. Versioned as 0.3.0 (folded into [Unreleased]);
docs/public-api.md updated; outbound webhooks left as the sole roadmap
item. API stays v1 (additive).

Includes fixes from an adversarial code review: cursor validation
against filter injection, broadcast counts deferred to the DB aggregate
trigger, terminal broadcast status reflects failures, duplicate-phone
de-dup, up-front send-param validation (no orphan conversations),
PATCH null-clear semantics, error-checked tag sync, bounded tag filter,
unified audit-user attribution, and a maxDuration on the broadcast
fan-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:10:19 +03:00
Arnas Donauskas
41815acc88
Merge pull request #324 from ArnasDon/feat/inbox-tag-company-filter
feat(inbox): filter conversations by contact tags and company
2026-07-01 13:06:14 +03:00
Arnas Donauskas
f8aaac131b feat(inbox): filter conversations by contact tags and company
Adds contact-based filters to the Inbox conversation list (issue #272):

- Multi-select tag filter with OR logic (a conversation matches if its
  contact has any selected tag), consistent with Broadcast audiences.
- Company filter (exact match) over companies present in the inbox.
- Filters stack with the existing status filter and search; active
  selections show as removable chips with a clear-all control.

Tags are hydrated via a shared CONVERSATION_SELECT embed and normalized
onto contact.tags, so the realtime hydrate path keeps new conversations
matching an active filter. Filtering logic lives in a pure, unit-tested
matchesContactFilters helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:03:53 +03:00
Arnas Donauskas
6fb71d21d2
Merge pull request #247 from ArnasDon/dependabot/npm_and_yarn/supabase-76c1b082ed
chore(deps): bump the supabase group across 1 directory with 2 updates
2026-07-01 12:00:13 +03:00
Arnas Donauskas
427b4612da
Merge pull request #310 from bellopushon/feat/automation-deal-selectors
feat(automations): add deal pipeline selectors
2026-07-01 11:46:13 +03:00
Arnas Donauskas
f4beb6f368
Merge pull request #322 from RiadDeveloper/main
Prevent redundant profile fetches on refresh
2026-07-01 11:27:20 +03:00
Arnas Donauskas
6ff44644c6
Merge pull request #279 from AlexTPaul/main
feat: Notifications
2026-07-01 11:24:36 +03:00
Arnas Donauskas
1e05ba028c
Merge pull request #282 from ArnasDon/dependabot/npm_and_yarn/base-ui/react-1.6.0
chore(deps): bump @base-ui/react from 1.5.0 to 1.6.0
2026-07-01 11:24:02 +03:00
Arnas Donauskas
1229de52d2
Merge pull request #315 from ArnasDon/dependabot/npm_and_yarn/dev-dependencies-d62044d6eb
chore(deps-dev): bump prettier from 3.8.4 to 3.9.1 in the dev-dependencies group
2026-07-01 11:21:14 +03:00
Arnas Donauskas
e60b08ac2e
Merge pull request #323 from ArnasDon/fix/whatsapp-config-draft-preserve
fix(settings): preserve WhatsApp credential drafts on tab refocus
2026-07-01 11:18:59 +03:00
Arnas Donauskas
7baeb94003 fix(settings): preserve WhatsApp credential drafts on tab refocus
The WhatsApp settings load effect re-ran fetchConfig() whenever `user`
or `profileLoading` changed. Supabase's onAuthStateChange fires a token
refresh (new `user` object, profileLoading toggles) when the browser
tab regains focus, so returning to the tab re-hydrated the form and
overwrote any unsaved credential input.

Guard the effect with a ref tracking the account id already loaded, so
it only fetches once per account and ignores auth-state churn that
doesn't actually change the account. Explicit reloads after save,
registration verification, and reset still call fetchConfig directly.

Closes #307

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 11:16:54 +03:00
Arnas Donauskas
493cf4195a
Merge pull request #316 from ArnasDon/dependabot/npm_and_yarn/types/node-26.0.1
chore(deps-dev): bump @types/node from 25.9.3 to 26.0.1
2026-07-01 11:14:46 +03:00
Arnas Donauskas
92934d6828
Merge pull request #317 from ArnasDon/dependabot/npm_and_yarn/lucide-react-1.22.0
chore(deps): bump lucide-react from 1.18.0 to 1.22.0
2026-07-01 11:13:00 +03:00
Riad Developer
1fce87c4c6
Prevent redundant profile fetches on refresh
Replace `user` object with `user?.id` in dependency arrays to prevent unnecessary re-renders. Add `lastFetchedUserIdRef` to track the user ID for successfully initiated profile fetches, preventing redundant API calls when the window refocuses or the auth token is refreshed.
2026-06-30 13:01:09 -07:00
AlexTPaul
cd863cf9ee feat(notifications): implement account-based notification retrieval and update logic 2026-06-30 11:16:20 +05:30
Alex Titus Paul
cf842fcee2
Merge branch 'ArnasDon:main' into main 2026-06-30 10:44:07 +05:30
Arnas Donauskas
65b677181e
Merge pull request #320 from ArnasDon/fix/template-picker-account-scope
fix(templates): scope template picker to account, not user
2026-06-29 17:35:51 +03:00
Arnas Donauskas
3110932d78
fix(templates): scope template picker to account, not user
The inbox/contact TemplatePicker fetched approved templates filtered by
`user_id = current user`, so in a shared account a teammate only saw
templates they personally created — others' approved templates were
hidden, leaving them unable to send those templates.

message_templates is account-owned and its RLS SELECT policy already
scopes rows to account members (is_account_member, migration 017), so
the user_id filter was both redundant and over-restrictive. Drop it and
rely on RLS, matching the broadcast template picker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:33:41 +03:00
Arnas Donauskas
26f246b3d0
Merge pull request #319 from ArnasDon/feat/send-template-to-contact
feat(contacts): send an approved template to a single contact (#296)
2026-06-29 17:28:32 +03:00