Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
383 changes: 349 additions & 34 deletions messages/en.json

Large diffs are not rendered by default.

2,011 changes: 2,011 additions & 0 deletions messages/es.json

Large diffs are not rendered by default.

2,011 changes: 2,011 additions & 0 deletions messages/pt.json

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions src/app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
CardTitle,
} from "@/components/ui/card";
import { MessageSquare, CheckCircle, ArrowLeft } from "lucide-react";
import { useTranslations } from "next-intl";

export default function ForgotPasswordPage() {
const t = useTranslations("ForgotPassword");
const [email, setEmail] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -50,12 +52,12 @@ export default function ForgotPasswordPage() {
<CheckCircle className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-xl text-foreground">
Check your email
{t("checkEmailTitle")}
</CardTitle>
<CardDescription className="text-muted-foreground">
We&apos;ve sent a password reset link to{" "}
<span className="text-foreground">{email}</span>. Please check your
inbox.
{t("checkEmailBodyBefore")}{" "}
<span className="text-foreground">{email}</span>
{t("checkEmailBodyAfter")}
</CardDescription>
</CardHeader>
<CardContent>
Expand All @@ -64,7 +66,7 @@ export default function ForgotPasswordPage() {
variant="outline"
className="w-full border-border text-muted-foreground hover:bg-muted hover:text-foreground"
>
Back to sign in
{t("backToSignIn")}
</Button>
</Link>
</CardContent>
Expand All @@ -80,9 +82,9 @@ export default function ForgotPasswordPage() {
<div className="mb-2 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10">
<MessageSquare className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-xl text-foreground">Reset password</CardTitle>
<CardTitle className="text-xl text-foreground">{t("title")}</CardTitle>
<CardDescription className="text-muted-foreground">
Enter your email and we&apos;ll send you a reset link
{t("description")}
</CardDescription>
</CardHeader>
<CardContent>
Expand All @@ -95,7 +97,7 @@ export default function ForgotPasswordPage() {

<div className="flex flex-col gap-2">
<Label htmlFor="email" className="text-muted-foreground">
Email
{t("email")}
</Label>
<Input
id="email"
Expand All @@ -113,7 +115,7 @@ export default function ForgotPasswordPage() {
disabled={loading}
className="mt-2 h-10 w-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{loading ? "Sending..." : "Send reset link"}
{loading ? t("sending") : t("sendResetLink")}
</Button>
</form>

Expand All @@ -122,7 +124,7 @@ export default function ForgotPasswordPage() {
className="mt-6 flex items-center justify-center gap-2 text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to sign in
{t("backToSignIn")}
</Link>
</CardContent>
</Card>
Expand Down
42 changes: 21 additions & 21 deletions src/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { MessageSquare, CheckCircle, UsersRound } from "lucide-react";
import { useTranslations } from "next-intl";

// `useSearchParams` opts the component out of static prerendering
// unless wrapped in Suspense — same pattern as /login.
Expand All @@ -27,6 +28,7 @@ export default function SignupPage() {
}

function SignupPageInner() {
const t = useTranslations("Signup");
const searchParams = useSearchParams();
// When the user lands here from `/join/<token>` we carry the
// invite token in the query so it survives the signup → email
Expand All @@ -49,12 +51,12 @@ function SignupPageInner() {
setError(null);

if (password !== confirmPassword) {
setError("Passwords do not match");
setError(t("passwordsDoNotMatch"));
return;
}

if (password.length < 6) {
setError("Password must be at least 6 characters");
setError(t("passwordTooShort"));
return;
}

Expand Down Expand Up @@ -98,12 +100,12 @@ function SignupPageInner() {
<CheckCircle className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-xl text-foreground">
Check your email
{t("checkEmailTitle")}
</CardTitle>
<CardDescription className="text-muted-foreground">
We&apos;ve sent a confirmation link to{" "}
<span className="text-foreground">{email}</span>. Please check your
inbox and click the link to verify your account.
{t("checkEmailBodyBefore")}{" "}
<span className="text-foreground">{email}</span>
{t("checkEmailBodyAfter")}
</CardDescription>
</CardHeader>
<CardContent>
Expand All @@ -118,7 +120,7 @@ function SignupPageInner() {
variant="outline"
className="w-full border-border text-muted-foreground hover:bg-muted hover:text-foreground"
>
Back to sign in
{t("backToSignIn")}
</Button>
</Link>
</CardContent>
Expand All @@ -139,12 +141,10 @@ function SignupPageInner() {
)}
</div>
<CardTitle className="text-xl text-foreground">
{inviteToken ? "Create account & join" : "Create account"}
{inviteToken ? t("titleInvite") : t("title")}
</CardTitle>
<CardDescription className="text-muted-foreground">
{inviteToken
? "Verify your email, then accept the invitation to join your team."
: "Get started with CRM Template for WhatsApp"}
{inviteToken ? t("descriptionInvite") : t("description")}
</CardDescription>
</CardHeader>
<CardContent>
Expand All @@ -157,12 +157,12 @@ function SignupPageInner() {

<div className="flex flex-col gap-2">
<Label htmlFor="fullName" className="text-muted-foreground">
Full name
{t("fullName")}
</Label>
<Input
id="fullName"
type="text"
placeholder="John Doe"
placeholder={t("fullNamePlaceholder")}
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
Expand All @@ -172,7 +172,7 @@ function SignupPageInner() {

<div className="flex flex-col gap-2">
<Label htmlFor="email" className="text-muted-foreground">
Email
{t("email")}
</Label>
<Input
id="email"
Expand All @@ -187,12 +187,12 @@ function SignupPageInner() {

<div className="flex flex-col gap-2">
<Label htmlFor="password" className="text-muted-foreground">
Password
{t("password")}
</Label>
<Input
id="password"
type="password"
placeholder="At least 6 characters"
placeholder={t("passwordPlaceholder")}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
Expand All @@ -202,12 +202,12 @@ function SignupPageInner() {

<div className="flex flex-col gap-2">
<Label htmlFor="confirmPassword" className="text-muted-foreground">
Confirm password
{t("confirmPassword")}
</Label>
<Input
id="confirmPassword"
type="password"
placeholder="Repeat your password"
placeholder={t("confirmPasswordPlaceholder")}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
Expand All @@ -220,12 +220,12 @@ function SignupPageInner() {
disabled={loading}
className="mt-2 h-10 w-full bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{loading ? "Creating account..." : "Create account"}
{loading ? t("creatingAccount") : t("createAccount")}
</Button>
</form>

<p className="mt-6 text-center text-sm text-muted-foreground">
Already have an account?{" "}
{t("alreadyHaveAccount")}{" "}
<Link
href={
inviteToken
Expand All @@ -234,7 +234,7 @@ function SignupPageInner() {
}
className="text-primary hover:text-primary/80"
>
Sign in
{t("signIn")}
</Link>
</p>
</CardContent>
Expand Down
13 changes: 7 additions & 6 deletions src/app/(dashboard)/agents/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { Bot, Sparkles, Settings2, BarChart3 } from 'lucide-react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { AiPlayground } from '@/components/agents/ai-playground';
Expand All @@ -12,6 +13,7 @@ import { canEditSettings } from '@/lib/auth/roles';
type Tab = 'playground' | 'setup' | 'usage';

export default function AgentsPage() {
const t = useTranslations('Agents');
const { accountRole } = useAuth();
const canViewUsage = accountRole ? canEditSettings(accountRole) : false;
const [tab, setTab] = useState<Tab>('playground');
Expand Down Expand Up @@ -41,12 +43,11 @@ export default function AgentsPage() {
<div className="flex items-center gap-2">
<Bot className="h-6 w-6 text-primary" />
<h1 className="text-2xl font-bold tracking-tight text-foreground">
AI Agents
{t('title')}
</h1>
</div>
<p className="mt-1 text-sm text-muted-foreground">
Your bring-your-own-key AI agent — set it up, then test it in the
playground before it replies to customers in the inbox.
{t('subtitle')}
</p>

{decided && (
Expand All @@ -57,14 +58,14 @@ export default function AgentsPage() {
>
<TabsList>
<TabsTrigger value="playground">
<Sparkles className="mr-1.5 h-4 w-4" /> Playground
<Sparkles className="mr-1.5 h-4 w-4" /> {t('tabPlayground')}
</TabsTrigger>
<TabsTrigger value="setup">
<Settings2 className="mr-1.5 h-4 w-4" /> Setup
<Settings2 className="mr-1.5 h-4 w-4" /> {t('tabSetup')}
</TabsTrigger>
{canViewUsage && (
<TabsTrigger value="usage">
<BarChart3 className="mr-1.5 h-4 w-4" /> Usage
<BarChart3 className="mr-1.5 h-4 w-4" /> {t('tabUsage')}
</TabsTrigger>
)}
</TabsList>
Expand Down
12 changes: 6 additions & 6 deletions src/app/(dashboard)/automations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { AUTOMATION_TEMPLATES, type TemplateSlug } from "@/lib/automations/templates"
import { type TemplateSlug } from "@/lib/automations/templates"
import { triggerMeta, formatRelative } from "@/lib/automations/trigger-meta"
import { cn } from "@/lib/utils"

Expand All @@ -62,6 +62,7 @@ export default function AutomationsPage() {
const router = useRouter()
const canCreate = useCan("send-messages")
const t = useTranslations("Automations.list")
const tmplT = useTranslations("AutomationTemplates")
const [automations, setAutomations] = useState<Automation[] | null>(null)
const [error, setError] = useState<string | null>(null)
const [pendingDelete, setPendingDelete] = useState<Automation | null>(null)
Expand Down Expand Up @@ -183,7 +184,6 @@ export default function AutomationsPage() {
<h2 className="mb-3 text-sm font-semibold text-muted-foreground">{t("templatesTitle")}</h2>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
{TEMPLATE_ORDER.map((slug) => {
const t = AUTOMATION_TEMPLATES[slug]
const Icon = TEMPLATE_ICON[slug]
return (
<button
Expand All @@ -194,8 +194,8 @@ export default function AutomationsPage() {
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary group-hover:bg-primary/15">
<Icon className="h-5 w-5" />
</div>
<div className="text-sm font-semibold text-foreground">{t.name}</div>
<p className="mt-1 text-xs text-muted-foreground">{t.description}</p>
<div className="text-sm font-semibold text-foreground">{tmplT(`${slug}.name`)}</div>
<p className="mt-1 text-xs text-muted-foreground">{tmplT(`${slug}.description`)}</p>
</button>
)
})}
Expand Down Expand Up @@ -299,7 +299,7 @@ function AutomationCard({
{automation.name}
</span>
{automation.is_active && (
<span className="relative flex h-2 w-2" aria-label="active">
<span className="relative flex h-2 w-2" aria-label={t("activeAria")}>
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-primary" />
</span>
Expand Down Expand Up @@ -336,7 +336,7 @@ function AutomationCard({

<DropdownMenu>
<DropdownMenuTrigger
aria-label="Open menu"
aria-label={t("openMenu")}
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[popup-open]:bg-muted"
>
<MoreVertical className="h-4 w-4" />
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/broadcasts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export default function BroadcastsPage() {
{anySending && (
<div
role="progressbar"
aria-label="Broadcast in progress"
aria-label={t('inProgressAria')}
className="broadcast-indeterminate fixed inset-x-0 top-0 z-40 h-0.5 overflow-hidden bg-muted"
>
<div className="broadcast-indeterminate-bar h-0.5 bg-primary" />
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/contacts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ export default function ContactsPage() {
indeterminate={!allOnPageSelected && someOnPageSelected}
onCheckedChange={toggleSelectAll}
disabled={contacts.length === 0}
aria-label="Select all contacts on this page"
aria-label={t('selectAllAria')}
/>
</TableHead>
<TableHead className="text-muted-foreground">{t('tableColumns.name')}</TableHead>
Expand Down
4 changes: 3 additions & 1 deletion src/app/(dashboard)/dashboard-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthProvider, useAuth } from "@/hooks/use-auth";
import { Sidebar } from "@/components/layout/sidebar";
import { Header } from "@/components/layout/header";
import { PresenceHeartbeat } from "@/components/presence/presence-heartbeat";
import { useTranslations } from "next-intl";

// Auth-gated dashboard shell. Extracted from the layout so the layout
// itself can stay a server component and export metadata (noindex) —
Expand All @@ -14,6 +15,7 @@ import { PresenceHeartbeat } from "@/components/presence/presence-heartbeat";
function DashboardShellInner({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
const t = useTranslations("Common");

// Sidebar drawer state — only used on mobile. On lg+ the sidebar is
// always visible and this stays at `false` (ignored by the component).
Expand All @@ -31,7 +33,7 @@ function DashboardShellInner({ children }: { children: React.ReactNode }) {
<div className="flex h-screen items-center justify-center bg-background">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<p className="text-sm text-muted-foreground">Loading...</p>
<p className="text-sm text-muted-foreground">{t("loading")}</p>
</div>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions src/app/(dashboard)/flows/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default function FlowsPage() {
const router = useRouter();
const canCreate = useCan("send-messages");
const t = useTranslations("Flows.list");
const tmplT = useTranslations("FlowTemplates");
const [flows, setFlows] = useState<FlowRow[]>([]);
const [loading, setLoading] = useState(true);
const [createOpen, setCreateOpen] = useState(false);
Expand Down Expand Up @@ -274,10 +275,10 @@ export default function FlowsPage() {
>
<Icon className="h-5 w-5 text-primary" />
<span className="text-sm font-semibold text-popover-foreground">
{template.name}
{tmplT(`${template.slug}.name`)}
</span>
<span className="text-xs leading-relaxed text-muted-foreground">
{template.description}
{tmplT(`${template.slug}.description`)}
</span>
<span className="mt-auto border-t border-border pt-2 text-[11px] text-muted-foreground">
{t("nodeCount", { count: template.node_count })}
Expand Down
Loading