feat(i18n): complete settings module internationalization

This commit is contained in:
Ivan Mercedes 2026-07-07 19:56:55 -04:00
parent caf0f3660e
commit 9357fd9ec9
4 changed files with 267 additions and 128 deletions

View File

@ -808,13 +808,34 @@
"close_conversation": "Close Conversation"
},
"triggers": {
"new_message_received": { "label": "New Message Received", "hint": "Any incoming message" },
"first_inbound_message": { "label": "First Message from Contact", "hint": "First time this contact ever messages you (works for manually-added contacts too)" },
"keyword_match": { "label": "Keyword Match", "hint": "Message contains specific keyword(s)" },
"new_contact_created": { "label": "New Contact Created", "hint": "When a contact is auto-created from an incoming message" },
"conversation_assigned": { "label": "Conversation Assigned", "hint": "When assigned to an agent" },
"tag_added": { "label": "Tag Added", "hint": "When a tag is added to a contact" },
"time_based": { "label": "Time-Based", "hint": "On a recurring schedule" }
"new_message_received": {
"label": "New Message Received",
"hint": "Any incoming message"
},
"first_inbound_message": {
"label": "First Message from Contact",
"hint": "First time this contact ever messages you (works for manually-added contacts too)"
},
"keyword_match": {
"label": "Keyword Match",
"hint": "Message contains specific keyword(s)"
},
"new_contact_created": {
"label": "New Contact Created",
"hint": "When a contact is auto-created from an incoming message"
},
"conversation_assigned": {
"label": "Conversation Assigned",
"hint": "When assigned to an agent"
},
"tag_added": {
"label": "Tag Added",
"hint": "When a tag is added to a contact"
},
"time_based": {
"label": "Time-Based",
"hint": "On a recurring schedule"
}
},
"trigger": "Trigger",
"triggerType": "Trigger type",
@ -1065,17 +1086,148 @@
"flow": "Flow control"
},
"nodes": {
"start": { "label": "Start", "blurb": "Entry point of the flow" },
"send_message": { "label": "Send message", "blurb": "Sends a WhatsApp text message" },
"send_buttons": { "label": "Send buttons", "blurb": "Sends quick-reply buttons" },
"send_list": { "label": "Send list", "blurb": "Sends a tappable list of options" },
"send_media": { "label": "Send media", "blurb": "Sends an image, video, or document" },
"collect_input": { "label": "Collect input", "blurb": "Asks a question, saves the reply" },
"condition": { "label": "If / else", "blurb": "Branches on a rule" },
"set_tag": { "label": "Tag contact", "blurb": "Adds or removes a contact tag" },
"handoff": { "label": "Handoff to agent", "blurb": "Hands the conversation to a human" },
"end": { "label": "End", "blurb": "Ends the flow" }
"start": {
"label": "Start",
"blurb": "Entry point of the flow"
},
"send_message": {
"label": "Send message",
"blurb": "Sends a WhatsApp text message"
},
"send_buttons": {
"label": "Send buttons",
"blurb": "Sends quick-reply buttons"
},
"send_list": {
"label": "Send list",
"blurb": "Sends a tappable list of options"
},
"send_media": {
"label": "Send media",
"blurb": "Sends an image, video, or document"
},
"collect_input": {
"label": "Collect input",
"blurb": "Asks a question, saves the reply"
},
"condition": {
"label": "If / else",
"blurb": "Branches on a rule"
},
"set_tag": {
"label": "Tag contact",
"blurb": "Adds or removes a contact tag"
},
"handoff": {
"label": "Handoff to agent",
"blurb": "Hands the conversation to a human"
},
"end": {
"label": "End",
"blurb": "Ends the flow"
}
}
}
},
"Settings": {
"overview": {
"notSetup": "Not set up yet",
"connected": "Connected",
"needsReconnecting": "Needs reconnecting",
"viewTeamMembers": "View team members",
"membersCount": "{count} {count, plural, =1 {member} other {members}}",
"pendingInvites": "{count} pending {count, plural, =1 {invite} other {invites}}",
"manageTemplates": "Manage message templates",
"templatesCount": "{count} {count, plural, =1 {template} other {templates}}",
"pendingReview": "{count} pending review",
"tagsAndFields": "Tags and custom fields",
"tagsCount": "{count} {count, plural, =1 {tag} other {tags}}",
"fieldsCount": "{count} custom {count, plural, =1 {field} other {fields}}",
"appearance": "{mode} mode · {theme} accent",
"yourAccount": "Your account",
"loading": "Loading…"
},
"whatsapp": {
"title": "WhatsApp connection",
"description": "Connect your Meta WhatsApp Business API. Credentials, webhook, and setup steps all live here.",
"tokenCorrupted": "Stored token can't be decrypted",
"resetting": "Resetting...",
"resetConfig": "Reset Configuration",
"credentialsValid": "Credentials valid",
"notConnected": "Not Connected",
"connectedDesc": "Your access token authenticates with Meta. See Registration status below for whether webhooks are actually wired.",
"notConnectedDesc": "Configure your Meta API credentials below to connect your WhatsApp Business account.",
"registered": "Registered — Meta will deliver events to wacrm",
"notRegistered": "Not registered — Meta will not deliver events",
"verifyWithMeta": "Verify with Meta",
"subscribedSince": "Subscribed since {date}. Click Verify with Meta if events stop arriving.",
"unknownDate": "unknown",
"lastAttemptFailed": "Last attempt failed with: ",
"retryHint": "Enter (or correct) the 2-step PIN below and click Save Configuration to retry.",
"noRegistrationHint": "This number was saved before registration tracking existed, or registration was skipped. Enter the 2-step PIN below and click Save Configuration to subscribe it.",
"diagnosticLastRun": "Diagnostic — last run: ",
"live": "live",
"notLive": "not live",
"apiCredentialsTitle": "API Credentials",
"apiCredentialsDesc": "Enter your Meta WhatsApp Business API credentials.",
"phoneNumberId": "Phone Number ID",
"wabaId": "WhatsApp Business Account ID",
"accessToken": "Permanent Access Token",
"accessTokenPlaceholder": "Enter your access token",
"tokenHidden": "Token is hidden for security. Re-enter it to update configuration.",
"webhookVerifyToken": "Webhook Verify Token",
"webhookVerifyTokenPlaceholder": "Create a custom verify token",
"webhookVerifyTokenHint": "A custom string you create. Must match the token you set in Meta webhook settings.",
"twoStepPin": "Two-step verification PIN",
"optional": "(optional)",
"pinPlaceholder": "6-digit PIN from Meta WhatsApp Manager",
"pinHint": "Needed only to wire inbound messages for a production number. Set it in Meta Business Manager → WhatsApp Accounts → Phone Numbers → Two-step verification, then paste it here so wacrm can subscribe the number — otherwise Meta routes inbound events to whichever app last claimed it (the symptom that hits second numbers under a shared WABA). Meta test numbers have no PIN and are pre-registered — leave this blank for them. Leaving it blank also keeps an existing registration untouched.",
"webhookTitle": "Webhook Configuration",
"webhookDesc": "Use this URL as your webhook callback in the Meta App Dashboard.",
"webhookUrl": "Webhook Callback URL",
"saving": "Saving...",
"saveConfig": "Save Configuration",
"testing": "Testing...",
"testConnection": "Test API Connection",
"setupInstructions": "Setup Instructions",
"setupInstructionsDesc": "Follow these steps to connect your WhatsApp Business API.",
"step1": "Create a Meta App",
"step1_1": "Go to developers.facebook.com",
"step1_2": "Click \"My Apps\" and then \"Create App\"",
"step1_3": "Select \"Business\" as the app type",
"step1_4": "Fill in app details and create",
"step2": "Add WhatsApp Product",
"step2_1": "In your app dashboard, click \"Add Product\"",
"step2_2": "Find \"WhatsApp\" and click \"Set Up\"",
"step2_3": "Follow the setup wizard to link your business",
"step3": "Get API Credentials",
"step3_1": "Go to WhatsApp > API Setup",
"step3_2": "Copy your <strong class=\"text-foreground\">Phone Number ID</strong>",
"step3_3": "Copy your <strong class=\"text-foreground\">WhatsApp Business Account ID</strong>",
"step3_4": "Generate a <strong class=\"text-foreground\">Permanent Access Token</strong> from Business Settings > System Users",
"step4": "Configure Webhooks",
"step4_1": "Go to WhatsApp > Configuration",
"step4_2": "Click \"Edit\" on the Webhook section",
"step4_3": "Paste the <strong class=\"text-foreground\">Webhook Callback URL</strong> from above",
"step4_4": "Enter the same <strong class=\"text-foreground\">Verify Token</strong> you set here",
"step4_5": "Subscribe to \"messages\" webhook field",
"metaDocs": "Meta WhatsApp API Documentation"
},
"sections": {
"overview": "Overview",
"profile": "Your profile",
"security": "Login & security",
"appearance": "Appearance",
"whatsapp": "WhatsApp",
"templates": "Templates",
"fields": "Fields & tags",
"deals": "Deals & currency",
"members": "Team members",
"api": "API keys"
},
"groups": {
"account": "Account",
"workspace": "Workspace"
}
}
}

