mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
SSO styling changes (#5671)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## 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. --------- Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
This commit is contained in:
parent
d135e25d02
commit
00a9174939
@ -4,7 +4,7 @@ import { authService, UserInfo } from '@app/services/authService';
|
||||
import { buildOAuthCallbackHtml } from '@app/utils/oauthCallbackHtml';
|
||||
import { BASE_PATH } from '@app/constants/app';
|
||||
import { STIRLING_SAAS_URL } from '@app/constants/connection';
|
||||
import '@app/routes/authShared/auth.css';
|
||||
import '@app/components/SetupWizard/desktopOAuth.css';
|
||||
|
||||
type KnownProviderId = 'google' | 'github' | 'keycloak' | 'azure' | 'apple' | 'oidc';
|
||||
export type OAuthProviderId = KnownProviderId | string;
|
||||
@ -95,8 +95,9 @@ export const DesktopOAuthButtons: React.FC<DesktopOAuthButtonsProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
// Desktop always uses its own styling classes (independent of web)
|
||||
return (
|
||||
<div className="oauth-container-vertical">
|
||||
<div className="oauth-container-vertical-desktop">
|
||||
{providers
|
||||
.filter((providerConfigEntry) => providerConfigEntry && providerConfigEntry.id)
|
||||
.map((providerEntry) => {
|
||||
@ -114,15 +115,19 @@ export const DesktopOAuthButtons: React.FC<DesktopOAuthButtonsProps> = ({
|
||||
key={providerEntry.id}
|
||||
onClick={() => handleOAuthLogin(providerEntry)}
|
||||
disabled={isDisabled || oauthLoading}
|
||||
className="oauth-button-vertical"
|
||||
className="oauth-button-vertical-desktop"
|
||||
title={label}
|
||||
>
|
||||
<img
|
||||
src={`${BASE_PATH}/Login/${iconConfig?.file || GENERIC_PROVIDER_ICON}`}
|
||||
alt={label}
|
||||
className="oauth-icon-tiny"
|
||||
/>
|
||||
{label}
|
||||
<span className="oauth-button-left-desktop">
|
||||
<span className="oauth-icon-wrapper-desktop">
|
||||
<img
|
||||
src={`${BASE_PATH}/Login/${iconConfig?.file || GENERIC_PROVIDER_ICON}`}
|
||||
alt={label}
|
||||
className="oauth-icon-tiny-desktop"
|
||||
/>
|
||||
</span>
|
||||
<span className="oauth-button-text-desktop">{label}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -13,6 +13,7 @@ import '@app/routes/authShared/auth.css';
|
||||
interface SelfHostedLoginScreenProps {
|
||||
serverUrl: string;
|
||||
enabledOAuthProviders?: SSOProviderConfig[];
|
||||
loginMethod?: string;
|
||||
onLogin: (username: string, password: string) => Promise<void>;
|
||||
onOAuthSuccess: (userInfo: UserInfo) => Promise<void>;
|
||||
mfaCode: string;
|
||||
@ -25,6 +26,7 @@ interface SelfHostedLoginScreenProps {
|
||||
export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
|
||||
serverUrl,
|
||||
enabledOAuthProviders,
|
||||
loginMethod = 'all',
|
||||
onLogin,
|
||||
onOAuthSuccess,
|
||||
mfaCode,
|
||||
@ -38,6 +40,9 @@ export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
|
||||
const [password, setPassword] = useState('');
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
|
||||
// Check if username/password authentication is allowed
|
||||
const isUserPassAllowed = loginMethod === 'all' || loginMethod === 'normal';
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Validation
|
||||
if (!username.trim()) {
|
||||
@ -69,7 +74,7 @@ export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
|
||||
<>
|
||||
<LoginHeader
|
||||
title={t('setup.selfhosted.title', 'Sign in to Server')}
|
||||
subtitle={t('setup.selfhosted.subtitle', 'Enter your server credentials')}
|
||||
subtitle={isUserPassAllowed ? t('setup.selfhosted.subtitle', 'Enter your server credentials') : undefined}
|
||||
/>
|
||||
|
||||
<ErrorMessage error={displayError} />
|
||||
@ -90,36 +95,42 @@ export const SelfHostedLoginScreen: React.FC<SelfHostedLoginScreenProps> = ({
|
||||
providers={enabledOAuthProviders}
|
||||
/>
|
||||
|
||||
<DividerWithText
|
||||
text={t('setup.login.orContinueWith', 'Or continue with email')}
|
||||
respondsToDarkMode={false}
|
||||
opacity={0.4}
|
||||
/>
|
||||
{/* Only show divider if username/password auth is also allowed */}
|
||||
{isUserPassAllowed && (
|
||||
<DividerWithText
|
||||
text={t('setup.login.orContinueWith', 'Or continue with email')}
|
||||
respondsToDarkMode={false}
|
||||
opacity={0.4}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<EmailPasswordForm
|
||||
email={username}
|
||||
password={password}
|
||||
setEmail={(value) => {
|
||||
setUsername(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
setPassword={(value) => {
|
||||
setPassword(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
mfaCode={mfaCode}
|
||||
setMfaCode={(value) => {
|
||||
setMfaCode(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
showMfaField={requiresMfa || Boolean(mfaCode)}
|
||||
requiresMfa={requiresMfa}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={loading}
|
||||
submitButtonText={t('setup.login.submit', 'Login')}
|
||||
/>
|
||||
{/* Only show email/password form if username/password auth is allowed */}
|
||||
{isUserPassAllowed && (
|
||||
<EmailPasswordForm
|
||||
email={username}
|
||||
password={password}
|
||||
setEmail={(value) => {
|
||||
setUsername(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
setPassword={(value) => {
|
||||
setPassword(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
mfaCode={mfaCode}
|
||||
setMfaCode={(value) => {
|
||||
setMfaCode(value);
|
||||
setValidationError(null);
|
||||
}}
|
||||
showMfaField={requiresMfa || Boolean(mfaCode)}
|
||||
requiresMfa={requiresMfa}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={loading}
|
||||
submitButtonText={t('setup.login.submit', 'Login')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -73,6 +73,7 @@ export const ServerSelection: React.FC<ServerSelectionProps> = ({ onSelect, load
|
||||
|
||||
// Fetch OAuth providers and check if login is enabled
|
||||
const enabledProviders: SSOProviderConfig[] = [];
|
||||
let loginMethod = 'all'; // Default to 'all' (allows both SSO and username/password)
|
||||
try {
|
||||
console.log('[ServerSelection] Fetching login configuration...');
|
||||
const response = await fetch(`${url}/api/v1/proprietary/ui-data/login`);
|
||||
@ -108,6 +109,10 @@ export const ServerSelection: React.FC<ServerSelectionProps> = ({ onSelect, load
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract loginMethod from response
|
||||
loginMethod = data.loginMethod || 'all';
|
||||
console.log('[ServerSelection] Login method:', loginMethod);
|
||||
|
||||
// Extract provider IDs from authorization URLs
|
||||
// Example: "/oauth2/authorization/google" → "google"
|
||||
const providerEntries = Object.entries(data.providerList || {});
|
||||
@ -149,11 +154,12 @@ export const ServerSelection: React.FC<ServerSelectionProps> = ({ onSelect, load
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection successful - pass URL and OAuth providers
|
||||
// Connection successful - pass URL, OAuth providers, and login method
|
||||
console.log('[ServerSelection] ✅ Server selection complete, proceeding to login');
|
||||
onSelect({
|
||||
url,
|
||||
enabledOAuthProviders: enabledProviders.length > 0 ? enabledProviders : undefined,
|
||||
loginMethod,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[ServerSelection] ❌ Unexpected error during connection test:', error);
|
||||
|
||||
96
frontend/src/desktop/components/SetupWizard/desktopOAuth.css
Normal file
96
frontend/src/desktop/components/SetupWizard/desktopOAuth.css
Normal file
@ -0,0 +1,96 @@
|
||||
/* Desktop-specific OAuth button styles for self-hosted server connections */
|
||||
/* These styles are isolated from the web SSO buttons to prevent conflicts */
|
||||
|
||||
.oauth-container-vertical-desktop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.875rem; /* 14px */
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-desktop {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0.75rem 1rem; /* 12px 16px */
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
background-color: var(--auth-card-bg-light-only);
|
||||
font-size: 1rem; /* 16px */
|
||||
font-weight: 500;
|
||||
color: var(--auth-text-primary-light-only);
|
||||
cursor: pointer;
|
||||
gap: 0.75rem; /* 12px */
|
||||
font-family: inherit;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-desktop:hover:not(:disabled) {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-desktop:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-desktop:focus-visible {
|
||||
outline: 2px solid var(--auth-border-focus-light-only);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Fix Mantine Button internal spans */
|
||||
.oauth-button-vertical-desktop .mantine-Button-inner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-desktop .mantine-Button-label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.75rem;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.oauth-button-left-desktop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.oauth-button-text-desktop {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: inherit;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.oauth-icon-wrapper-desktop {
|
||||
width: 1.25rem; /* 20px */
|
||||
height: 1.25rem; /* 20px */
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.oauth-icon-tiny-desktop {
|
||||
width: 1.25rem; /* 20px */
|
||||
height: 1.25rem; /* 20px */
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -327,6 +327,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
<SelfHostedLoginScreen
|
||||
serverUrl={serverConfig?.url || ''}
|
||||
enabledOAuthProviders={serverConfig?.enabledOAuthProviders}
|
||||
loginMethod={serverConfig?.loginMethod}
|
||||
onLogin={handleSelfHostedLogin}
|
||||
onOAuthSuccess={handleSelfHostedOAuthSuccess}
|
||||
mfaCode={selfHostedMfaCode}
|
||||
|
||||
@ -12,6 +12,7 @@ export interface SSOProviderConfig {
|
||||
export interface ServerConfig {
|
||||
url: string;
|
||||
enabledOAuthProviders?: SSOProviderConfig[];
|
||||
loginMethod?: string;
|
||||
}
|
||||
|
||||
export interface ConnectionConfig {
|
||||
|
||||
@ -193,24 +193,24 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.875rem 1.5rem; /* 14px 24px */
|
||||
border: none;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 999px;
|
||||
background: #0f172a;
|
||||
background-color: #ffffff;
|
||||
font-size: 1rem; /* 16px */
|
||||
font-weight: 600;
|
||||
color: #f8fafc;
|
||||
color: #1f2937;
|
||||
cursor: pointer;
|
||||
gap: 1rem; /* 16px */
|
||||
font-family: inherit;
|
||||
box-shadow: 0 0.4rem 1rem rgba(15, 23, 42, 0.25);
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.08);
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.oauth-button-vertical:hover:not(:disabled) {
|
||||
background: #111827;
|
||||
box-shadow: 0 0.55rem 1.25rem rgba(15, 23, 42, 0.3);
|
||||
background-color: #f9fafb;
|
||||
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-1px);
|
||||
color: #f8fafc;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-tinted {
|
||||
@ -295,7 +295,7 @@
|
||||
}
|
||||
|
||||
.oauth-button-vertical:focus-visible {
|
||||
outline: 3px solid rgba(59, 130, 246, 0.6);
|
||||
outline: 3px solid rgba(59, 130, 246, 0.5);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@ -367,8 +367,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.oauth-button-vertical-tinted .oauth-icon-wrapper {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user