Skip to content
Open
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
72 changes: 34 additions & 38 deletions packages/react/src/components/adapters/Consent.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

import {
type ConsentPurposeData,
FlowMetadataResponse,
PromptElement,
resolveFlowTemplateLiterals,
} from '@thunderid/browser';
import {type ConsentPurposeData, PromptElement} from '@thunderid/browser';
import {type ChangeEvent, FC, ReactNode} from 'react';
import ConsentCheckboxList, {getConsentOptionalKey} from './ConsentCheckboxList';
import Typography from '../primitives/Typography/Typography';
Expand Down Expand Up @@ -41,8 +36,10 @@ export interface ConsentRenderProps {
export interface ConsentConfig {
essential?: string;
optional?: string;
permission?: string;
essentialInfo?: string;
optionalInfo?: string;
permissionInfo?: string;
}

/**
Expand Down Expand Up @@ -78,41 +75,29 @@ export interface ConsentProps {
* Callback invoked when a user toggles an optional attribute.
*/
onInputChange: (name: string, value: string) => void;
/**
* Config to modified detail in consent page
*/
config?: Record<string, unknown>;

/**
* Config of meta response
*/
meta?: FlowMetadataResponse | null;

/**
* translation data
*/
t?: UseTranslation['t'];
}

const defaultConfig: Required<Pick<ConsentConfig, 'essential' | 'optional'>> = {
essential: 'Essential Attributtes',
optional: 'Optional Attributes',
// default config for consent related translation keys
const defaultConfig: ConsentConfig = {
Comment thread
zesu22 marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to revisit this again. But okay to proceed with this for now

essential: 'essential_claims',
optional: 'optional_claims',
permission: 'authorize_scope',
essentialInfo: 'essential_claims_info',
optionalInfo: 'optional_claims_info',
permissionInfo: 'authorize_scope_info',
Comment on lines +86 to +92

@ThaminduDilshan ThaminduDilshan Aug 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These consent.* keys are not added to the locale bundles in packages/javascript/src/i18n/translations, so they all miss at runtime and rely on the inline fallbacks.

Also, the _info suffix diverges from the SDK convention of nesting variants (e.g. copyable_text.copy/.copied) — prefer essential_claims.info.

};

/**
* Consent component renders the list of purposes and their associated attributes (essential and optional)
* based on the data provided by the backend. It allows users to toggle optional attributes while essential
* attributes are displayed as read-only.
*/
const Consent: FC<ConsentProps> = ({
consentData,
formValues,
config: suppliedConfig = {},
onInputChange,
children,
meta,
t,
}: ConsentProps) => {
const Consent: FC<ConsentProps> = ({consentData, formValues, onInputChange, children, t}: ConsentProps) => {
// Computed per render (not at module scope): a CSP nonce configured on <ThunderIDProvider>
// isn't known until the provider renders, and Emotion needs it applied before any style
// insertion happens. These are static, so Emotion's own cache dedupes the repeat calls to a
Expand All @@ -135,19 +120,27 @@ const Consent: FC<ConsentProps> = ({
});
const optionalSectionLabelClass: string = css({alignItems: 'center', display: 'flex', gap: '4px'});

/** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */
/** Resolve i18n keys */
const resolve = (text: string | undefined): string => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a diff in the two resolve functions. Here we're returning "" (empty) if a key is not found. However in the resolve() function in packages/react/src/components/adapters/ConsentCheckboxList.tsx, we're returning the key as it is.

Shall we modify the resolve() function here also to display the key if the translation is not found?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also prefer to get rid of the duplicated resolve() functions

if (!text || (!t && !meta)) {
if (!text || !t) {
return text || '';
}
return resolveFlowTemplateLiterals(text, {meta, t: t || ((k: string): string => k)});

const key: string = `consent.${text}`;
const translated: string = t(key);
return translated === key ? '' : translated;
};

const config: ConsentConfig = {...defaultConfig, ...suppliedConfig};
const essentialInfo = typeof config.essentialInfo === 'string' ? resolve(config.essentialInfo.trim()) : '';
const optionalInfo = typeof config.optionalInfo === 'string' ? resolve(config.optionalInfo.trim()) : '';
const essentialLabel = resolve(config['essential']);
const optionalLabel = resolve(config['optional']);
const essentialInfo = resolve(defaultConfig['essentialInfo']);
const optionalInfo = resolve(defaultConfig['optionalInfo']);
const permissionInfo = resolve(defaultConfig['permissionInfo']);
/**
* Falls back to default config values if essential/optional
* keys cannot be resolved via translation files .
*/
const essentialLabel = resolve(defaultConfig['essential']) || 'Essential Attributes';
const optionalLabel = resolve(defaultConfig['optional']) || 'Optional Attributes';
const permissionLabel = resolve(defaultConfig['permission']) || 'Permissions';

/**
* Method to check whether master toggle button is checked or not
Expand Down Expand Up @@ -223,6 +216,7 @@ const Consent: FC<ConsentProps> = ({
purpose={purpose}
formValues={formValues}
onInputChange={onInputChange}
t={t}
/>
</div>
)}
Expand All @@ -232,10 +226,11 @@ const Consent: FC<ConsentProps> = ({
<div className={optionalSectionHeaderClass}>
<div className={optionalSectionLabelClass}>
<Typography variant="subtitle2" fontWeight="bold">
{purpose.type === 'permissions' ? 'Permissions' : optionalLabel}
{purpose.type === 'permissions' ? permissionLabel : optionalLabel}
</Typography>
{optionalInfo !== '' && (
<Tooltip helperText={optionalInfo}>
{/* Show tooltip for optional claims/permissions according to their type */}
{Boolean(purpose.type === 'permissions' ? permissionInfo : optionalInfo) && (
<Tooltip helperText={purpose.type === 'permissions' ? permissionInfo : optionalInfo}>
<Info width="1rem" height="1rem" />
</Tooltip>
)}
Expand All @@ -252,6 +247,7 @@ const Consent: FC<ConsentProps> = ({
purpose={purpose}
formValues={formValues}
onInputChange={onInputChange}
t={t}
/>
</div>
)}
Expand Down
20 changes: 18 additions & 2 deletions packages/react/src/components/adapters/ConsentCheckboxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useTheme from '../../contexts/Theme/useTheme';
import {cx} from '../../styles/emotion';
import Toggle from '../primitives/Toggle/Toggle';
import Typography from '../primitives/Typography/Typography';
import {UseTranslation} from '../../hooks/useTranslation';

/**
* Computes the form value key for tracking an optional attribute's consent state.
Expand Down Expand Up @@ -78,6 +79,10 @@ export interface ConsentCheckboxListProps {
purpose: ConsentPurposeData;
/** Whether to render essential (disabled) or optional (toggleable) attributes */
variant: ConsentInputVariant;
/**
* translation data
*/
t?: UseTranslation['t'];
}

/**
Expand All @@ -93,10 +98,21 @@ const ConsentCheckboxList: FC<ConsentCheckboxListProps> = ({
formValues,
onInputChange,
children,
t,
}: ConsentCheckboxListProps) => {
const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();
const styles: Record<string, string> = useStyles(theme, colorScheme);

const resolve = (text: string | undefined): string => {
if (!text || !t) {
return text || '';
}

const key: string = `consent.${text}`;
const translated: string = t(key);
return translated === key ? text : translated;
};

const attributes: string[] = (variant === 'ESSENTIAL' ? purpose.essential : purpose.optional).map(
(e): string => e.name,
);
Expand Down Expand Up @@ -153,11 +169,11 @@ const ConsentCheckboxList: FC<ConsentCheckboxListProps> = ({
styles['typography'],
)}
>
{attr}
{resolve(attr)}
</Typography>
</div>
{isEssential ? (
<Typography variant="body2">Required</Typography>
<Typography variant="body2">{resolve('required')}</Typography>

@ThaminduDilshan ThaminduDilshan Aug 18, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a default translation for this? It seems fallback translation for required key is not present. Shall we add it?

) : (
<Toggle
id={inputId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,8 +813,6 @@ const createAuthComponentFromFlow = (
consentData={consentPromptRawData as any}
formValues={formValues}
onInputChange={onInputChange}
config={component.config}
meta={options.meta}
t={options.t}
/>
);
Expand Down
Loading