mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
feat(i18n): complete settings module internationalization
This commit is contained in:
parent
caf0f3660e
commit
9357fd9ec9
186
messages/en.json
186
messages/en.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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">
|
||||
"{lastRegistrationError}"
|
||||
</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 "My Apps" and then "Create App"</li>
|
||||
<li>Select "Business" 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 "Add Product"</li>
|
||||
<li>Find "WhatsApp" and click "Set Up"</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 > 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 > 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 > Configuration</li>
|
||||
<li>Click "Edit" 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 "messages" 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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user