Commit Graph

637 Commits

Author SHA1 Message Date
Arnas Donauskas
3e4ed3714d
Merge pull request #382 from ArnasDon/dependabot/npm_and_yarn/recharts-3.9.2
chore(deps): bump recharts from 3.8.1 to 3.9.2
2026-07-17 13:35:49 +03:00
Arnas Donauskas
3ca3ff512a
Merge pull request #383 from ArnasDon/dependabot/npm_and_yarn/next-intl-4.13.2
chore(deps): bump next-intl from 4.13.1 to 4.13.2
2026-07-17 13:35:40 +03:00
Arnas Donauskas
8725865198
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:54 +03:00
Arnas Donauskas
75a082cfe4
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:49 +03:00
Arnas Donauskas
356e198f93
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:44 +03:00
Arnas Donauskas
17cd16a52a
Merge pull request #391 from mooouuu/fix/missing-tags-translation
fix(i18n): add missing contact tags tab label
2026-07-17 12:48:26 +03:00
Arnas Donauskas
5413dd634a
Merge pull request #393 from mooouuu/feat/tag-added-trigger
feat(automations): dispatch tag added triggers
2026-07-17 12:48:18 +03:00
Arnas Donauskas
96e60adecf
Merge pull request #396 from opastorello/fix/cron-timing-safe
fix: use timing-safe compare for cron secret
2026-07-17 12:48:10 +03:00
Arnas Donauskas
f69c82aea0
Merge pull request #398 from ArnasDon/fix/365-login-ngrok-redirect
fix: full-page navigation after login so the session is applied (#365)
2026-07-17 12:41:47 +03:00
Arnas Donauskas
1110f1fb45
fix: full-page navigation after login so the session is applied (#365)
Login signed in client-side then soft-navigated with router.push into
/dashboard, a route the middleware gates on the Supabase session cookie.
The soft navigation can reach the protected route before the server
observes the freshly written cookies, so the middleware bounces it back
to /login -- which looks like the page "just refreshing" instead of
signing in. It surfaced over ngrok/HTTPS tunnels (added latency widens
the window) while staying hidden on localhost.

- login: navigate with window.location.href after signInWithPassword,
  mirroring the deliberate full reload the invite-accept flow already
  uses in join/[token]/page.tsx; drops the now-unused useRouter.
- next.config: allow-list common dev tunnel origins (ngrok, cloudflared,
  localtunnel) so Next 16 stops 403-ing dev-only resources over the
  tunnel, extendable via ALLOWED_DEV_ORIGINS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 12:29:01 +03:00
Nicolas Pastorello
ed4ce6b52e fix: use timing-safe compare for cron secret
Match flows cron implementation (timingSafeEqual).
2026-07-16 13:03:21 -03:00
Mauricio Martinez
292f52f903 feat(automations): dispatch tag added triggers 2026-07-16 00:26:21 -06:00
Mauricio Martinez
6a8744c003 fix(i18n): add missing contact tags tab label 2026-07-15 23:41:29 -06:00
Fabian Andres Parra Sanchez
3b48b6e3f4 feat: add Colombian Peso (COP) currency option 2026-07-14 06:55:31 -05:00
dependabot[bot]
eb3c6fdc20
chore(deps): bump next-intl from 4.13.1 to 4.13.2
Bumps [next-intl](https://github.com/amannn/next-intl) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.13.1...v4.13.2)

---
updated-dependencies:
- dependency-name: next-intl
  dependency-version: 4.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:36:42 +00:00
dependabot[bot]
0ec5de789a
chore(deps): bump recharts from 3.8.1 to 3.9.2
Bumps [recharts](https://github.com/recharts/recharts) from 3.8.1 to 3.9.2.
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.9.2)

---
updated-dependencies:
- dependency-name: recharts
  dependency-version: 3.9.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:36:33 +00:00
dependabot[bot]
d9c1f5cc4a
chore(deps-dev): bump the dev-dependencies group with 4 updates
---
updated-dependencies:
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:35:52 +00:00
moduvoice
b8a0a99bdb feat(i18n): add Korean (ko) translation
Adds messages/ko.json — full next-intl translation covering all 12
namespaces (LoginPage, Sidebar, Header, ModeToggle, Dashboard, Inbox,
Contacts, Pipelines, Broadcasts, Automations, Flows, Settings).

Scoped to the language file only, per maintainer guidance on PR #362:
no changes to the default locale, component text, or any other file.

Verified 1:1 key parity with messages/en.json (1426/1426 leaf keys)
and exact preservation of all interpolation variables and HTML-ish
tags across every message.
2026-07-12 00:06:31 +07:00
Arnas Donauskas
b867760801
Merge pull request #364 from ArnasDon/fix/duplicate-conversations
fix: stop inbox creating duplicate chats for one contact (#363)
2026-07-10 16:27:12 +03:00
Arnas Donauskas
faf0f740a4
fix: stop inbound chats fragmenting into duplicate conversations (#363)
An inbound message could create a second conversation for a contact
under a race (Meta retries a delivery, or a batch fans out to concurrent
after() runs). There was no UNIQUE index and no unique-violation
backstop on conversations, unlike contacts (migration 022). Worse, once
two conversations existed the `.single()` lookup errored on every later
message and the webhook created yet another conversation each time,
snowballing into a wall of duplicate chats.

- findOrCreateConversation now resolves oldest-first with .limit(1)
  instead of .single(), so pre-existing duplicates converge, and catches
  23505 on insert to re-resolve the winning row.
- Same hardening applied to the public-API resolver (resolve-conversation).
- Migration 036 merges existing duplicate conversations into the oldest
  thread (re-pointing messages, reactions, deals, flow_runs,
  notifications, ai_usage_log — no data loss) then adds a
  UNIQUE (account_id, contact_id) index as the authoritative guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:27:46 +03:00
Arnas Donauskas
137760e8f8
Merge pull request #358 from ArnasDon/feat/whatsapp-interactive-messages
feat: WhatsApp interactive messages (reply buttons + lists)
2026-07-08 19:10:53 +03:00
Arnas Donauskas
78b42dfba9 feat: WhatsApp interactive messages (reply buttons + lists)
Add full support for WhatsApp Cloud API interactive messages across the
inbox, automations, and a new quick-replies feature. Extends the
interactive plumbing that previously existed only for the Flows engine.

Sending
- send-message core handles `interactive` (reply buttons ≤3, list ≤10
  rows) via the existing Meta senders; validated server-side before send.
- Inbox composer gains a "+" menu → builder dialog with live WhatsApp-style
  preview + Meta char limits (button 20, list row 24, body 1024).
- Automation "Send Buttons" / "Send List" steps in the builder.
- Public v1 /messages accepts type=interactive.

Storage / round-trip
- migration 035: messages.interactive_payload JSONB + quick_replies table.
- Outbound interactive persists the structured payload (composer,
  automations, and Flows) so the thread re-renders the buttons/rows.

Receiving
- Bubble renders outbound interactive via the shared preview; inbound taps
  (button_reply/list_reply) keep the "chosen option" affordance.

Automations
- New `interactive_reply` trigger matches on tapped button/list IDs
  (exact), enabling chained multi-step menus.

Quick replies
- New account-scoped quick_replies (text or interactive snippets), managed
  in Settings and insertable from the composer; "Save as quick reply" in
  the builder.

Shared core in src/lib/whatsapp/interactive.ts + src/components/interactive/.
Includes unit tests for the validator, send-param validation, the
interactive_reply trigger match, and the new automation step/trigger
validation. typecheck + lint clean; full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:03:15 +03:00
Arnas Donauskas
3eff5b854e
Merge pull request #357 from ArnasDon/fix/mcp-server-json
Correct MCP registry namespace casing (ArnasDon), bump to 0.1.1
2026-07-08 18:06:03 +03:00
Arnas Donauskas
21506cb9d8 Correct registry namespace casing; bump to 0.1.1
The MCP registry namespace is case-sensitive and keyed to the GitHub
login: the owner is `ArnasDon`, not `arnasdon`. Publishing as
io.github.arnasdon/wacrm-mcp is rejected 403. Fix the name in
server.json and the matching mcpName in package.json to
io.github.ArnasDon/wacrm-mcp.

Bump to 0.1.1 because the published 0.1.0 baked in the lowercase
mcpName; the registry validates the published package's mcpName
against server.json, so a fresh npm version is required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:58:20 +03:00
Arnas Donauskas
e23f954221
Merge pull request #356 from ArnasDon/fix/mcp-server-json
Fix MCP registry validation in server.json
2026-07-08 17:49:35 +03:00
Arnas Donauskas
18e4bf4d21 Fix MCP registry validation in server.json
Shorten description to the registry's 100-char limit and migrate the
$schema to the current 2025-12-11 version (2025-10-17 is deprecated).
`mcp-publisher validate` now passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:46:21 +03:00
Arnas Donauskas
9441533458
Merge pull request #355 from ArnasDon/feature/mcp-server
Add MCP server for the public API
2026-07-08 17:34:03 +03:00
Arnas Donauskas
78f2f79d3b Add MCP server for the public API
Introduce mcp-server/ — a stdio Model Context Protocol server that
wraps the /api/v1 public API so MCP clients (Claude, Cursor, etc.) can
drive a self-hosted wacrm instance in natural language.

- Thin wrapper over the existing REST API; all auth, scoping, and rate
  limiting stay server-side. No new backend surface.
- Read-only by default: write and broadcast tools are only registered
  when WACRM_ENABLE_WRITES / WACRM_ENABLE_BROADCASTS are set, layered
  on top of the API key's own scopes. send_broadcast also requires an
  explicit confirm and is marked destructive.
- Registry-ready: server.json manifest, package.json mcpName linked to
  it (io.github.arnasdon/wacrm-mcp), and glama.json for discovery.
- Docs: docs/mcp.md + README bullet + mcp-server/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:30:24 +03:00
Arnas Donauskas
95f6052e34
Merge pull request #353 from ArnasDon/feat/ai-autoreply-polish
feat(ai): polish auto-reply — inbox controls, real handoff, usage logging
2026-07-08 16:39:17 +03:00
Arnas Donauskas
2311b7ea35 Merge origin/main; internationalize new AI auto-reply UI
Brings in the i18n PR (#348) that landed on main and localizes the new
strings this branch added so nothing regresses the new next-intl
convention:
- ai-config.tsx: resolved import conflict; handoff-target picker strings
  moved to Settings.aiConfig (handoffTo / handoffToDesc / handoffQueue).
- message-bubble.tsx: "AI" badge + tooltip → Inbox.bubble
  (aiBadge / aiBadgeTitle).
- ai-thread-banner.tsx: take-over/resume banner + toasts → new
  Inbox.aiBanner namespace.

The AI Agents section (/agents playground, setup tabs, usage card) is
still English on main, so those strings are left as-is for a future
section-wide i18n pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:35:27 +03:00
Arnas Donauskas
eefb6179c2 fix(db): resolve duplicate migration version 031 (rename profiles-RLS fix to 034)
The security PR (#352) shipped 031_fix_profiles_update_rls.sql while
031_ai_reply_slot_grant.sql (#345) already held version 031. Supabase
keys schema_migrations on the numeric prefix, so applying both to a fresh
DB fails with `duplicate key ... (version)=(031)` (SQLSTATE 23505) — which
is what broke the Supabase Preview check on this PR.

Rename the newer, colliding file to 034 to restore unique versions. It's
idempotent and independent of the AI tables, so re-sequencing after 033 is
safe; a prod DB that already applied 031 will apply this fresh as 034
without side effects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:04:39 +03:00
Arnas Donauskas
077d203980
Merge pull request #348 from ivanmercedes/feat/i18n-dashboard-shell
feat(i18n): finalize localization for Dashboard, Inbox, Settings, and Automations
2026-07-08 15:39:18 +03:00
Arnas Donauskas
e132cc7a15 fix(ai): address code-review findings on auto-reply polish
Correctness:
- Resume AI now releases ANY assignment (not just the caller's own).
  A handoff-assigned thread previously stayed assigned after resume, so
  the eligibility gate silently kept the bot muted — Resume was a no-op.
- GET /api/ai/usage: guard days >= 1 (Number(null)===0 is finite, so the
  30-day default was unreachable and the window collapsed to 1 day).
- GET /api/ai/usage: align the query cutoff to the start of the oldest
  charted local day so rows in the oldest partial day no longer count in
  totals while missing from the daily series (totals now match the bars).
- ai-thread-banner: cache account AI-status per accountId and never cache
  a transient fetch failure — fixes cross-account leak and the banner
  staying hidden all session after one failed /api/ai/config call.
- /api/ai/draft: construct the service-role client inside a guard and
  fire-and-forget the usage log, so a missing service-role key (or any
  log failure) can't 500 an already-generated, already-billed draft.

Efficiency:
- Don't await usage logging on the reply hot path (auto-reply + draft);
  logAiUsage swallows its own errors, so fire-and-forget is safe and
  removes a DB round-trip from the customer-facing send.

Reuse / consistency:
- usage route reuses date-utils (daysAgoStart/lastNDayKeys/localDayKey)
  and now buckets by LOCAL day like every other dashboard chart (was UTC).
- extract formatCompactNumber (shared with formatCurrencyShort); drop the
  hand-rolled month table in favour of date-fns; extract
  fetchAccountMembers/memberLabel and use them in the AI settings picker.

Resume still resets the per-conversation reply count (a deliberate,
manual, rate-limited re-engagement — documented in the handler).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:35:41 +03:00
Ivan Mercedes
4690c4d929 refactor: address PR feedback for security and cleanups
- refactor: migrate dangerouslySetInnerHTML(t.raw) to t.rich() to prevent HTML injection vectors
- fix: restore per-status wording for flow activation toasts ("Flow activated.", "Archived.", "Saved as draft.")
- chore: remove unused `tComposer` in message-thread.tsx
- chore: remove unused `ROLE_DESCRIPTIONS` in invite-member-dialog.tsx
2026-07-08 07:58:33 -04:00
Arnas Donauskas
c736e7a43f feat(ai): add token-usage dashboard (AI Agents → Usage)
Surfaces the ai_usage_log data added in the previous commit:
- GET /api/ai/usage — admin-only aggregation (totals, per-mode,
  per-model, zero-filled daily series) over a 7/30/90-day window,
  mirroring the ai_usage_log SELECT policy
- AiUsageCard — stat tiles + daily token bar chart + by-model list,
  rendered under a new admin-only "Usage" tab on the AI Agents page

Counts only; no message content is stored or shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:31:36 +03:00
Arnas Donauskas
b0194dfc27 feat(ai): polish auto-reply — inbox controls, real handoff, usage logging
Makes the AI auto-reply bot visible and controllable from the inbox,
completes the handoff, and records token spend. Builds on the existing
lib/ai engine rather than replacing it.

Track A — user-facing
- messages.ai_generated flag → small "AI" badge on bot-generated bubbles
- "Take over" / "Resume AI" banner per conversation
  (POST /api/ai/autoreply/[id]) — take-over pauses + assigns to the agent
- handoff now assigns to a configurable target (agent or unassigned
  queue) and writes an internal note summarizing the exchange; the
  existing on_conversation_assigned trigger notifies the assignee
- Settings → AI: "Hand off to" target picker

Track B — observability + safety
- providers return normalized token usage; logged per run to ai_usage_log
  (admin-readable) from both auto-reply and draft
- account-wide auto-reply rate limit (30/min) on top of the
  per-conversation cap

Migration: 033_ai_reply_polish.sql (messages.ai_generated,
ai_configs.handoff_agent_id, conversations.ai_handoff_summary,
ai_usage_log). Tests + typecheck + lint pass (607 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:03:33 +03:00
Arnas Donauskas
23838a9959
Merge pull request #352 from ArnasDon/fix/ghsa-8jqh-598v-rfxc-webhook-ssrf
fix(security): SSRF guard on automation send_webhook action (GHSA-8jqh-598v-rfxc)
2026-07-08 10:09:23 +03:00
Arnas Donauskas
7d1ddbfdb8 fix(security): SSRF guard on automation send_webhook action (GHSA-8jqh-598v-rfxc)
The send_webhook automation step fetched an account-controlled URL with
attacker-controlled headers and no SSRF protection, following redirects
by default — letting an agent+ reach cloud metadata (169.254.169.254),
loopback, and RFC1918 hosts from the server's network (status code
surfaces in automation logs). The separate webhook_endpoints delivery
path already guards this; the automation path did not.

Route send_webhook through the existing isDeliverableUrl guard before the
fetch, and mirror the deliver.ts hardening: redirect: 'manual' (a public
URL can't 3xx-bounce to an internal one) and a 10s timeout. Add an engine
regression test asserting a link-local target never reaches fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:04:11 +03:00
Arnas Donauskas
03e851bea5
Merge pull request #351 from ArnasDon/fix/ghsa-34q7-fv77-625j-role-checks
fix(security): enforce agent role on service-role write routes (GHSA-34q7-fv77-625j)
2026-07-08 09:39:41 +03:00
Arnas Donauskas
1d7829e68a fix(security): enforce agent role on service-role write routes (GHSA-34q7-fv77-625j)
Flows and automations write routes verify only authentication/membership
(via the RLS client, whose *_select policies have no minimum role) and
then mutate with the service-role client, which bypasses the agent-gated
*_insert/update/delete RLS policies. A read-only `viewer` therefore had
full write power.

H3 (High): add requireRole('agent') to the flows write handlers —
PUT/DELETE (flows/[id]), activate (flows/[id]/activate), and create
(flows POST). GET/list stay open to viewers.

M2 (Medium): same gate on automations POST, the engine trigger
(automations/engine, which sends outbound WhatsApp), and the [id]
mutation handlers (PATCH/DELETE/duplicate). The cron route is unchanged —
it is gated by the x-cron-secret shared secret, not user auth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:35:20 +03:00
Arnas Donauskas
e01f7ed371
Merge pull request #350 from ArnasDon/fix/ghsa-fg5p-2qc3-jmxr-db-authz
fix(security): patch DB-layer authz bypasses (GHSA-fg5p-2qc3-jmxr)
2026-07-08 09:13:48 +03:00
Arnas Donauskas
2b32308703 fix(security): patch DB-layer authz bypasses (GHSA-fg5p-2qc3-jmxr)
C1 (Critical): the profiles_update RLS policy gates only on user_id,
leaving account_role/account_id — the source of truth for
is_account_member() — freely writable by the row's own user. A
browser-client UPDATE could self-promote a viewer to owner or move
into another tenant. Add a BEFORE UPDATE trigger that rejects changes
to those columns when current_user = 'authenticated'; the SECURITY
DEFINER member/invitation RPCs (postgres) and the service_role backend
are unaffected.

H2 (High): match_ai_knowledge_fts/_semantic were SECURITY DEFINER,
filtered only on the caller-supplied p_account_id with no membership
check, and were granted to authenticated — letting any logged-in user
read another account's knowledge base via a foreign p_account_id.
Recreate both as SECURITY INVOKER so the existing
ai_knowledge_chunks_select RLS policy applies; the service_role
auto-reply bot still bypasses RLS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 09:10:07 +03:00
Ivan Mercedes
de75715cff feat(i18n): localize Inbox components and update english translations 2026-07-07 21:24:54 -04:00
Ivan Mercedes
801969a361 feat(i18n): localize settings ai and meta sections
- Integrated next-intl into ai-config.tsx and ai-knowledge.tsx panels.
- Defined translation schemas for Settings.aiConfig and Settings.aiKnowledge in en.json.
- Migrated hardcoded role labels from role-meta.ts to use translation keys dynamically in settings-overview.tsx using the 'roles' namespace.
- All Typescript and Linter checks successfully passed without regressions.
2026-07-07 21:17:41 -04:00
Ivan Mercedes
9ee79638da feat(i18n): localize settings configuration panels
- Implemented next-intl across Appearance, Security, API Keys, and Deals settings panels.
- Defined translation schemas for Settings.appearance, Settings.security, Settings.apiKeys, and Settings.deals in en.json.
- Removed explicit 'any' types in components by utilizing local useTranslations hooks directly inside sub-components (ModeCard, ThemeCard, CreateKeyDialog).
- Eliminated all new typescript and eslint errors related to the localization migrations.
- Updated progress trackers to reflect completion.
2026-07-07 21:09:50 -04:00
Ivan Mercedes
c30e117552 refactor: update translation key types and simplify delete aria-label implementation 2026-07-07 21:00:33 -04:00
Ivan Mercedes
a6596de2c9 refactor: improve type safety for translations by replacing any types with specific parameters and node types 2026-07-07 20:55:21 -04:00
Ivan Mercedes
eeea478315 feat(i18n): localize settings templates and profile modul 2026-07-07 20:50:57 -04:00
Ivan Mercedes
a91b8a7ba7 feat(i18n): complete flows module internationalization 2026-07-07 20:12:38 -04:00
Ivan Mercedes
7cb019081b feat(i18n): translate settings page titles 2026-07-07 19:57:36 -04:00