Skip to content

[Portal] Migrate Advanced settings screens to Radix UI#5771

Open
fungc-io wants to merge 23 commits into
authgear:mainfrom
fungc-io:portal-radix-advanced-settings
Open

[Portal] Migrate Advanced settings screens to Radix UI#5771
fungc-io wants to merge 23 commits into
authgear:mainfrom
fungc-io:portal-radix-advanced-settings

Conversation

@fungc-io

@fungc-io fungc-io commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Migrate the Advanced settings screens to the Radix/v2 component system. This is the non–Hook-page portion of #5761 (the Hooks-page migration is excluded), plus a round of review-driven fixes on top.

Migration (v2 component adoption)

  • Custom Email Provider, SMTP, Custom SMS Gateway, Admin API, Account Deletion, Account Anonymization, Cookie Lifetime, Endpoint Direct Access, SAML Certificate, Edit Config migrated to v2 form controls, SaveFunctionBar, and Radix dialogs.
  • New v2 building blocks: SaveFunctionBar, ConfirmationDialog, extended TextField/FormField/SecondaryButton, Toggle stories.

Review fixes layered on top

  • Forms/a11y: restore the required-field indicator and label↔input association dropped during migration (FormField/TextField).
  • Dialogs/buttons: fix the SMTP test-email dialog (v2 buttons were unstyled inside a Fluent portal) by moving it to a Radix dialog; standardize button order (primary on the right) across the save bar and dialogs.
  • CopyIconButton: extract one shared v2 component with proper unmount cleanup (was duplicated and leaked a timer).
  • SaveFunctionBar: robust content-column alignment (handles late-mounting anchor, window/nested scroll), rAF-throttled, and an appear/disappear animation.
  • Admin API: correct the download icon; align section label width with the other screens.
  • Layout consistency: extract v2/SettingsSectionCard (label + content, responsive stacking) and adopt it across the Advanced screens; align page-title spacing.
  • Assets/i18n: replace the fake-SVG SendGrid logo with a real vector; remove dead translation keys.
  • CI hygiene: make the screens pass eslint, stylelint, and prettier.

Test plan

  • cd portal && npm run typecheck
  • npm run eslint / npm run stylelint / npm run prettier
  • npm run browserslist-coverage-lint
  • npm test (jest)
  • Manually verify each migrated Advanced screen renders, saves/discards, and is responsive on narrow viewports.

🤖 Generated with Claude Code

jimmycwc and others added 23 commits June 1, 2026 15:03
Add password type and plain suffix support on TextField, optional
labelSize on FormField, and match disabled SecondaryButton styling to
the design system.

Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce a floating save bar aligned to content width, with discard
confirmation and Storybook coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the enable toggle with provider cards, adopt v2 form controls and
SaveFunctionBar, update typography and spacing to match design, and swap
provider logos to SVG assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use v2 components for Admin API Configuration screen and make the keys table horizontally scrollable on narrow viewports.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use ConfirmationDialog for SaveFunctionBar, FormContainer reset, and
navigation blocker flows. Raise dialog overlay z-index so Monaco editor
UI does not paint above the modal mask.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document default, checked, with-text, and disabled Toggle variants.

Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor Account Deletion, Account Anonymization, Cookie Lifetime,
Endpoint Direct Access, SMTP, SMS Provider, Admin API, Edit Config,
and SAML Certificate to v2 layout patterns with SaveFunctionBar.
Align content width to Admin API grid span 9 and add page descriptions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use inline path elements instead of a base64 PNG for sharper rendering.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restore the required-field indicator and the label/input association that the
Radix/v2 migration dropped from the Advanced-settings screens.

- FormField: add `required` (renders a red asterisk) and `htmlFor` (renders the
  label as a <label htmlFor> so clicking focuses the control and screen readers
  announce the pairing).
- TextField: generate an id, forward it to the label (htmlFor) and the input
  (id), and pass `required` through to the input.
- Re-mark the SMTP and SMS fields that carried required={true} before the
  migration; for the Twilio sender/credential radio groups the marker goes on
  the group FormField label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The send-test-email dialog was left as a Fluent Dialog while its Send/Cancel
buttons were migrated to the v2 (Radix) PrimaryButton/SecondaryButton. Fluent's
Dialog portals to a Layer on document.body, outside the Radix <Theme> provider,
so the Radix buttons rendered without their theme tokens and were invisible.

Migrate the dialog to a Radix Dialog (matching the ConfirmationDialog pattern)
so its content renders inside the Theme context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The copy-to-clipboard icon button was duplicated verbatim in
AdminAPIConfigurationScreen and SMSProviderConfigurationScreen, and neither
copy cleared its 2s "copied" reset timer on unmount (it fired setState on an
unmounted component when a table row was removed or the screen was left).

