Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/localizations/src/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,23 @@ export const enUS: LocalizationResource = {
title: 'Choose an account',
titleWithoutPersonal: 'Choose an organization',
},
oauthConsent: {
action__allow: 'Allow',
action__deny: 'Deny',
offlineAccessNotice: " You'll stay signed in until you sign out or revoke access.",
redirectNotice: 'If you allow access, this app will redirect you to {{domainAction}}.',
redirectUriModal: {
subtitle: 'Make sure you trust {{applicationName}} and that this URL belongs to {{applicationName}}.',
title: 'Redirect URL',
},
scopeList: {
title: 'This will allow {{applicationName}} access to:',
},
subtitle: 'wants to access {{applicationName}} on behalf of {{identifier}}',
viewFullUrl: 'View full URL',
warning:
'Make sure that you trust {{applicationName}} ({{domainAction}}). You may be sharing sensitive data with this site or app.',
},
organizationProfile: {
apiKeysPage: {
title: 'API keys',
Expand Down
16 changes: 16 additions & 0 deletions packages/shared/src/types/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,22 @@ export type __internal_LocalizationResource = {
suggestionsAcceptedLabel: LocalizationValue;
action__createOrganization: LocalizationValue;
};
oauthConsent: {
subtitle: LocalizationValue<'applicationName' | 'identifier'>;
scopeList: {
title: LocalizationValue<'applicationName'>;
};
action__deny: LocalizationValue;
action__allow: LocalizationValue;
warning: LocalizationValue<'applicationName' | 'domainAction'>;
redirectNotice: LocalizationValue<'domainAction'>;
offlineAccessNotice: LocalizationValue;
viewFullUrl: LocalizationValue;
redirectUriModal: {
title: LocalizationValue;
subtitle: LocalizationValue<'applicationName'>;
};
};
unstable__errors: UnstableErrors;
dates: {
previous6Days: LocalizationValue<'date'>;
Expand Down
83 changes: 83 additions & 0 deletions packages/ui/src/components/OAuthConsent/InlineAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';

import { Text } from '@/ui/customizables';
import { Tooltip } from '@/ui/elements/Tooltip';

type InlineActionProps = {
text: string;
actionText: string;
onClick: () => void;
tooltipText: string;
};

export function InlineAction({ text, actionText, onClick, tooltipText }: InlineActionProps) {
const idx = text.indexOf(actionText);
if (idx === -1) {
return <>{text}</>;
}

let before = text.slice(0, idx);
let after = text.slice(idx + actionText.length);

// Pull adjacent parentheses into the action span so they don't wrap separately
let prefix = '';
let suffix = '';
if (before.endsWith('(')) {
before = before.slice(0, -1);
prefix = '(';
}
if (after.startsWith(')')) {
after = after.slice(1);
suffix = ')';
}

const actionContent = (
<Tooltip.Root>
<Tooltip.Trigger>
<Text
as='span'
role='button'
tabIndex={0}
aria-label={tooltipText}
variant='caption'
onClick={onClick}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
sx={{
textDecoration: 'underline',
textDecorationStyle: 'dotted',
cursor: 'pointer',
outline: 'none',
display: 'inline-block',
}}
>
{actionText}
</Text>
</Tooltip.Trigger>
<Tooltip.Content text={tooltipText} />
</Tooltip.Root>
);

return (
<>
{before}
{prefix || suffix ? (
<Text
as='span'
sx={{ whiteSpace: 'nowrap' }}
>
{prefix}
{actionContent}
{suffix}
</Text>
) : (
actionContent
)}
{after}
</>
);
}
101 changes: 42 additions & 59 deletions packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ import { useUser } from '@clerk/shared/react';
import { useState } from 'react';

import { useEnvironment, useOAuthConsentContext } from '@/ui/contexts';
import { Box, Button, Flow, Grid, Text } from '@/ui/customizables';
import { Box, Button, Flow, Grid, localizationKeys, Text, useLocalizations } from '@/ui/customizables';
import { ApplicationLogo } from '@/ui/elements/ApplicationLogo';
import { Card } from '@/ui/elements/Card';
import { withCardStateProvider } from '@/ui/elements/contexts';
import { Header } from '@/ui/elements/Header';
import { Modal } from '@/ui/elements/Modal';
import { Tooltip } from '@/ui/elements/Tooltip';
import { Alert, Textarea } from '@/ui/primitives';
import { common } from '@/ui/styledSystem';
import { colors } from '@/ui/utils/colors';
import { InlineAction } from './InlineAction';
import { LogoGroup, LogoGroupItem, LogoGroupIcon, LogoGroupSeparator } from './LogoGroup';
import { OrgSelect } from './OrgSelect';
import {
Expand Down Expand Up @@ -55,10 +53,14 @@ export function OAuthConsentInternal() {
const { hostname } = new URL(redirectUrl);
return hostname.split('.').slice(-2).join('.');
} catch {
return '';
return 'https://example.com';
}
}

const { t } = useLocalizations();
const domainAction = getRootDomain();
const viewFullUrlText = t(localizationKeys('oauthConsent.viewFullUrl'));

return (
<Flow.Root flow='oauthConsent'>
<Card.Root>
Expand Down Expand Up @@ -125,7 +127,12 @@ export function OAuthConsentInternal() {
</LogoGroup>
)}
<Header.Title localizationKey={oAuthApplicationName} />
<Header.Subtitle localizationKey={`wants to access ${applicationName} on behalf of ${primaryIdentifier}`} />
<Header.Subtitle
localizationKey={localizationKeys('oauthConsent.subtitle', {
applicationName,
identifier: primaryIdentifier || '',
})}
/>
</Header.Root>

{selectOptions.length > 0 && (
Expand All @@ -138,7 +145,11 @@ export function OAuthConsentInternal() {

<ListGroup>
<ListGroupHeader>
<ListGroupHeaderTitle localizationKey={`This will allow ${oAuthApplicationName} access to:`} />
<ListGroupHeaderTitle
localizationKey={localizationKeys('oauthConsent.scopeList.title', {
applicationName: oAuthApplicationName,
})}
/>
</ListGroupHeader>
<ListGroupContent>
{displayedScopes.map(item => (
Expand All @@ -154,30 +165,17 @@ export function OAuthConsentInternal() {
colorScheme='warning'
variant='caption'
>
Make sure that you trust {oAuthApplicationName} {''}
<Tooltip.Root>
<Tooltip.Trigger>
<Text
as='span'
role='button'
tabIndex={0}
aria-label='View full URL'
variant='caption'
sx={{
textDecoration: 'underline',
textDecorationStyle: 'dotted',
cursor: 'pointer',
outline: 'none',
display: 'inline-block',
}}
onClick={() => setIsUriModalOpen(true)}
>
({getRootDomain()})
</Text>
</Tooltip.Trigger>
<Tooltip.Content text={`View full URL`} />
</Tooltip.Root>
{''}. You may be sharing sensitive data with this site or app.
<InlineAction
text={t(
localizationKeys('oauthConsent.warning', {
applicationName: oAuthApplicationName || applicationName,
domainAction,
}),
)}
actionText={domainAction}
onClick={() => setIsUriModalOpen(true)}
tooltipText={viewFullUrlText}
/>
</Text>
</Alert>
<Grid
Expand All @@ -187,11 +185,11 @@ export function OAuthConsentInternal() {
<Button
colorScheme='secondary'
variant='outline'
localizationKey='Deny'
localizationKey={localizationKeys('oauthConsent.action__deny')}
onClick={onDeny}
/>
<Button
localizationKey='Allow'
localizationKey={localizationKeys('oauthConsent.action__allow')}
onClick={onAllow}
/>
<Text
Expand All @@ -201,30 +199,13 @@ export function OAuthConsentInternal() {
colorScheme='secondary'
variant='caption'
>
If you allow access, this app will redirect you to{' '}
<Tooltip.Root>
<Tooltip.Trigger>
<Text
as='span'
role='button'
tabIndex={0}
aria-label='View full URL'
variant='caption'
sx={{
textDecoration: 'underline',
textDecorationStyle: 'dotted',
cursor: 'pointer',
outline: 'none',
display: 'inline-block',
}}
onClick={() => setIsUriModalOpen(true)}
>
{getRootDomain()}
</Text>
</Tooltip.Trigger>
<Tooltip.Content text={`View full URL`} />
</Tooltip.Root>
.{hasOfflineAccess && " You'll stay signed in until you sign out or revoke access."}
<InlineAction
text={t(localizationKeys('oauthConsent.redirectNotice', { domainAction }))}
actionText={domainAction}
onClick={() => setIsUriModalOpen(true)}
tooltipText={viewFullUrlText}
/>
{hasOfflineAccess && t(localizationKeys('oauthConsent.offlineAccessNotice'))}
</Text>
</Grid>
</Card.Content>
Expand Down Expand Up @@ -262,9 +243,11 @@ function RedirectUriModal({ onOpen, onClose, isOpen, redirectUri, oAuthApplicati
<Card.Root>
<Card.Content>
<Header.Root>
<Header.Title localizationKey={`Redirect URL`} />
<Header.Title localizationKey={localizationKeys('oauthConsent.redirectUriModal.title')} />
<Header.Subtitle
localizationKey={`Make sure you trust ${oAuthApplicationName} and that this URL belongs to ${oAuthApplicationName}.`}
localizationKey={localizationKeys('oauthConsent.redirectUriModal.subtitle', {
applicationName: oAuthApplicationName,
})}
/>
</Header.Root>
<Textarea
Expand Down
Loading
Loading