0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

fix(console): fix SSO connector logo fallback logic ()

This commit is contained in:
Darcy Ye 2023-11-29 11:07:20 +08:00 committed by GitHub
parent 8ebaac1e2c
commit 4be339d4bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 99 additions and 29 deletions
packages/console/src/pages
EnterpriseSso/SsoConnectorLogo
UserDetails/UserSettings/components
UserSocialIdentities
UserSsoIdentities

View file

@ -6,6 +6,7 @@ import ImageWithErrorFallback from '@/ds-components/ImageWithErrorFallback';
import useTheme from '@/hooks/use-theme';
import * as styles from './index.module.scss';
import { pickLogoForCurrentThemeHelper } from './utils';
type Props = {
className?: string;
@ -23,9 +24,13 @@ const pickLogoForCurrentTheme = (
branding: SsoConnectorWithProviderConfig['branding']
): string => {
// Need to use `||` here since `??` operator can not avoid empty strings.
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const configuredLogo = isDarkMode ? branding.darkLogo : branding.logo || branding.darkLogo;
const builtInLogo = isDarkMode ? logoDark : logo || logoDark;
// Since `logo` and `darkLogo` are both optional, when it is dark mode and `darkLogo` is not configured, should fallback to `logo`.
const configuredLogo = pickLogoForCurrentThemeHelper(
isDarkMode,
branding.logo,
branding.darkLogo
);
const builtInLogo = pickLogoForCurrentThemeHelper(isDarkMode, logo, logoDark);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return configuredLogo || builtInLogo;

View file

@ -0,0 +1,38 @@
import { pickLogoForCurrentThemeHelper } from './utils';
describe('pickLogoForCurrentThemeHelper', () => {
const logo = 'logo';
const logoDark = 'logoDark';
it('dark mode, logo non-empty, logoDark non-empty, should get logoDark', () => {
expect(pickLogoForCurrentThemeHelper(true, logo, logoDark)).toBe(logoDark);
});
it('light mode, logo non-empty, logoDark non-empty, should get logo', () => {
expect(pickLogoForCurrentThemeHelper(false, logo, logoDark)).toBe(logo);
});
it('dark mode, logo non-empty, logoDark empty, should get logo', () => {
expect(pickLogoForCurrentThemeHelper(true, logo, '')).toBe(logo);
});
it('light mode, logo non-empty, logoDark empty, should get logo', () => {
expect(pickLogoForCurrentThemeHelper(false, logo, '')).toBe(logo);
});
it('dark mode, logo empty, logoDark non-empty, should get logoDark', () => {
expect(pickLogoForCurrentThemeHelper(true, '', logoDark)).toBe(logoDark);
});
it('light mode, logo empty, logoDark non-empty, should get logoDark', () => {
expect(pickLogoForCurrentThemeHelper(false, '', logoDark)).toBe(logoDark);
});
it('dark mode, logo empty, logoDark empty, should get empty string', () => {
expect(pickLogoForCurrentThemeHelper(true, '', '')).toBe('');
});
it('light mode, logo empty, logoDark empty, should get empty string', () => {
expect(pickLogoForCurrentThemeHelper(false, '', '')).toBe('');
});
});

View file

@ -0,0 +1,9 @@
import { type Optional } from '@silverhand/essentials';
export const pickLogoForCurrentThemeHelper = <T extends string | Optional<string>>(
isDarkMode: boolean,
logo: T,
logoDark: T
): T => {
return (isDarkMode ? logoDark : logo) || logoDark || logo;
};

View file

@ -11,10 +11,14 @@
align-items: center;
.icon {
width: 32px;
height: 32px;
border-radius: _.unit(2);
flex-shrink: 0;
background: transparent;
width: 20px;
height: 20px;
> img {
width: 100%;
height: 100%;
}
}
.name {

View file

@ -1,12 +1,16 @@
import type { SsoConnectorWithProviderConfig, UserSsoIdentity } from '@logto/schemas';
import {
ssoBrandingGuard,
type SsoConnectorWithProviderConfig,
type UserSsoIdentity,
} from '@logto/schemas';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import { z } from 'zod';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import Table from '@/ds-components/Table';
import type { RequestError } from '@/hooks/use-api';
import useTheme from '@/hooks/use-theme';
import SsoConnectorLogo from '@/pages/EnterpriseSso/SsoConnectorLogo';
import * as styles from '../UserSocialIdentities/index.module.scss';
@ -24,8 +28,21 @@ type DisplayConnector = {
issuer: UserSsoIdentity['issuer'];
};
const isIdentityDisplayConnector = (
identity: Partial<DisplayConnector>
): identity is DisplayConnector => {
// `DisplayConnector` instance should have `providerLogo`, `providerLogoDark` and `name` to be non-empty string and `branding` to be an object.
const identityGuard = z.object({
providerLogo: z.string().min(1),
providerLogoDark: z.string().min(1),
branding: ssoBrandingGuard,
name: z.string().min(1),
});
const result = identityGuard.safeParse(identity);
return result.success;
};
function UserSsoIdentities({ ssoIdentities }: Props) {
const theme = useTheme();
const { t } = useTranslation(undefined, {
keyPrefix: 'admin_console',
});
@ -41,25 +58,19 @@ function UserSsoIdentities({ ssoIdentities }: Props) {
return;
}
return (
ssoIdentities
.map((identity) => {
const {
providerLogo,
providerLogoDark,
connectorName: name,
} = data.find((ssoConnector) => ssoConnector.id === identity.ssoConnectorId) ?? {};
const { identityId: userIdentity, issuer } = identity;
return ssoIdentities
.map((identity) => {
const {
providerLogo,
providerLogoDark,
branding,
connectorName: name,
} = data.find((ssoConnector) => ssoConnector.id === identity.ssoConnectorId) ?? {};
const { identityId: userIdentity, issuer } = identity;
if (!(providerLogo && providerLogoDark && name)) {
return;
}
return { providerLogo, providerLogoDark, name, userIdentity, issuer };
})
// eslint-disable-next-line unicorn/prefer-native-coercion-functions
.filter((identity): identity is DisplayConnector => Boolean(identity))
);
return { providerLogo, providerLogoDark, branding, name, userIdentity, issuer };
})
.filter((identity): identity is DisplayConnector => isIdentityDisplayConnector(identity));
}, [data, ssoIdentities]);
const hasLinkedSsoIdentities = Boolean(displaySsoConnectors && displaySsoConnectors.length > 0);
@ -89,7 +100,10 @@ function UserSsoIdentities({ ssoIdentities }: Props) {
colSpan: 5,
render: ({ providerLogo, providerLogoDark, branding, name }) => (
<div className={styles.connectorName}>
<SsoConnectorLogo data={{ providerLogo, providerLogoDark, branding }} />
<SsoConnectorLogo
containerClassName={styles.icon}
data={{ providerLogo, providerLogoDark, branding }}
/>
<div className={styles.name}>
<span>{name}</span>
</div>