fix: WaCRM favicon + template language defaults to en_US

1) Favicon
   Next.js was serving its default triangle icon because src/app/
   favicon.ico was the stock placeholder. Replaced with a dynamic
   icon route (src/app/icon.tsx) that renders the sidebar mark —
   emerald-500 rounded square with a white MessageSquare glyph.
   Next.js auto-injects <link rel="icon"> pointing to the generated
   PNG.

2) Template language default
   Users hit Meta error #132001 "Template name does not exist in the
   translation" because the template manager defaulted new templates'
   language to "en" while Meta's WhatsApp templates are typically
   approved under "en_US". Meta treats "en" and "en_US" as distinct
   language codes — a template approved under one is rejected if sent
   with the other.

   Fixes:
   - Default template form language to "en_US" (matches the DB
     default on message_templates.language and the broadcast
     sender's fallback).
   - Swap the freeform <Input> for a datalist-backed input
     autocompleting the ~17 most common Meta language codes.
   - Inline help text: "Must match the exact language code the
     template is approved under on Meta — e.g. en_US and en are
     distinct."

   For the user's existing welcome_message template (language="en"),
   the fix is to edit it in Settings → Message Templates and change
   Language to whatever Meta shows in the WhatsApp Manager
   (typically en_US). One-time data fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-04-17 16:03:02 +03:00
parent f8543270d1
commit 980ce6aa25
No known key found for this signature in database
2 changed files with 87 additions and 3 deletions

45
src/app/icon.tsx Normal file
View File

@ -0,0 +1,45 @@
import { ImageResponse } from "next/og";
// Replaces the default Next.js favicon with the WaCRM mark — emerald
// rounded square + white chat-bubble glyph — matching the sidebar
// logo in `src/components/layout/sidebar.tsx`. Next.js renders this
// at build time and auto-injects <link rel="icon"> into <head>.
//
// This route takes precedence over src/app/favicon.ico, which is the
// Next.js default and can stay on disk harmlessly (or be removed).
export const runtime = "edge";
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#10b981", // emerald-500
borderRadius: 6,
}}
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
</svg>
</div>
),
{ ...size },
);
}

View File

@ -53,15 +53,43 @@ interface TemplateFormData {
footer_text: string; footer_text: string;
} }
// Meta's language codes are exact — "en" and "en_US" are distinct and a
// template approved under one will be rejected if you send with the other
// (Graph API error #132001 "Template name does not exist in the
// translation"). Default to en_US to match the DB default on
// message_templates.language and the broadcasts sender's fallback.
const emptyForm: TemplateFormData = { const emptyForm: TemplateFormData = {
name: '', name: '',
category: 'Marketing', category: 'Marketing',
language: 'en', language: 'en_US',
body_text: '', body_text: '',
header_type: '', header_type: '',
footer_text: '', footer_text: '',
}; };
// Common Meta template language codes. The field still accepts any
// string — this just offers autocomplete for the usual suspects. Full
// list: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates#supported-languages
const COMMON_LANGUAGE_CODES = [
'en_US',
'en_GB',
'en',
'es',
'es_ES',
'es_MX',
'fr',
'fr_FR',
'de',
'it',
'pt_BR',
'pt_PT',
'nl',
'pl',
'ru',
'tr',
'lt',
];
export function TemplateManager() { export function TemplateManager() {
const supabase = createClient(); const supabase = createClient();
const { user, loading: authLoading } = useAuth(); const { user, loading: authLoading } = useAuth();
@ -123,7 +151,7 @@ export function TemplateManager() {
user_id: user.id, user_id: user.id,
name: form.name.trim(), name: form.name.trim(),
category: form.category, category: form.category,
language: form.language.trim() || 'en', language: form.language.trim() || 'en_US',
body_text: form.body_text.trim(), body_text: form.body_text.trim(),
header_type: form.header_type || null, header_type: form.header_type || null,
footer_text: form.footer_text.trim() || null, footer_text: form.footer_text.trim() || null,
@ -285,11 +313,22 @@ export function TemplateManager() {
<div className="space-y-2"> <div className="space-y-2">
<Label className="text-slate-300">Language</Label> <Label className="text-slate-300">Language</Label>
<Input <Input
placeholder="en" list="template-language-codes"
placeholder="en_US"
value={form.language} value={form.language}
onChange={(e) => setForm({ ...form, language: e.target.value })} onChange={(e) => setForm({ ...form, language: e.target.value })}
className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500" className="bg-slate-800 border-slate-700 text-white placeholder:text-slate-500"
/> />
<datalist id="template-language-codes">
{COMMON_LANGUAGE_CODES.map((code) => (
<option key={code} value={code} />
))}
</datalist>
<p className="text-[11px] text-slate-500">
Must match the exact language code the template is approved
under on Meta — e.g. <code>en_US</code> and <code>en</code>{' '}
are distinct.
</p>
</div> </div>
</div> </div>