Merge pull request #649 from the-luap/fix/branding-customcss-preset-drop-645
fix(branding+whatsapp): preserve customCss through preset switches (#645) + admin-pinned WhatsApp template language (#647)
This commit is contained in:
@@ -259,7 +259,13 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
if (preset) {
|
||||
setSelectedPreset(presetKey);
|
||||
setLocalTheme(preset.config);
|
||||
setCustomCss(''); // Clear custom CSS when selecting a preset
|
||||
// Don't wipe customCss on preset pick — preset configs carry no
|
||||
// customCss, and the admin's persisted styling extras should
|
||||
// survive a layout switch (#645). Matches ThemeCustomizer.tsx
|
||||
// which never cleared it. The parent's handleThemeChange merges
|
||||
// via `customCss: newTheme.customCss ?? currentTheme.customCss`,
|
||||
// so propagating preset.config (no customCss) keeps the saved
|
||||
// value intact end-to-end.
|
||||
if (onPresetChange) {
|
||||
onPresetChange(presetKey);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export const WhatsAppTab: React.FC = () => {
|
||||
const [wabaId, setWabaId] = useState('');
|
||||
const [accessToken, setAccessToken] = useState('');
|
||||
const [templateName, setTemplateName] = useState('gallery_ready');
|
||||
const [templateLanguage, setTemplateLanguage] = useState('');
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [showToken, setShowToken] = useState(false);
|
||||
const [testPhone, setTestPhone] = useState('');
|
||||
@@ -43,6 +44,7 @@ export const WhatsAppTab: React.FC = () => {
|
||||
// Leave it visible-as-masked so the admin sees that a token exists.
|
||||
setAccessToken(data.access_token || '');
|
||||
setTemplateName(data.template_name || 'gallery_ready');
|
||||
setTemplateLanguage(data.template_language || '');
|
||||
setEnabled(Boolean(data.enabled));
|
||||
}
|
||||
}, [data]);
|
||||
@@ -53,6 +55,7 @@ export const WhatsAppTab: React.FC = () => {
|
||||
waba_id: wabaId,
|
||||
access_token: accessToken,
|
||||
template_name: templateName,
|
||||
template_language: templateLanguage,
|
||||
enabled,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
@@ -175,6 +178,23 @@ export const WhatsAppTab: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
|
||||
{t('settings.whatsapp.templateLanguage', 'Template language')}
|
||||
</label>
|
||||
<Input
|
||||
value={templateLanguage}
|
||||
onChange={(e) => setTemplateLanguage(e.target.value)}
|
||||
placeholder={t('settings.whatsapp.templateLanguagePlaceholder', 'e.g. en_US, de_DE, ar, pt_BR') as string}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{t(
|
||||
'settings.whatsapp.templateLanguageHint',
|
||||
'Meta template language code, exactly as you registered it in Meta Business Manager (`ar`, `en_US`, `de_DE`, `pt_BR`, etc.). Leave empty to fall back to the system default language. Meta returns "template not found in language" if this doesn\'t match a registered template.',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-sm text-neutral-800 dark:text-neutral-200">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -1835,6 +1835,9 @@
|
||||
"accessTokenHint": "Beim Abruf maskiert als \"********\" gespeichert. Maskierten Wert beibehalten, um das bestehende Token zu behalten; neuen Wert eingeben, um zu ersetzen.",
|
||||
"templateName": "Vorlagenname",
|
||||
"templateNameHint": "Name der von Meta genehmigten Nachrichtenvorlage. Die Standardvorlage `gallery_ready` erwartet 5 Body-Parameter: Kundenname, Event-Name, Galerie-Link, Passwortzeile, Ablaufdatum. Die Vorlage vor der Aktivierung im Meta Business Manager genehmigen lassen.",
|
||||
"templateLanguage": "Vorlagensprache",
|
||||
"templateLanguagePlaceholder": "z. B. en_US, de_DE, ar, pt_BR",
|
||||
"templateLanguageHint": "Meta-Sprachcode der Vorlage, exakt wie im Meta Business Manager hinterlegt (`ar`, `en_US`, `de_DE`, `pt_BR` usw.). Leer lassen, um auf die Standardsprache der Installation zurückzufallen. Stimmt der Code nicht mit einer registrierten Vorlage überein, meldet Meta „Vorlage in dieser Sprache nicht gefunden“.",
|
||||
"enabled": "WhatsApp-Benachrichtigungen senden",
|
||||
"savedToast": "WhatsApp-Einstellungen gespeichert.",
|
||||
"testHeading": "Testnachricht senden",
|
||||
|
||||
@@ -1391,6 +1391,9 @@
|
||||
"accessTokenHint": "Stored masked as \"********\" on GET. Leave the masked value to keep the existing token; type a new one to replace it.",
|
||||
"templateName": "Template name",
|
||||
"templateNameHint": "Name of the Meta-approved message template. The default `gallery_ready` expects 5 body parameters: customer name, event name, gallery link, password line, expiry date. Approve the template in Meta Business Manager before enabling.",
|
||||
"templateLanguage": "Template language",
|
||||
"templateLanguagePlaceholder": "e.g. en_US, de_DE, ar, pt_BR",
|
||||
"templateLanguageHint": "Meta template language code, exactly as you registered it in Meta Business Manager (`ar`, `en_US`, `de_DE`, `pt_BR`, etc.). Leave empty to fall back to the system default language. Meta returns \"template not found in language\" if this doesn't match a registered template.",
|
||||
"enabled": "Send WhatsApp notifications",
|
||||
"savedToast": "WhatsApp settings saved.",
|
||||
"testHeading": "Send a test message",
|
||||
|
||||
@@ -186,12 +186,14 @@ export const BrandingPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleThemeChange = (newTheme: ThemeConfig) => {
|
||||
// Preset configs don't carry a logoUrl, so a preset change inside the
|
||||
// customizer arrives here with newTheme.logoUrl=undefined. Keep the
|
||||
// existing logo instead of wiping branding_logo_url on save (#317).
|
||||
// Preset configs don't carry a logoUrl or customCss, so a preset change
|
||||
// inside the customizer arrives here with those fields undefined. Keep
|
||||
// the existing values instead of wiping the persisted ones on save
|
||||
// (#317 for logoUrl, #645 for customCss).
|
||||
const mergedTheme: ThemeConfig = {
|
||||
...newTheme,
|
||||
logoUrl: newTheme.logoUrl ?? currentTheme.logoUrl
|
||||
logoUrl: newTheme.logoUrl ?? currentTheme.logoUrl,
|
||||
customCss: newTheme.customCss ?? currentTheme.customCss
|
||||
};
|
||||
setCurrentTheme(mergedTheme);
|
||||
if (newTheme.logoUrl !== undefined && newTheme.logoUrl !== currentTheme.logoUrl) {
|
||||
@@ -207,10 +209,20 @@ export const BrandingPage: React.FC = () => {
|
||||
// Get the preset theme config
|
||||
const preset = GALLERY_THEME_PRESETS[presetName];
|
||||
if (preset) {
|
||||
// Preserve the existing logo when switching presets (#317).
|
||||
setCurrentTheme(prev => ({ ...preset.config, logoUrl: prev.logoUrl }));
|
||||
// Preserve the existing logo + custom CSS when switching presets
|
||||
// (#317 for logo, #645 for customCss). Presets define a look; they
|
||||
// shouldn't silently drop the admin's persisted styling extras.
|
||||
setCurrentTheme(prev => ({
|
||||
...preset.config,
|
||||
logoUrl: prev.logoUrl,
|
||||
customCss: prev.customCss
|
||||
}));
|
||||
if (isPreviewMode) {
|
||||
setTheme({ ...preset.config, logoUrl: currentTheme.logoUrl });
|
||||
setTheme({
|
||||
...preset.config,
|
||||
logoUrl: currentTheme.logoUrl,
|
||||
customCss: currentTheme.customCss
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface WhatsAppConfig {
|
||||
waba_id: string;
|
||||
access_token: string; // masked '********' on GET when a real token is stored
|
||||
template_name: string;
|
||||
// Meta template language code (`ar`, `en_US`, `de_DE`, etc.) (#647). Must
|
||||
// match the language the operator registered with Meta for `template_name`,
|
||||
// otherwise Meta returns template_not_found_in_language (132001). Empty
|
||||
// string falls through to general_default_language.
|
||||
template_language: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user