refactor(tenants): visibility form requires explicit appearance values
Simplifies the tenant Visibility tab to match the tenant-only appearance
model established by the previous commits:
- primary_color: now required. Removed the "No brand color / Use system
default (no brand accent)" toggle — every tenant carries a concrete
primary color. Legacy NULL rows render the neutral app default (#2fa4a4)
in the color picker and are converted to that explicit hex on save.
- default_theme: the select no longer offers a "Use system default" empty
option. Legacy NULL rows resolve to 'light' at render time; saves always
persist a valid theme via SettingsAppGateway::normalizeTheme().
- allow_user_theme: replaced the tri-state "Use system default (allowed) /
Force allow / Force disallow" select with a single boolean switch
("Users may choose their own theme"). Legacy NULL rows load as checked.
Saves persist 0/1 explicitly.
TenantService: sanitize no longer reads primary_color_use_default or
allow_user_theme_mode; it validates primary_color as a required hex and
treats allow_user_theme as a plain boolean. Both create and update paths
write concrete values only — no more NULL writes for these three fields.
DirectorySettingsGateway gains a normalizeTheme() delegate so TenantService
can route through the same gateway it uses for isAllowedTheme().
Removed now-unused app-color-default-toggle JS component + its runtime
registration + its architecture-test entry. i18n cleanup: "No brand
color", "Use system default (no brand accent)", "When enabled the tenant
renders without a brand accent color.", "Use system default (light)",
"Use system default (allowed)", "Force allow user theme", "Force disallow
user theme", "User theme policy is invalid" all removed. New copy: "Users
may choose their own theme" + helper text, plus a tightened tab blockquote.
Tests: TenantServiceTest validInput() updated to send concrete values;
settingsGateway mock gets normalizeTheme() + isAllowedTheme() defaults.
All 1985 tests pass; PHPStan level 5 clean; QG-006 clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,6 @@ import { initContrastToggle } from './components/app-contrast-toggle.js';
|
||||
import { initConfirmActions } from './components/app-confirm-actions.js';
|
||||
import { initDetailsState } from './components/app-details-state.js';
|
||||
import { initTenantSwitcher } from './components/app-tenant-switcher.js';
|
||||
import { initColorDefaultToggle } from './components/app-color-default-toggle.js';
|
||||
import { initCustomFieldOptionsToggle } from './components/app-custom-field-options-toggle.js';
|
||||
import { initTenantSsoToggle } from './components/app-tenant-sso-toggle.js';
|
||||
import { initTenantLdapToggle } from './components/app-tenant-ldap-toggle.js';
|
||||
@@ -233,10 +232,6 @@ const bootRuntime = async () => {
|
||||
selector: '[data-app-component="tenant-switcher"]',
|
||||
configPath: 'components.tenantSwitcher',
|
||||
});
|
||||
runtime.register('color-default-toggle', initColorDefaultToggle, {
|
||||
scope: 'global',
|
||||
configPath: 'components.colorDefaultToggle',
|
||||
});
|
||||
runtime.register('custom-field-options-toggle', initCustomFieldOptionsToggle, {
|
||||
scope: 'global',
|
||||
configPath: 'components.customFieldOptionsToggle',
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Toggles color input between custom value and default/inherited color.
|
||||
*/
|
||||
import { warnOnce } from '../core/app-dom.js';
|
||||
import { createConditionalToggleInit } from '../core/app-conditional-controls.js';
|
||||
|
||||
const syncToggle = (toggle, scope) => {
|
||||
const targetSelector = toggle.dataset.colorTarget || '';
|
||||
if (!targetSelector) {
|
||||
warnOnce('UI_EL_MISSING', 'Missing data-color-target', { module: 'color-default-toggle' });
|
||||
return;
|
||||
}
|
||||
const target = scope.querySelector(targetSelector);
|
||||
if (!(target instanceof HTMLInputElement)) {
|
||||
warnOnce('UI_EL_MISSING', `Missing color target: ${targetSelector}`, { module: 'color-default-toggle' });
|
||||
return;
|
||||
}
|
||||
const defaultValue = toggle.dataset.colorDefault || '#2fa4a4';
|
||||
if (toggle.checked) {
|
||||
if (!target.dataset.customValue) {
|
||||
target.dataset.customValue = target.value;
|
||||
}
|
||||
target.value = defaultValue;
|
||||
target.setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
target.removeAttribute('disabled');
|
||||
if (target.dataset.customValue) {
|
||||
target.value = target.dataset.customValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initRoot = (toggle) => {
|
||||
if (!(toggle instanceof HTMLInputElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scope = document;
|
||||
const targetSelector = toggle.dataset.colorTarget || '';
|
||||
const target = targetSelector ? scope.querySelector(targetSelector) : null;
|
||||
if (targetSelector && !target) {
|
||||
warnOnce('UI_EL_MISSING', `Missing color target: ${targetSelector}`, { module: 'color-default-toggle' });
|
||||
}
|
||||
|
||||
const onToggleChange = () => syncToggle(toggle, scope);
|
||||
toggle.addEventListener('change', onToggleChange);
|
||||
const cleanups = [() => toggle.removeEventListener('change', onToggleChange)];
|
||||
|
||||
if (target instanceof HTMLInputElement) {
|
||||
const onTargetInput = () => {
|
||||
if (toggle.checked) {
|
||||
toggle.checked = false;
|
||||
target.removeAttribute('disabled');
|
||||
}
|
||||
};
|
||||
target.addEventListener('input', onTargetInput);
|
||||
cleanups.push(() => target.removeEventListener('input', onTargetInput));
|
||||
}
|
||||
|
||||
syncToggle(toggle, scope);
|
||||
return () => cleanups.forEach((fn) => fn());
|
||||
};
|
||||
|
||||
export const initColorDefaultToggle = createConditionalToggleInit({
|
||||
rootSelector: '[data-color-default-toggle]',
|
||||
boundKey: 'colorDefaultToggleBound',
|
||||
initRoot,
|
||||
});
|
||||
Reference in New Issue
Block a user