View File

@ -2,6 +2,7 @@
import { useEffect, useState, type ReactNode } from 'react';
import { ChevronRight, Loader2 } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { createClient } from '@/lib/supabase/client';
import { useAuth } from '@/hooks/use-auth';
@ -38,6 +39,8 @@ export function SettingsOverview({
const { user, profile, accountId, accountRole, defaultCurrency, canManageMembers } =
useAuth();
const { mode, theme } = useTheme();
const t = useTranslations('Settings.overview');
const tSections = useTranslations('Settings.sections');
const [counts, setCounts] = useState<OverviewCounts | null>(null);
const [countsLoading, setCountsLoading] = useState(true);
@ -137,7 +140,7 @@ export function SettingsOverview({
};
}, [user?.id, accountId, canManageMembers]);
const displayName = profile?.full_name || profile?.email || 'Your account';
const displayName = profile?.full_name || profile?.email || t('yourAccount');
const initial = (profile?.full_name || profile?.email || 'U').charAt(0).toUpperCase();
const roleMeta = accountRole ? ROLE_META[accountRole] : null;
const RoleIcon = roleMeta?.icon;
@ -158,14 +161,14 @@ export function SettingsOverview({
section: 'whatsapp',
loading: whatsappLoading,
subtitle: !whatsapp?.configured ? (
'Not set up yet'
t('notSetup')
) : whatsapp.connected ? (
<>
<StatusDot tone="ok" /> Connected
<StatusDot tone="ok" /> {t('connected')}
</>
) : (
<>
<StatusDot tone="muted" /> Needs reconnecting
<StatusDot tone="muted" /> {t('needsReconnecting')}
</>
),
},
@ -174,12 +177,10 @@ export function SettingsOverview({
loading: countsLoading,
subtitle:
counts?.members == null
? 'View team members'
: `${counts.members} member${counts.members === 1 ? '' : 's'}${
? t('viewTeamMembers')
: `${t('membersCount', { count: counts.members })}${
counts.pendingInvites
? ` · ${counts.pendingInvites} pending invite${
counts.pendingInvites === 1 ? '' : 's'
}`
? ` · ${t('pendingInvites', { count: counts.pendingInvites })}`
: ''
}`,
},
@ -188,10 +189,10 @@ export function SettingsOverview({
loading: countsLoading,
subtitle:
counts?.templates == null
? 'Manage message templates'
: `${counts.templates} template${counts.templates === 1 ? '' : 's'}${
? t('manageTemplates')
: `${t('templatesCount', { count: counts.templates })}${
counts.templatesPending
? ` · ${counts.templatesPending} pending review`
? ` · ${t('pendingReview', { count: counts.templatesPending })}`
: ''
}`,
},
@ -205,15 +206,15 @@ export function SettingsOverview({
loading: countsLoading,
subtitle:
counts?.tags == null && counts?.customFields == null
? 'Tags and custom fields'
: `${counts?.tags ?? 0} tag${counts?.tags === 1 ? '' : 's'} · ${
counts?.customFields ?? 0
} custom field${counts?.customFields === 1 ? '' : 's'}`,
? t('tagsAndFields')
: `${t('tagsCount', { count: counts?.tags ?? 0 })} · ${t('fieldsCount', {
count: counts?.customFields ?? 0,
})}`,
},
{
section: 'appearance',
loading: false,
subtitle: `${cap(mode)} mode · ${themeName} accent`,
subtitle: t('appearance', { mode: cap(mode), theme: themeName }),
},
];
@ -267,12 +268,12 @@ export function SettingsOverview({
</span>
<span className="min-w-0 flex-1">
<span className="block text-sm font-semibold text-foreground">
{meta.label}
{tSections(section)}
</span>
<span className="mt-0.5 flex items-center gap-1.5 text-xs text-muted-foreground">
{loading ? (
<>
<Loader2 className="size-3 animate-spin" /> Loading…
<Loader2 className="size-3 animate-spin" /> {t('loading')}
</>
) : (
subtitle

View File

@ -1,6 +1,7 @@
'use client';
import { useEffect, useRef, type ReactNode } from 'react';
import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
import {
@ -30,6 +31,7 @@ export function SettingsRail({
onSelect: (section: SettingsSection) => void;
hints?: Partial<Record<SettingsSection, ReactNode>>;
}) {
const t = useTranslations('Settings');
const activeRef = useRef<HTMLButtonElement>(null);
// When horizontal (mobile), keep the active chip in view. On desktop
@ -64,7 +66,7 @@ export function SettingsRail({
>
{label ? (
<div className="hidden px-3 pt-3.5 pb-1.5 text-[11px] font-semibold tracking-[0.09em] text-muted-foreground uppercase lg:block">
{label}
{t(`groups.${group}`)}
</div>
) : null}
{items.map((s) => {
@ -87,7 +89,7 @@ export function SettingsRail({
)}
>
<Icon className="size-4 shrink-0" />
<span className="flex-1">{meta.label}</span>
<span className="flex-1">{t(`sections.${s}`)}</span>
{hints?.[s] != null ? (
<span
className={cn(

View File

@ -16,6 +16,7 @@ import {
} from 'lucide-react';
import { createClient } from '@/lib/supabase/client';
import { useAuth } from '@/hooks/use-auth';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@ -36,6 +37,7 @@ type ConnectionStatus = 'connected' | 'disconnected' | 'unknown';
type ResetReason = 'token_corrupted' | 'meta_api_error' | null;
export function WhatsAppConfig() {
const t = useTranslations('Settings.whatsapp');
const supabase = createClient();
// After multi-user, whatsapp_config is one-row-per-account, not
// one-row-per-user. We pull `accountId` straight off the auth
@ -373,8 +375,8 @@ export function WhatsAppConfig() {
return (
<section className="animate-in fade-in-50 duration-200">
<SettingsPanelHead
title="WhatsApp connection"
description="Connect your Meta WhatsApp Business API. Credentials, webhook, and setup steps all live here."
title={t("title")}
description={t("description")}
/>
<div className="flex items-center justify-center py-12">
<Loader2 className="size-6 animate-spin text-primary" />
@ -388,8 +390,8 @@ export function WhatsAppConfig() {
return (
<section className="animate-in fade-in-50 duration-200">
<SettingsPanelHead
title="WhatsApp connection"
description="Connect your Meta WhatsApp Business API. Credentials, webhook, and setup steps all live here."
title={t("title")}
description={t("description")}
/>
<div className="grid gap-6 lg:grid-cols-[1fr_380px]">
{/* Main config form */}
@ -415,12 +417,12 @@ export function WhatsAppConfig() {
{resetting ? (
<>
<Loader2 className="size-4 animate-spin" />
Resetting...
{t('resetting')}
</>
) : (
<>
<RotateCcw className="size-4" />
Reset Configuration
{t('resetConfig')}
</>
)}
</Button>
@ -438,14 +440,14 @@ export function WhatsAppConfig() {
<XCircle className="size-4 text-red-500" />
)}
<AlertTitle className="text-foreground mb-0">
{connectionStatus === 'connected' ? 'Credentials valid' : 'Not Connected'}
{connectionStatus === 'connected' ? t('credentialsValid') : t('notConnected')}
</AlertTitle>
</div>
<AlertDescription className="text-muted-foreground">
{connectionStatus === 'connected'
? 'Your access token authenticates with Meta. See Registration status below for whether webhooks are actually wired.'
? t('connectedDesc')
: statusMessage ||
'Configure your Meta API credentials below to connect your WhatsApp Business account.'}
t('notConnectedDesc')}
</AlertDescription>
</Alert>
@ -475,8 +477,8 @@ export function WhatsAppConfig() {
}
>
{isRegistered
? 'Registered — Meta will deliver events to wacrm'
: 'Not registered — Meta will not deliver events'}
? t('registered')
: t('notRegistered')}
</AlertTitle>
</div>
<Button
@ -491,44 +493,39 @@ export function WhatsAppConfig() {
) : (
<Zap className="size-3.5" />
)}
Verify with Meta
{t('verifyWithMeta')}
</Button>
</div>
<AlertDescription className="text-muted-foreground mt-2 text-xs leading-relaxed">
{isRegistered ? (
<>
Subscribed since{' '}
{config.registered_at
? new Date(config.registered_at).toLocaleString()
: 'unknown'}
. Click <strong>Verify with Meta</strong> if events
stop arriving.
</>
<span
dangerouslySetInnerHTML={{
__html: t('subscribedSince', {
date: config.registered_at
? new Date(config.registered_at).toLocaleString()
: t('unknownDate'),
}),
}}
/>
) : lastRegistrationError ? (
<>
Last attempt failed with:{' '}
{t('lastAttemptFailed')}
<span className="text-red-300">
&quot;{lastRegistrationError}&quot;
</span>
. Enter (or correct) the 2-step PIN below and click
Save Configuration to retry.
. {t('retryHint')}
</>
) : (
<>
This number was saved before registration tracking
existed, or registration was skipped. Enter the
2-step PIN below and click Save Configuration to
subscribe it.
</>
<>{t('noRegistrationHint')}</>
)}
</AlertDescription>
{registrationProbe && (
<div className="mt-3 rounded border border-border bg-card/60 px-3 py-2 space-y-1.5 text-[11px]">
<p className="font-medium text-foreground">
Diagnostic — last run: {' '}
{t('diagnosticLastRun')}
<span className={registrationProbe.live ? 'text-emerald-400' : 'text-amber-400'}>
{registrationProbe.live ? 'live' : 'not live'}
{registrationProbe.live ? t('live') : t('notLive')}
</span>
</p>
<ul className="space-y-0.5 text-muted-foreground">
@ -560,14 +557,14 @@ export function WhatsAppConfig() {
{/* API Credentials */}
<Card>
<CardHeader>
<CardTitle className="text-foreground">API Credentials</CardTitle>
<CardTitle className="text-foreground">{t('apiCredentialsTitle')}</CardTitle>
<CardDescription className="text-muted-foreground">
Enter your Meta WhatsApp Business API credentials.
{t('apiCredentialsDesc')}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label className="text-muted-foreground">Phone Number ID</Label>
<Label className="text-muted-foreground">{t('phoneNumberId')}</Label>
<Input
placeholder="e.g. 100234567890123"
value={phoneNumberId}
@ -577,7 +574,7 @@ export function WhatsAppConfig() {
</div>
<div className="space-y-2">
<Label className="text-muted-foreground">WhatsApp Business Account ID</Label>
<Label className="text-muted-foreground">{t('wabaId')}</Label>
<Input
placeholder="e.g. 100234567890456"
value={wabaId}
@ -587,11 +584,11 @@ export function WhatsAppConfig() {
</div>
<div className="space-y-2">
<Label className="text-muted-foreground">Permanent Access Token</Label>
<Label className="text-muted-foreground">{t('accessToken')}</Label>
<div className="relative">
<Input
type={showToken ? 'text' : 'password'}
placeholder="Enter your access token"
placeholder={t('accessTokenPlaceholder')}
value={accessToken}
onChange={(e) => {
setAccessToken(e.target.value);
@ -615,34 +612,34 @@ export function WhatsAppConfig() {
</div>
{config && !tokenEdited && (
<p className="text-xs text-muted-foreground">
Token is hidden for security. Re-enter it to update configuration.
{t('tokenHidden')}
</p>
)}
</div>
<div className="space-y-2">
<Label className="text-muted-foreground">Webhook Verify Token</Label>
<Label className="text-muted-foreground">{t('webhookVerifyToken')}</Label>
<Input
placeholder="Create a custom verify token"
placeholder={t('webhookVerifyTokenPlaceholder')}
value={verifyToken}
onChange={(e) => setVerifyToken(e.target.value)}
className="bg-muted border-border text-foreground placeholder:text-muted-foreground"
/>
<p className="text-xs text-muted-foreground">
A custom string you create. Must match the token you set in Meta webhook settings.
{t('webhookVerifyTokenHint')}
</p>
</div>
<div className="space-y-2">
<Label className="text-muted-foreground">
Two-step verification PIN
<span className="ml-1 text-muted-foreground">(optional)</span>
{t('twoStepPin')}
<span className="ml-1 text-muted-foreground">{t('optional')}</span>
</Label>
<Input
type="text"
inputMode="numeric"
maxLength={6}
placeholder="6-digit PIN from Meta WhatsApp Manager"
placeholder={t('pinPlaceholder')}
value={pin}
onChange={(e) =>
setPin(e.target.value.replace(/\D/g, '').slice(0, 6))
@ -650,20 +647,7 @@ export function WhatsAppConfig() {
className="bg-muted border-border text-foreground placeholder:text-muted-foreground tracking-widest"
/>
<p className="text-xs text-muted-foreground leading-relaxed">
Needed only to wire <strong className="text-muted-foreground">inbound</strong> messages
for a <strong className="text-muted-foreground">production</strong> number. Set it in{' '}
<strong className="text-muted-foreground">
Meta Business Manager → WhatsApp Accounts → Phone
Numbers → Two-step verification
</strong>
, then paste it here so wacrm can subscribe the number —
otherwise Meta routes inbound events to whichever app
last claimed it (the symptom that hits second numbers
under a shared WABA).{' '}
<strong className="text-muted-foreground">Meta test numbers</strong> have no
PIN and are pre-registered — leave this blank for them.
Leaving it blank also keeps an existing registration
untouched.
<span dangerouslySetInnerHTML={{ __html: t('pinHint') }} />
</p>
</div>
</CardContent>
@ -672,14 +656,14 @@ export function WhatsAppConfig() {
{/* Webhook URL */}
<Card>
<CardHeader>
<CardTitle className="text-foreground">Webhook Configuration</CardTitle>
<CardTitle className="text-foreground">{t('webhookTitle')}</CardTitle>
<CardDescription className="text-muted-foreground">
Use this URL as your webhook callback in the Meta App Dashboard.
{t('webhookDesc')}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label className="text-muted-foreground">Webhook Callback URL</Label>
<Label className="text-muted-foreground">{t('webhookUrl')}</Label>
<div className="flex gap-2">
<Input
readOnly
@ -709,10 +693,10 @@ export function WhatsAppConfig() {
{saving ? (
<>
<Loader2 className="size-4 animate-spin" />
Saving...
{t('saving')}
</>
) : (
'Save Configuration'
t('saveConfig')
)}
</Button>
<Button
@ -724,12 +708,12 @@ export function WhatsAppConfig() {
{testing ? (
<>
<Loader2 className="size-4 animate-spin" />
Testing...
{t('testing')}
</>
) : (
<>
<Zap className="size-4" />
Test API Connection
{t('testConnection')}
</>
)}
</Button>
@ -743,12 +727,12 @@ export function WhatsAppConfig() {
{resetting ? (
<>
<Loader2 className="size-4 animate-spin" />
Resetting...
{t('resetting')}
</>
) : (
<>
<RotateCcw className="size-4" />
Reset Configuration
{t('resetConfig')}
</>
)}
</Button>
@ -760,9 +744,9 @@ export function WhatsAppConfig() {
<div>
<Card>
<CardHeader>
<CardTitle className="text-foreground text-base">Setup Instructions</CardTitle>
<CardTitle className="text-foreground text-base">{t('setupInstructions')}</CardTitle>
<CardDescription className="text-muted-foreground">
Follow these steps to connect your WhatsApp Business API.
{t('setupInstructionsDesc')}
</CardDescription>
</CardHeader>
<CardContent>
@ -771,15 +755,15 @@ export function WhatsAppConfig() {
<AccordionTrigger className="text-muted-foreground hover:text-foreground hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground">1</span>
Create a Meta App
{t('step1')}
</span>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Go to <span className="text-primary">developers.facebook.com</span></li>
<li>Click &quot;My Apps&quot; and then &quot;Create App&quot;</li>
<li>Select &quot;Business&quot; as the app type</li>
<li>Fill in app details and create</li>
<li dangerouslySetInnerHTML={{ __html: t('step1_1') }} />
<li>{t('step1_2')}</li>
<li>{t('step1_3')}</li>
<li>{t('step1_4')}</li>
</ol>
</AccordionContent>
</AccordionItem>
@ -788,14 +772,14 @@ export function WhatsAppConfig() {
<AccordionTrigger className="text-muted-foreground hover:text-foreground hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground">2</span>
Add WhatsApp Product
{t('step2')}
</span>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>In your app dashboard, click &quot;Add Product&quot;</li>
<li>Find &quot;WhatsApp&quot; and click &quot;Set Up&quot;</li>
<li>Follow the setup wizard to link your business</li>
<li>{t('step2_1')}</li>
<li>{t('step2_2')}</li>
<li>{t('step2_3')}</li>
</ol>
</AccordionContent>
</AccordionItem>
@ -804,15 +788,15 @@ export function WhatsAppConfig() {
<AccordionTrigger className="text-muted-foreground hover:text-foreground hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground">3</span>
Get API Credentials
{t('step3')}
</span>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Go to WhatsApp &gt; API Setup</li>
<li>Copy your <strong className="text-foreground">Phone Number ID</strong></li>
<li>Copy your <strong className="text-foreground">WhatsApp Business Account ID</strong></li>
<li>Generate a <strong className="text-foreground">Permanent Access Token</strong> from Business Settings &gt; System Users</li>
<li>{t('step3_1')}</li>
<li dangerouslySetInnerHTML={{ __html: t('step3_2') }} />
<li dangerouslySetInnerHTML={{ __html: t('step3_3') }} />
<li dangerouslySetInnerHTML={{ __html: t('step3_4') }} />
</ol>
</AccordionContent>
</AccordionItem>
@ -821,16 +805,16 @@ export function WhatsAppConfig() {
<AccordionTrigger className="text-muted-foreground hover:text-foreground hover:no-underline">
<span className="flex items-center gap-2">
<span className="flex size-5 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground">4</span>
Configure Webhooks
{t('step4')}
</span>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Go to WhatsApp &gt; Configuration</li>
<li>Click &quot;Edit&quot; on the Webhook section</li>
<li>Paste the <strong className="text-foreground">Webhook Callback URL</strong> from above</li>
<li>Enter the same <strong className="text-foreground">Verify Token</strong> you set here</li>
<li>Subscribe to &quot;messages&quot; webhook field</li>
<li>{t('step4_1')}</li>
<li>{t('step4_2')}</li>
<li dangerouslySetInnerHTML={{ __html: t('step4_3') }} />
<li dangerouslySetInnerHTML={{ __html: t('step4_4') }} />
<li>{t('step4_5')}</li>
</ol>
</AccordionContent>
</AccordionItem>
@ -844,7 +828,7 @@ export function WhatsAppConfig() {
className="inline-flex items-center gap-1.5 text-sm text-primary hover:text-primary/80 transition-colors"
>
<ExternalLink className="size-3.5" />
Meta WhatsApp API Documentation
{t('metaDocs')}
</a>
</div>
</CardContent>