Skip to content

refactor(clerk-js): Expand ApplicationLogo usage for oAuth logo #6518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 11, 2025
64 changes: 32 additions & 32 deletions packages/clerk-js/src/ui/components/OAuthConsent/OAuthConsent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useUser } from '@clerk/shared/react';
import type { ComponentProps } from 'react';
import { useState } from 'react';

import { useEnvironment, useOAuthConsentContext } from '@/ui/contexts';
import { Box, Button, Flex, Flow, Grid, Icon, Text } from '@/ui/customizables';
import { ApplicationLogo } from '@/ui/elements/ApplicationLogo';
import { Avatar } from '@/ui/elements/Avatar';
import { Card } from '@/ui/elements/Card';
import { withCardStateProvider } from '@/ui/elements/contexts';
import { Header } from '@/ui/elements/Header';
Expand Down Expand Up @@ -42,13 +42,13 @@ export function OAuthConsentInternal() {
{/* both have avatars */}
{oAuthApplicationLogoUrl && logoImageUrl && (
<ConnectionHeader>
<Avatar
imageUrl={oAuthApplicationLogoUrl}
size={t => t.space.$12}
rounded={false}
/>
<ConnectionItem justify='end'>
<ApplicationLogo src={oAuthApplicationLogoUrl} />
</ConnectionItem>
<ConnectionSeparator />
<ApplicationLogo />
<ConnectionItem justify='start'>
<ApplicationLogo />
</ConnectionItem>
</ConnectionHeader>
)}
{/* only OAuth app has an avatar */}
Expand All @@ -59,11 +59,7 @@ export function OAuthConsentInternal() {
position: 'relative',
}}
>
<Avatar
imageUrl={oAuthApplicationLogoUrl}
size={t => t.space.$12}
rounded={false}
/>
<ApplicationLogo src={oAuthApplicationLogoUrl} />
<ConnectionIcon
size='sm'
sx={t => ({
Expand All @@ -77,31 +73,21 @@ export function OAuthConsentInternal() {
)}
{/* only Clerk application has an avatar */}
{!oAuthApplicationLogoUrl && logoImageUrl && (
<Flex
justify='center'
align='center'
gap={4}
sx={t => ({
marginBlockEnd: t.space.$6,
})}
>
<ConnectionIcon />
<ConnectionHeader>
<ConnectionItem justify='end'>
<ConnectionIcon />
</ConnectionItem>
<ConnectionSeparator />
<ApplicationLogo />
</Flex>
<ConnectionItem justify='start'>
<ApplicationLogo />
</ConnectionItem>
</ConnectionHeader>
)}
{/* no avatars */}
{!oAuthApplicationLogoUrl && !logoImageUrl && (
<Flex
justify='center'
align='center'
gap={4}
sx={t => ({
marginBlockEnd: t.space.$6,
})}
>
<ConnectionHeader>
<ConnectionIcon />
</Flex>
</ConnectionHeader>
)}
<Header.Title localizationKey={oAuthApplicationName} />
<Header.Subtitle
Expand Down Expand Up @@ -320,6 +306,20 @@ function ConnectionHeader({ children }: { children: React.ReactNode }) {
);
}

function ConnectionItem({ children, ...props }: ComponentProps<typeof Flex>) {
return (
<Flex
{...props}
sx={{
flex: 1,
...(props.sx || {}),
}}
>
{children}
</Flex>
);
}

function ConnectionIcon({ size = 'md', sx }: { size?: 'sm' | 'md'; sx?: ThemableCssProp }) {
const scale: ThemableCssProp = t => {
const value = size === 'sm' ? t.space.$6 : t.space.$12;
Expand Down
55 changes: 43 additions & 12 deletions packages/clerk-js/src/ui/elements/ApplicationLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { isExternalUrl } from '@clerk/shared/url';
import React from 'react';

import { useEnvironment } from '../contexts';
import { descriptors, Flex, Image, useAppearance } from '../customizables';
import type { PropsOfComponent } from '../styledSystem';
import { Link } from '../primitives';
import type { InternalTheme, PropsOfComponent } from '../styledSystem';
import { common } from '../styledSystem';
import { RouterLink } from './RouterLink';

const getContainerHeightForImageRatio = (imageRef: React.RefObject<HTMLImageElement>, width: string) => {
Expand All @@ -21,15 +24,24 @@ const getContainerHeightForImageRatio = (imageRef: React.RefObject<HTMLImageElem
return width;
};

type ApplicationLogoProps = PropsOfComponent<typeof Flex>;
type ApplicationLogoProps = PropsOfComponent<typeof Flex> & {
/**
* The URL of the image to display.
*/
src?: string;
/**
* The URL to navigate to when the logo is clicked.
*/
href?: string;
};

export const ApplicationLogo = (props: ApplicationLogoProps) => {
const imageRef = React.useRef<HTMLImageElement>(null);
const [loaded, setLoaded] = React.useState(false);
const { logoImageUrl, applicationName, homeUrl } = useEnvironment().displayConfig;
const { parsedLayout } = useAppearance();
const imageSrc = parsedLayout.logoImageUrl || logoImageUrl;
const logoUrl = parsedLayout.logoLinkUrl || homeUrl;
const imageSrc = props.src || parsedLayout.logoImageUrl || logoImageUrl;
const logoUrl = props.href || parsedLayout.logoLinkUrl || homeUrl;

if (!imageSrc) {
return null;
Expand Down Expand Up @@ -65,14 +77,33 @@ export const ApplicationLogo = (props: ApplicationLogoProps) => {
]}
>
{logoUrl ? (
<RouterLink
sx={{
justifyContent: 'center',
}}
to={logoUrl}
>
{image}
</RouterLink>
isExternalUrl(logoUrl) ? (
<Link
href={logoUrl}
isExternal
css={(theme: InternalTheme) => ({
'&:focus': {
...common.focusRing(theme),
outline: 'none',
},
})}
>
{image}
</Link>
) : (
<RouterLink
sx={theme => ({
justifyContent: 'center',
'&:focus': {
...common.focusRing(theme),
outline: 'none',
},
})}
to={logoUrl}
>
{image}
</RouterLink>
)
) : (
image
)}
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ export function joinURL(base: string, ...input: string[]): string {
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
export const isAbsoluteUrl = (url: string) => ABSOLUTE_URL_REGEX.test(url);

export const isExternalUrl = (url: string) => {
return isAbsoluteUrl(url) && !url.startsWith(window.location.origin);
};
Loading