diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 70ea6c728fa..3b5b3e1bc61 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -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',
diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts
index 5e65b15004e..094ac9cdb5b 100644
--- a/packages/shared/src/types/localization.ts
+++ b/packages/shared/src/types/localization.ts
@@ -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'>;
diff --git a/packages/ui/src/components/OAuthConsent/InlineAction.tsx b/packages/ui/src/components/OAuthConsent/InlineAction.tsx
new file mode 100644
index 00000000000..b8361d17cfc
--- /dev/null
+++ b/packages/ui/src/components/OAuthConsent/InlineAction.tsx
@@ -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 = (
+
+
+ {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onClick();
+ }
+ }}
+ sx={{
+ textDecoration: 'underline',
+ textDecorationStyle: 'dotted',
+ cursor: 'pointer',
+ outline: 'none',
+ display: 'inline-block',
+ }}
+ >
+ {actionText}
+
+
+
+
+ );
+
+ return (
+ <>
+ {before}
+ {prefix || suffix ? (
+
+ {prefix}
+ {actionContent}
+ {suffix}
+
+ ) : (
+ actionContent
+ )}
+ {after}
+ >
+ );
+}
diff --git a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
index bc610b673a5..8df909f4a78 100644
--- a/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
+++ b/packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
@@ -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 {
@@ -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 (
@@ -125,7 +127,12 @@ export function OAuthConsentInternal() {
)}
-
+
{selectOptions.length > 0 && (
@@ -138,7 +145,11 @@ export function OAuthConsentInternal() {
-
+
{displayedScopes.map(item => (
@@ -154,30 +165,17 @@ export function OAuthConsentInternal() {
colorScheme='warning'
variant='caption'
>
- Make sure that you trust {oAuthApplicationName} {''}
-
-
- setIsUriModalOpen(true)}
- >
- ({getRootDomain()})
-
-
-
-
- {''}. You may be sharing sensitive data with this site or app.
+ setIsUriModalOpen(true)}
+ tooltipText={viewFullUrlText}
+ />
- If you allow access, this app will redirect you to{' '}
-
-
- setIsUriModalOpen(true)}
- >
- {getRootDomain()}
-
-
-
-
- .{hasOfflineAccess && " You'll stay signed in until you sign out or revoke access."}
+ setIsUriModalOpen(true)}
+ tooltipText={viewFullUrlText}
+ />
+ {hasOfflineAccess && t(localizationKeys('oauthConsent.offlineAccessNotice'))}
@@ -262,9 +243,11 @@ function RedirectUriModal({ onOpen, onClose, isOpen, redirectUri, oAuthApplicati
-
+