fix(frontend): prevent hydration errors in admin security form and improve autofill support (#5613)

# Description of Changes

## What was changed
- Updated several Mantine `label` compositions in
`AdminSecuritySection.tsx` to avoid invalid HTML nesting that can
trigger React hydration errors (e.g., a `<div>` rendered inside a
`<p>`).
- Changed `Group` used inside `NumberInput` / `Select` labels to render
as an inline element via `component="span"`.
- Added `name` attributes to multiple form controls (`Switch`, `Select`,
`NumberInput`, `Textarea`) to satisfy browser/autofill recommendations
and improve form field identification.

## Why the change was made
- Fixes the runtime warning/error:
- `In HTML, <div> cannot be a descendant of <p>. This will cause a
hydration error.`
- Caused by block-level wrappers inside Mantine `Text`/`p` label
rendering.
- Addresses the browser audit warning:
  - `A form field element should have an id or name attribute`
- Adding stable `name` attributes improves autofill behavior and form
accessibility tooling.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Ludy
2026-01-31 17:26:15 +01:00
committed by GitHub
parent a46dc141d0
commit d990d4181d

View File

@@ -219,6 +219,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="enableLogin"
checked={settings?.enableLogin || false}
onChange={(e) => setSettings({ ...settings, enableLogin: e.target.checked })}
disabled={!loginEnabled}
@@ -229,6 +230,7 @@ export default function AdminSecuritySection() {
<div>
<Select
name="loginMethod"
label={t('admin.settings.security.loginMethod.label', 'Login Method')}
description={t('admin.settings.security.loginMethod.description', 'The authentication method to use for user login')}
value={settings?.loginMethod || 'all'}
@@ -251,8 +253,9 @@ export default function AdminSecuritySection() {
<div>
<NumberInput
name="loginAttemptCount"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.loginAttemptCount.label', 'Login Attempt Limit')}</span>
<PendingBadge show={isFieldPending('loginAttemptCount')} />
</Group>
@@ -268,8 +271,9 @@ export default function AdminSecuritySection() {
<div>
<NumberInput
name="loginResetTimeMinutes"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.loginResetTimeMinutes.label', 'Login Reset Time (minutes)')}</span>
<PendingBadge show={isFieldPending('loginResetTimeMinutes')} />
</Group>
@@ -285,8 +289,9 @@ export default function AdminSecuritySection() {
<div>
<Select
name="xFrameOptions"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.xFrameOptions.label', 'X-Frame-Options')}</span>
<PendingBadge show={isFieldPending('xFrameOptions')} />
</Group>
@@ -332,6 +337,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="jwt_persistence"
checked={settings?.jwt?.persistence || false}
onChange={(e) => setSettings({ ...settings, jwt: { ...settings?.jwt, persistence: e.target.checked } })}
disabled={!loginEnabled}
@@ -349,6 +355,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="jwt_enableKeyRotation"
checked={settings?.jwt?.enableKeyRotation || false}
onChange={(e) => setSettings({ ...settings, jwt: { ...settings?.jwt, enableKeyRotation: e.target.checked } })}
disabled={!loginEnabled}
@@ -366,6 +373,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="jwt_enableKeyCleanup"
checked={settings?.jwt?.enableKeyCleanup || false}
onChange={(e) => setSettings({ ...settings, jwt: { ...settings?.jwt, enableKeyCleanup: e.target.checked } })}
disabled={!loginEnabled}
@@ -376,8 +384,9 @@ export default function AdminSecuritySection() {
<div>
<NumberInput
name="jwt_keyRetentionDays"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.jwt.keyRetentionDays.label', 'Key Retention Days')}</span>
<PendingBadge show={isFieldPending('jwt.keyRetentionDays')} />
</Group>
@@ -400,6 +409,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="jwt_secureCookie"
checked={settings?.jwt?.secureCookie || false}
onChange={(e) => setSettings({ ...settings, jwt: { ...settings?.jwt, secureCookie: e.target.checked } })}
disabled={!loginEnabled}
@@ -427,6 +437,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="audit_enabled"
checked={settings?.audit?.enabled || false}
onChange={(e) => setSettings({ ...settings, audit: { ...settings?.audit, enabled: e.target.checked } })}
disabled={!loginEnabled}
@@ -437,8 +448,9 @@ export default function AdminSecuritySection() {
<div>
<NumberInput
name="audit_level"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.audit.level.label', 'Audit Level')}</span>
<PendingBadge show={isFieldPending('audit.level')} />
</Group>
@@ -454,8 +466,9 @@ export default function AdminSecuritySection() {
<div>
<NumberInput
name="audit_retentionDays"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.audit.retentionDays.label', 'Audit Retention (days)')}</span>
<PendingBadge show={isFieldPending('audit.retentionDays')} />
</Group>
@@ -490,6 +503,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="html_urlSecurity_enabled"
checked={settings?.html?.urlSecurity?.enabled || false}
onChange={(e) => setSettings({
...settings,
@@ -506,8 +520,9 @@ export default function AdminSecuritySection() {
<div>
<Select
name="html_urlSecurity_level"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.htmlUrlSecurity.level.label', 'Security Level')}</span>
<PendingBadge show={isFieldPending('html.urlSecurity.level')} />
</Group>
@@ -539,8 +554,9 @@ export default function AdminSecuritySection() {
{/* Allowed Domains */}
<div>
<Textarea
name="html_urlSecurity_allowedDomains"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.htmlUrlSecurity.allowedDomains.label', 'Allowed Domains (Whitelist)')}</span>
<PendingBadge show={isFieldPending('html.urlSecurity.allowedDomains')} />
</Group>
@@ -567,8 +583,9 @@ export default function AdminSecuritySection() {
{/* Blocked Domains */}
<div>
<Textarea
name="html_urlSecurity_blockedDomains"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.htmlUrlSecurity.blockedDomains.label', 'Blocked Domains (Blacklist)')}</span>
<PendingBadge show={isFieldPending('html.urlSecurity.blockedDomains')} />
</Group>
@@ -595,8 +612,9 @@ export default function AdminSecuritySection() {
{/* Internal TLDs */}
<div>
<Textarea
name="html_urlSecurity_internalTlds"
label={
<Group gap="xs">
<Group component="span" gap="xs">
<span>{t('admin.settings.security.htmlUrlSecurity.internalTlds.label', 'Internal TLDs')}</span>
<PendingBadge show={isFieldPending('html.urlSecurity.internalTlds')} />
</Group>
@@ -632,6 +650,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="html_urlSecurity_blockPrivateNetworks"
checked={settings?.html?.urlSecurity?.blockPrivateNetworks || false}
onChange={(e) => setSettings({
...settings,
@@ -655,6 +674,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="html_urlSecurity_blockLocalhost"
checked={settings?.html?.urlSecurity?.blockLocalhost || false}
onChange={(e) => setSettings({
...settings,
@@ -678,6 +698,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="html_urlSecurity_blockLinkLocal"
checked={settings?.html?.urlSecurity?.blockLinkLocal || false}
onChange={(e) => setSettings({
...settings,
@@ -701,6 +722,7 @@ export default function AdminSecuritySection() {
</div>
<Group gap="xs">
<Switch
name="html_urlSecurity_blockCloudMetadata"
checked={settings?.html?.urlSecurity?.blockCloudMetadata || false}
onChange={(e) => setSettings({
...settings,