Extract a single components/v2/CopyIconButton that clears the timer in an
effect cleanup, and use it in both screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The key-row "Download" action used Pencil1Icon (an edit icon); switch it to
DownloadIcon. When only one key remains, Delete is disabled with no
explanation — restore the "At least 1 key per project" hint (reusing the
AdminAPIConfigurationScreen.keys.delete.tooltip string the migration had
orphaned) as a DropdownMenu.Label shown below the disabled item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The alignment hook only measured the anchor on a one-shot layout effect keyed
by the ref object, used getBoundingClientRect + setState on every scroll event
with no throttle, and located the scroll container with a fragile overflowY
walk that missed window-level scrolling.

- rAF-coalesce measurements and skip setState when left/width are unchanged, so
  scrolling no longer re-renders the bar every frame.
- Listen for scroll in the capture phase on window, which catches any ancestor
  scroll container (nested or the window/document) without locating it.
- Retry measurement until a late-mounting anchor appears, and (re)observe the
  current anchor element, instead of giving up when it is null.
- Hoist the discard dialog's onOpenChange into a stable useCallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hint shown when only one key remains is unnecessary; keep the download
icon fix and remove the DropdownMenu.Label hint (and its CSS). The
AdminAPIConfigurationScreen.keys.delete.tooltip string is unused again and is
removed with the other dead i18n keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop three portal translation keys no longer referenced after the migration:
- AdminAPIConfigurationScreen.keys.delete.tooltip
- EndpointDirectAccessScreen.section1.option.ShowLoginAndRedirectToSettings.label--disabled
- EndpointDirectAccessScreen.section1.option.ShowLoginAndRedirectToCustomURL.label--disabled

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Radix migration left the migrated screens failing `make -C portal lint`.
Make them lint-clean:

- Replace forbidden raw <h1> page titles with <Text as="p" size="5"
  weight="bold"> to match the other migrated screens (react/forbid-elements).
- Set explicit values on boolean JSX attributes: aria-hidden, suffixPlain,
  hideFooterComponent (react/jsx-boolean-value).
- Capture e.target.value before the nested setState updater in the Cookie
  Lifetime and Endpoint Direct Access change handlers
  (local/no-unsafe-react-event-usage).
- Remove two now-unnecessary react/no-unstable-nested-components disable
  directives in Endpoint Direct Access.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration had stored sendgrid_logo.svg as a base64 PNG wrapped in an SVG
<image> element (no vector benefit, still rasterized). Replace it with a true
vector SendGrid mark, consistent with the other provider logos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The provider selection cards passed a fixed numberOfColumns (4 for SMS, 3 for
SMTP), producing a repeat(N, minmax(160px, 1fr)) grid that cannot wrap: once the
content column is narrower than N*160px it overflows and crops the last card.

- Drop numberOfColumns at both call sites so the grid uses the responsive
  auto-fit default, which wraps to a new row instead of overflowing (and still
  shows all cards in one row when wide, capped by the item count).
- Vertically center the icon + label of title-only cards so a wrapped label
  stays aligned with the equal-height sibling cards; cards with a subtitle
  (Project Wizard) keep top alignment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ConfirmationDialog rendered the confirm (primary) button on the left and cancel
on the right, while SaveFunctionBar puts the primary action on the right.
Swap the dialog's order to cancel-left / confirm-right so all dialogs and the
save bar share one convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The save bar previously mounted and unmounted instantly. Keep it mounted across
the exit and play a slide + fade via CSS keyframes: saveFunctionBarIn on
appear, saveFunctionBarOut on disappear (then unmount). Respects
prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The save-bar screens rendered a zero-height contentWidthAnchor div as the first
grid child purely to measure the content-column width. In ScreenContent's grid
(row-gap: 20px) that empty leading row pushed the page title ~20px lower than on
Admin API, which has no save bar.

Attach the SaveFunctionBar measurement ref to the existing pageHeader (which
already spans the content column) and drop the dedicated anchor div and its CSS.
The title now sits flush at the top on every Advanced page, consistent with
Admin API, and the save bar still aligns to the content column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reens

The label-left / content-right settings card was duplicated inline across the
migrated Advanced screens, with an inconsistent label width (Admin API 140px vs
others 200px) and only Admin API stacking responsively on narrow viewports.

- Add components/v2/SettingsSectionCard: a bordered card with a 200px label
  column that stacks vertically on tablet, plus a Storybook story.
- Migrate Account Deletion, Account Anonymization, Cookie Lifetime, Endpoint
  Direct Access, SAML Certificate, SMTP and SMS onto it.
- Widen Admin API's section label to 200px to match, and drop SAML's now-dead
  sectionHeading CSS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the migrated Advanced screens pass `make -C portal` CI checks:

- eslint: set explicit values on bare boolean JSX attributes (aria-hidden,
  startClean) in SaveFunctionBar stories.
- stylelint: silence no-unsupported-browser-features for the scrollbar-hiding
  rules (Admin API + SAML key tables), and replace the deprecated
  word-break: break-word with overflow-wrap: break-word in v2 Callout.
- prettier: format the screens/components left unformatted by the migration
  (project pins prettier 2.x).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fungc-io

Copy link
Copy Markdown
Member Author

Preview of the PR:

SCR-20260611-psss SCR-20260611-psnh SCR-20260611-pslh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants