Skip to content

⏳ fix: Restore Login Card Width and Show SSO Loading State - #123

Closed
dustinhealy wants to merge 4 commits into
mainfrom
fix/click-ui-login
Closed

dustinhealy wants to merge 4 commits into
mainfrom
fix/click-ui-login

Conversation

@dustinhealy

@dustinhealy dustinhealy commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two login-screen fixes in AuthCard.tsx:

  1. Login card width (click-ui 0.9.1 regression). click-ui 0.9.1 ships unlayered CSS while our Tailwind utilities live in @layer utilities, and unlayered declarations beat layered ones regardless of specificity. Panel now declares width: var(--panel-width, auto), which silently overrides our w-full, so the card collapsed to content width (~248px). Fix: use the component's fillWidth prop (inline --panel-width: 100% wins the cascade) on both Panels, drop the dead w-full, keep max-w-md, and add min-w-70 (280px) so the card can never collapse below a usable width again.
  2. SSO loading indicator (pre-existing). handleSsoLogin reset its loading flag in a finally that runs immediately after the window.location.href assignment (assignment does not block), so no loading state ever painted, and the button never used click-ui's loading prop. Fix: keep the loading state through navigation (reset only on error paths) and drive the button with loading={ssoLoading}, which provides the spinner, disabled, and aria-busy. The auto-redirect effect gets the same treatment, and a response without an authUrl now surfaces the login-failed message instead of silently resetting.

Heads-up for reviewers: the unlayered-vs-layered cascade means plain Tailwind geometry utilities (w-*, p-*) no longer take effect on click-ui components as of 0.9.1; geometry should go through component props or important utilities (existing precedent: !max-w-2xl in FormDialog).

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

New AuthCard.test.tsx (6 cases, real click-ui rendered): SSO button shows loading while pending, remains loading after resolving with an authUrl (the finally-bug regression test) with location.href set, returns to non-loading on error and on rejection, and both Panels render with fillWidth and the min width. 4 of 6 fail against the pre-fix code.

Before/after proof:

07_AI-1779_login-card-width 06_AI-1775_sso-loading

Test Configuration:

  • bunx tsc --noEmit, bunx eslint src/ --max-warnings 0, bunx vitest run (805/805), bun run build: all green
  • Local dev server, login page (no auth required)

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Inline @clickhouse/click-ui in vitest server deps and exclude .claude/** from test discovery. click-ui 0.9.1 ships ESM that imports .css files, which Node's loader cannot parse, so Vitest must process the package through Vite.
click-ui 0.9.1 ships unlayered CSS while Tailwind utilities live in @layer utilities, so Panel's new width: var(--panel-width, auto) rule beat our w-full class and the login card collapsed to content width. Both Panels now use the fillWidth prop, which sets an inline --panel-width: 100% that wins the cascade, and the dead w-full class is replaced with min-w-70 so the card can never collapse below 17.5rem again even if the component CSS changes.

The SSO button never showed a loading indicator because handleSsoLogin reset ssoLoading in a finally block that ran immediately after the window.location.href assignment, which does not block, so the state reverted before the redirect painted. The success path now returns with ssoLoading still true and only error paths reset it, and the button uses click-ui's loading prop for the spinner, disabled state, and aria-busy. The same finally reset is removed from the auto-redirect effect.

Adds AuthCard tests covering the pending, redirecting, and error states of the SSO button plus a regression guard asserting both Panels carry the fillWidth inline style and min width class.
@dustinhealy dustinhealy changed the title 🐛 fix: Restore Login Card Width and Show SSO Loading State ⏳ fix: Restore Login Card Width and Show SSO Loading State Aug 11, 2026
@dustinhealy
dustinhealy marked this pull request as ready for review August 11, 2026 23:03
Base automatically changed from chore/click-ui-0.9.1 to main August 14, 2026 14:54
@dustinhealy

Copy link
Copy Markdown
Contributor Author

@codex review

@dustinhealy
dustinhealy marked this pull request as draft August 14, 2026 15:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7d882db48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/AuthCard.tsx Resolved
The successful SSO path intentionally leaves ssoLoading true while the browser navigates to the identity provider, but browsers that restore the login page from the back-forward cache (for example after cancelling at the IdP via the Back button) also restore that React state, leaving the button stuck loading and disabled until a manual reload.

A pageshow listener now clears the loading flag when the event reports a persisted page, keeping the loading state intact during the outbound navigation while recovering it on bfcache restores.
Frontend component tests belong in an adjacent __tests__ directory per the repo convention in AGENTS.md, and this file was added directly under src/components in this branch.
@dustinhealy

Copy link
Copy Markdown
Contributor Author

@codex re-review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 567ddb0dd8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dustinhealy
dustinhealy marked this pull request as ready for review August 14, 2026 16:07
danny-avila added a commit that referenced this pull request Oct 28, 2026
… the Provider Loads (#196)

* 🔑 feat: Add Google and SAML Admin Sign-In Through a Provider Registry and Keep Deep Links Through SSO

The sign-in page offers one button per SSO provider LibreChat has for admins,
from a registry in src/constants (SSO_PROVIDERS: OpenID, Google, SAML).
ssoProvidersFn replaces checkOpenIdFn and returns { providers, ssoOnly }.
OpenID still needs its /api/admin/oauth/openid/check probe. Google and SAML
come from one GET /api/config sent with the request's tenant, and count only
with socialLoginEnabled, since LibreChat registers their admin strategies under
ALLOW_SOCIAL_LOGIN alone. A provider left out of socialLogins is not offered,
as on LibreChat's own sign-in page. ADMIN_SSO_ENABLED=false still offers none.
OpenID and SAML buttons take the deployer's label from /api/config.

One POST ssoStartFn ({ provider, redirectTo }) builds the PKCE challenge and
keeps the verifier and the sanitized in-app path (toSafeRedirect) in the admin
session, instead of the redirectTo query LibreChat strips. oauthExchangeFn takes
{ code, provider }, stores the provider as tokenProvider, and returns and then
forgets the path. One /auth/$provider/callback route replaces the OpenID-only
one: an unknown provider is not found, a failure shows SsoError, and a sign-in
returns to the page it started from. Both callback paths are known metrics
routes.

Google sessions refresh through POST /api/admin/oauth/refresh with
provider: 'google' and keep the rotated refresh token. SAML sessions, which
LibreChat cannot renew, are never sent to the cookie-based /api/auth/refresh.

ADMIN_SSO_ONLY auto-redirects only when exactly one provider is offered, hides
the password form while any is, and brings the form back when a provider cannot
start or none resolves.

Supersedes #36 by @dustinhealy, reusing its provider registry, its
/api/config-based availability with the social-login and socialLogins gates,
and its idea of carrying the deep link through the admin session.

Closes #30
Closes #65

* 🔗 fix: Bind the SSO Session to the Provider That Started It and Follow the socialLogins Order

* 🧪 test: Expect the SSO Buttons in LibreChat's socialLogins Order

* ⏳ fix: Restore the Sign-In Card Width and Keep SSO Buttons Busy Until the Provider Loads

click-ui 0.9.1's unlayered `.panel { width: var(--panel-width, auto) }`
outranks Tailwind's layered `w-full`, so the sign-in card and the SSO-only
redirect card took their content's width: 304px and 311px at 1440 instead of
max-w-md's 448px. Both Panels now take Panel's `fillWidth` (an inline
`--panel-width: 100%`) and keep max-w-md.

startSso reset its busy state in a `finally` that ran right after
`window.location.href = authUrl`, so the button went back to its idle label,
enabled, while the browser was still loading the provider, and could be
clicked again. Only a start that fails gives the button back now: an error
result, a request that throws, or a result with an empty URL (assigning it
would reload /login). The button takes click-ui's `loading` (shimmer,
disabled, aria-busy); the other providers stay disabled.

On an SSO-only page with one provider, that state starts as the provider, so
the redirect card shows from the first render and ends the same way. A
`pageshow` with `persisted` (the browser restoring the page from
its back-forward cache, Back from the provider) clears it: the button is idle
again, and an SSO-only page offers its provider's button instead of a spinner
that never ends.

Supersedes #123 by @dustinhealy, whose fillWidth, loading and pageshow
approach this follows. Depends on #188.
@danny-avila

Copy link
Copy Markdown
Contributor

Thanks @dustinhealy for this. #196 is now merged. It redoes this PR on top of #188's provider buttons and follows your approach throughout: Panel's fillWidth for the card width, click-ui's loading on the SSO button, a busy state that ends only when a start fails, and the pageshow reset for back-forward cache restores.

What changed: main already had the click-ui bump and Vitest config, and the new tests use the existing AuthCard.test.tsx setup rather than mocking useLocalize. min-w-70 was dropped: with fillWidth the card already spans the column, and a fixed 280px pushed the card and its gutters past a 280px-wide screen.

Closing this in favor of #196. If the card or the loading state misbehaves somewhere, please comment or open an issue.

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