refactor: tokenize Auth brand-green accents, Skills, Sharing, Plugins, MCP colors

This commit is contained in:
Marco Beretta 2026-06-20 18:56:13 +02:00
parent ff98f70f42
commit 235d2c2a0a
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
20 changed files with 51 additions and 51 deletions

View file

@ -39,7 +39,7 @@ function AuthLayout({
<div className="mx-auto sm:max-w-sm">
<ErrorMessage>
{localize('com_auth_error_invalid_reset_token')}{' '}
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
<a className="font-semibold text-accent-primary hover:underline" href="/forgot-password">
{localize('com_auth_click_here')}
</a>{' '}
{localize('com_auth_to_try_again')}

View file

@ -11,7 +11,7 @@ function Footer({ startupConfig }: { startupConfig: TStartupConfig | null | unde
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-sm text-green-600 underline decoration-transparent transition-all duration-200 hover:text-green-700 hover:decoration-green-700 focus:text-green-700 focus:decoration-green-700 dark:text-green-500 dark:hover:text-green-400 dark:hover:decoration-green-400 dark:focus:text-green-400 dark:focus:decoration-green-400"
className="text-sm text-accent-primary underline decoration-transparent transition-all duration-200 hover:text-accent-primary-hover hover:decoration-accent-primary-hover focus:text-accent-primary-hover focus:decoration-accent-primary-hover"
href={privacyPolicy.externalUrl}
// Removed for WCAG compliance
// target={privacyPolicy.openNewTab ? '_blank' : undefined}
@ -23,7 +23,7 @@ function Footer({ startupConfig }: { startupConfig: TStartupConfig | null | unde
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-sm text-green-600 underline decoration-transparent transition-all duration-200 hover:text-green-700 hover:decoration-green-700 focus:text-green-700 focus:decoration-green-700 dark:text-green-500 dark:hover:text-green-400 dark:hover:decoration-green-400 dark:focus:text-green-400 dark:focus:decoration-green-400"
className="text-sm text-accent-primary underline decoration-transparent transition-all duration-200 hover:text-accent-primary-hover hover:decoration-accent-primary-hover focus:text-accent-primary-hover focus:decoration-accent-primary-hover"
href={termsOfService.externalUrl}
// Removed for WCAG compliance
// target={termsOfService.openNewTab ? '_blank' : undefined}

View file

@ -115,7 +115,7 @@ function Login() {
{localize('com_auth_no_account')}{' '}
<a
href={registerPage()}
className="inline-flex p-1 text-sm font-medium text-green-600 underline decoration-transparent transition-all duration-200 hover:text-green-700 hover:decoration-green-700 focus:text-green-700 focus:decoration-green-700 dark:text-green-500 dark:hover:text-green-400 dark:hover:decoration-green-400 dark:focus:text-green-400 dark:focus:decoration-green-400"
className="inline-flex p-1 text-sm font-medium text-accent-primary underline decoration-transparent transition-all duration-200 hover:text-accent-primary-hover hover:decoration-accent-primary-hover focus:text-accent-primary-hover focus:decoration-accent-primary-hover"
>
{localize('com_auth_sign_up')}
</a>

View file

@ -32,10 +32,10 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
const validTheme = isDark(theme) ? 'dark' : 'light';
const requireCaptcha = Boolean(startupConfig.turnstile?.siteKey);
const authInputClassName =
'webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-green-500 focus:outline-none focus-visible:border-green-500';
'webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-accent-primary focus:outline-none focus-visible:border-accent-primary';
const authSecretInputClassName = `${authInputClassName} h-auto pr-12`;
const authLabelClassName =
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-accent-primary rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
const authSecretButtonClassName =
'size-9 rounded-xl text-text-secondary-alt hover:bg-transparent hover:text-text-primary';
@ -148,7 +148,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
{startupConfig.passwordResetEnabled && (
<a
href="/forgot-password"
className="inline-flex p-1 text-sm font-medium text-green-600 underline decoration-transparent transition-all duration-200 hover:text-green-700 hover:decoration-green-700 focus:text-green-700 focus:decoration-green-700 dark:text-green-500 dark:hover:text-green-400 dark:hover:decoration-green-400 dark:focus:text-green-400 dark:focus:decoration-green-400"
className="inline-flex p-1 text-sm font-medium text-accent-primary underline decoration-transparent transition-all duration-200 hover:text-accent-primary-hover hover:decoration-accent-primary-hover focus:text-accent-primary-hover focus:decoration-accent-primary-hover"
>
{localize('com_auth_password_forgot')}
</a>

View file

@ -37,10 +37,10 @@ const Registration: React.FC = () => {
// only require captcha if we have a siteKey
const requireCaptcha = Boolean(startupConfig?.turnstile?.siteKey);
const authInputClassName =
'webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-green-500 focus:outline-none focus-visible:border-green-500';
'webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-accent-primary focus:outline-none focus-visible:border-accent-primary';
const authSecretInputClassName = `${authInputClassName} h-auto pr-12`;
const authLabelClassName =
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-accent-primary rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
const authSecretButtonClassName =
'size-9 rounded-xl text-text-secondary-alt hover:bg-transparent hover:text-text-primary';
@ -244,7 +244,7 @@ const Registration: React.FC = () => {
<a
href={loginPage()}
aria-label="Login"
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
className="inline-flex p-1 text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
>
{localize('com_auth_login')}
</a>

View file

@ -23,7 +23,7 @@ const ResetPasswordBodyText = () => {
<div className="flex flex-col space-y-4">
<p>{localize('com_auth_reset_password_if_email_exists')}</p>
<a
className="inline-flex text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
className="inline-flex text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
href={loginPage()}
>
{localize('com_auth_back_to_login')}
@ -53,7 +53,7 @@ function RequestPasswordReset() {
setBodyText(
<span>
{localize('com_auth_click')}{' '}
<a className="text-green-500 hover:underline" href={data.link}>
<a className="text-accent-primary hover:underline" href={data.link}>
{localize('com_auth_here')}
</a>{' '}
{localize('com_auth_to_reset_your_password')}
@ -105,12 +105,12 @@ function RequestPasswordReset() {
},
})}
aria-invalid={!!errors.email}
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-accent-primary focus:outline-none"
placeholder=" "
/>
<label
htmlFor="email"
className="absolute -top-2 left-2 z-10 bg-surface-primary px-2 text-sm text-text-secondary transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base peer-placeholder-shown:text-text-tertiary peer-focus:-top-2 peer-focus:text-sm peer-focus:text-green-600 dark:peer-focus:text-green-500"
className="absolute -top-2 left-2 z-10 bg-surface-primary px-2 text-sm text-text-secondary transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base peer-placeholder-shown:text-text-tertiary peer-focus:-top-2 peer-focus:text-sm peer-focus:text-accent-primary"
>
{localize('com_auth_email_address')}
</label>
@ -133,7 +133,7 @@ function RequestPasswordReset() {
</Button>
<a
href={loginPage()}
className="block text-center text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
className="block text-center text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
>
{localize('com_auth_back_to_login')}
</a>

View file

@ -21,9 +21,9 @@ function ResetPassword() {
const resetPassword = useResetPasswordMutation();
const { setError, setHeaderText, startupConfig } = useOutletContext<TLoginLayoutContext>();
const authInputClassName =
'webkit-dark-styles transition-color peer h-auto w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pr-12 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-green-500 focus:outline-none focus-visible:border-green-500';
'webkit-dark-styles transition-color peer h-auto w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pr-12 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-accent-primary focus:outline-none focus-visible:border-accent-primary';
const authLabelClassName =
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-accent-primary rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4';
const authSecretButtonClassName =
'size-9 rounded-xl text-text-secondary-alt hover:bg-transparent hover:text-text-primary';

View file

@ -156,7 +156,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
<button
type="button"
onClick={toggleBackupOn}
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
className="inline-flex p-1 text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
>
{localize('com_ui_use_backup_code')}
</button>
@ -164,7 +164,7 @@ const TwoFactorScreen: React.FC = React.memo(() => {
<button
type="button"
onClick={toggleBackupOff}
className="inline-flex p-1 text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"
className="inline-flex p-1 text-sm font-medium text-accent-primary transition-colors hover:text-accent-primary-hover"
>
{localize('com_ui_use_2fa_code')}
</button>

View file

@ -105,7 +105,7 @@ function RequestPasswordReset() {
{localize('com_auth_email_verification_in_progress')}
</h1>
<div className="mt-4 flex justify-center">
<Spinner className="h-8 w-8 text-green-500" />
<Spinner className="h-8 w-8 text-accent-primary" />
</div>
</div>
);

View file

@ -58,7 +58,7 @@ function AuthField({ name, config, hasValue, control, errors, autoFocus }: AuthF
<div aria-hidden="true">
{hasValue ? (
<div className="flex min-w-fit items-center gap-2 whitespace-nowrap rounded-full border border-border-light px-2 py-0.5 text-xs font-medium text-text-secondary">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<div className="h-1.5 w-1.5 rounded-full bg-status-success" />
<span>{localize('com_ui_set')}</span>
</div>
) : (
@ -104,7 +104,7 @@ function AuthField({ name, config, hasValue, control, errors, autoFocus }: AuthF
dangerouslySetInnerHTML={{ __html: sanitizedDescription }}
/>
)}
{errors[name] && <p className="text-xs text-red-500">{errors[name]?.message}</p>}
{errors[name] && <p className="text-xs text-text-destructive">{errors[name]?.message}</p>}
</div>
);
}

View file

@ -111,27 +111,27 @@ interface CompactStatusDotProps {
function CompactStatusDot({ serverStatus, isInitializing }: CompactStatusDotProps) {
if (isInitializing) {
return (
<div className="flex size-3.5 items-center justify-center rounded-full border-2 border-surface-secondary bg-blue-500">
<div className="flex size-3.5 items-center justify-center rounded-full border-2 border-surface-secondary bg-status-info">
<div className="size-1.5 animate-pulse rounded-full bg-white" />
</div>
);
}
if (!serverStatus) {
return <div className="size-3 rounded-full border-2 border-surface-secondary bg-gray-400" />;
return <div className="size-3 rounded-full border-2 border-surface-secondary bg-status-neutral" />;
}
const { connectionState, requiresOAuth } = serverStatus;
let colorClass = 'bg-gray-400';
let colorClass = 'bg-status-neutral';
if (connectionState === 'connected') {
colorClass = 'bg-green-500';
colorClass = 'bg-status-success';
} else if (connectionState === 'connecting') {
colorClass = 'bg-blue-500';
colorClass = 'bg-status-info';
} else if (connectionState === 'error') {
colorClass = 'bg-red-500';
colorClass = 'bg-status-error';
} else if (connectionState === 'disconnected' && requiresOAuth) {
colorClass = 'bg-amber-500';
colorClass = 'bg-status-warning';
}
return (

View file

@ -79,42 +79,42 @@ export function getStatusColor(
): string {
// In-progress states: blue
if (isInitializing?.(serverName)) {
return 'bg-blue-500';
return 'bg-status-info';
}
const status = connectionStatus?.[serverName];
if (!status) {
return 'bg-gray-400';
return 'bg-status-neutral';
}
const { connectionState, requiresOAuth } = status;
// Connecting: blue (in progress)
if (connectionState === 'connecting') {
return 'bg-blue-500';
return 'bg-status-info';
}
// Connected: green (success)
if (connectionState === 'connected') {
return 'bg-green-500';
return 'bg-status-success';
}
// Error: red
if (connectionState === 'error') {
return 'bg-red-500';
return 'bg-status-error';
}
// Disconnected: check if needs action or just inactive
if (connectionState === 'disconnected') {
// Needs OAuth = amber (requires user action)
if (requiresOAuth) {
return 'bg-amber-500';
return 'bg-status-warning';
}
// Simply disconnected = gray (neutral/inactive)
return 'bg-gray-400';
return 'bg-status-neutral';
}
return 'bg-gray-400';
return 'bg-status-neutral';
}
export function getStatusTextKey(

View file

@ -41,7 +41,7 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
const authField = config.authField.split('||')[0];
const isOptional = config.optional === true;
const inputClassName =
'flex h-10 max-h-10 w-full resize-none rounded-md border border-gray-200 bg-transparent px-3 py-2 text-sm text-gray-700 shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:border-gray-400 focus:bg-gray-50 focus:outline-none focus:ring-0 focus:ring-gray-400 focus:ring-opacity-0 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 focus:dark:bg-gray-600 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
'flex h-10 max-h-10 w-full resize-none rounded-md border border-border-light bg-transparent px-3 py-2 text-sm text-text-secondary shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-text-tertiary focus:border-border-medium focus:bg-surface-secondary focus:outline-none focus:ring-0 focus:ring-gray-400 focus:ring-opacity-0 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-700 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
const sharedProps = {
id: authField,
'aria-invalid': !!errors[authField],
@ -90,7 +90,7 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
<PluginTooltip content={config.description} position="right" />
</HoverCard>
{errors[authField] && (
<span role="alert" className="mt-1 text-sm text-red-400">
<span role="alert" className="mt-1 text-sm text-text-destructive">
{String(errors?.[authField]?.message ?? '')}
</span>
)}

View file

@ -10,7 +10,7 @@ function PluginTooltip({ content, position }: TPluginTooltipProps) {
<HoverCardPortal>
<HoverCardContent side={position} className="w-80">
<div className="space-y-2">
<div className="text-sm text-gray-600 dark:text-gray-300">{content}</div>
<div className="text-sm text-text-secondary">{content}</div>
</div>
</HoverCardContent>
</HoverCardPortal>

View file

@ -256,7 +256,7 @@ export default function GenericGrantAccessDialog({
aria-label={localize('com_ui_permissions_failed_load')}
className={cn('h-9', buttonClassName)}
>
<div className="flex min-w-[32px] items-center justify-center text-red-500">
<div className="flex min-w-[32px] items-center justify-center text-text-destructive">
<span className="flex h-6 w-6 items-center justify-center">
{isFetchingPermissions ? (
<Spinner className="h-4 w-4" />
@ -357,7 +357,7 @@ export default function GenericGrantAccessDialog({
<div className="space-y-2">
{!hasAtLeastOneOwner && hasChanges && (
<div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-center">
<div className="flex items-center justify-center gap-2 text-sm text-red-600 dark:text-red-400">
<div className="flex items-center justify-center gap-2 text-sm text-text-destructive">
<UserX className="h-4 w-4" aria-hidden="true" />
{localize('com_ui_at_least_one_owner_required')}
</div>

View file

@ -194,7 +194,7 @@ const PeoplePickerAdminSettings = () => {
isLoading ||
(isSelectedCustomRole && (isCustomRoleLoading || isCustomRoleError))
}
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
className="btn rounded bg-surface-submit font-bold text-white transition-all hover:bg-surface-submit-hover"
>
{localize('com_ui_save')}
</button>

View file

@ -124,7 +124,7 @@ export default function CreateSkillDialog({
},
})}
/>
{errors.name && <p className="text-xs text-red-500">{errors.name.message}</p>}
{errors.name && <p className="text-xs text-text-destructive">{errors.name.message}</p>}
</div>
{/* Description */}

View file

@ -187,7 +187,7 @@ export default function CreateSkillForm({
<div
id="skill-name-error"
className={cn(
'mt-1 w-56 text-sm text-red-500',
'mt-1 w-56 text-sm text-text-destructive',
errors.name ? 'visible h-auto' : 'invisible h-0',
)}
role={errors.name ? 'alert' : undefined}
@ -223,7 +223,7 @@ export default function CreateSkillForm({
className="mb-1 text-sm font-medium text-text-secondary"
>
{localize('com_ui_description')}
<span className="ml-0.5 text-red-500">*</span>
<span className="ml-0.5 text-text-destructive">*</span>
</label>
<TextareaAutosize
{...field}
@ -242,7 +242,7 @@ export default function CreateSkillForm({
{errors.description && (
<p
id="skill-description-error"
className="mt-1 text-sm text-red-500"
className="mt-1 text-sm text-text-destructive"
role="alert"
>
{errors.description.message}

View file

@ -144,7 +144,7 @@ const SkillContentEditor: React.FC<SkillContentEditorProps> = ({
}
/>
{errors[name] && (
<p className="mt-1 text-sm text-red-500" role="alert">
<p className="mt-1 text-sm text-text-destructive" role="alert">
{errors[name]?.message as string}
</p>
)}

View file

@ -207,7 +207,7 @@ export default function SkillForm({ skillId }: SkillFormProps) {
<div
id="skill-name-error"
className={cn(
'mt-1 w-56 text-sm text-red-500',
'mt-1 w-56 text-sm text-text-destructive',
errors.name ? 'visible h-auto' : 'invisible h-0',
)}
role={errors.name ? 'alert' : undefined}
@ -284,7 +284,7 @@ export default function SkillForm({ skillId }: SkillFormProps) {
className="mb-1 text-sm font-medium text-text-secondary"
>
{localize('com_ui_description')}
<span className="ml-0.5 text-red-500">*</span>
<span className="ml-0.5 text-text-destructive">*</span>
</label>
<TextareaAutosize
{...field}
@ -303,7 +303,7 @@ export default function SkillForm({ skillId }: SkillFormProps) {
{errors.description && (
<p
id="skill-description-error"
className="mt-1 text-sm text-red-500"
className="mt-1 text-sm text-text-destructive"
role="alert"
>
{errors.description.message}