diff --git a/package.json b/package.json index efd4c0d..f3d33f5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://github.com/transcend-io/consent-manager-ui.git" }, "homepage": "https://github.com/transcend-io/consent-manager-ui", - "version": "4.21.2", + "version": "4.22.0", "license": "MIT", "main": "build/ui", "files": [ diff --git a/src/components/CompleteOptionsInverted.tsx b/src/components/CompleteOptionsInverted.tsx index 6610c43..15a7bd9 100644 --- a/src/components/CompleteOptionsInverted.tsx +++ b/src/components/CompleteOptionsInverted.tsx @@ -1,7 +1,7 @@ import { h, JSX } from 'preact'; import { useState } from 'preact/hooks'; import { useIntl } from 'react-intl'; -import { useAirgap, useGetPurposeMessageKeys } from '../hooks'; +import { useAirgap, useGetInvertedPurposeMessageKeys } from '../hooks'; import { messages, completeOptionsMessages, @@ -36,7 +36,7 @@ export function CompleteOptionsInverted({ // Get the tracking purposes from Airgap for display const initialConsentSelections = getConsentSelections(airgap); - const purposeToMessageKey = useGetPurposeMessageKeys({ + const purposeToMessageKey = useGetInvertedPurposeMessageKeys({ consentSelection: initialConsentSelections, defaultPurposeToMessageKey: DEFAULT_PURPOSE_TO_INVERTED_MESSAGE_KEY, }); diff --git a/src/components/CompleteOptionsToggles.tsx b/src/components/CompleteOptionsToggles.tsx index 531056a..a939bdf 100644 --- a/src/components/CompleteOptionsToggles.tsx +++ b/src/components/CompleteOptionsToggles.tsx @@ -4,11 +4,19 @@ import { useMemo, useState } from 'preact/hooks'; import { useIntl } from 'react-intl'; import { getConsentSelections } from '../consent-selections'; import { CONSENT_OPTIONS } from '../constants'; -import { useAirgap, useGetPurposeMessageKeys } from '../hooks'; +import { + useAirgap, + useGetPurposeDescriptionKeys, + useGetPurposeMessageKeys, +} from '../hooks'; import { messages } from '../messages'; import type { HandleSetViewState } from '../types'; import { CloseButton } from './CloseButton'; -import { DEFAULT_PURPOSE_TO_MESSAGE_KEY, ORDER_OF_PURPOSES } from './constants'; +import { + DEFAULT_PURPOSE_TO_DESCRIPTION_KEY, + DEFAULT_PURPOSE_TO_MESSAGE_KEY, + ORDER_OF_PURPOSES, +} from './constants'; import { Switch } from './Switch'; /** @@ -37,7 +45,11 @@ export function CompleteOptionsToggles({ defaultPurposeToMessageKey: DEFAULT_PURPOSE_TO_MESSAGE_KEY, }); const purposeToDescription = useMemo(() => airgap.getPurposeTypes(), []); - + const purposeToDescriptionKey = useGetPurposeDescriptionKeys({ + consentSelection: initialConsentSelections, + defaultPurposeToDescriptionKey: DEFAULT_PURPOSE_TO_DESCRIPTION_KEY, + airgapPurposes: purposeToDescription, + }); // Set state on the currently selected toggles const [consentSelections, setConsentSelections] = useState( initialConsentSelections, @@ -140,7 +152,10 @@ export function CompleteOptionsToggles({ } />

- {purposeToDescription.Essential?.description} + {formatMessage( + purposeToDescriptionKey.Essential, + globalUiVariables, + )}

{orderedSelections.map(([purpose, isChecked], idx) => ( @@ -166,7 +181,10 @@ export function CompleteOptionsToggles({ {...(idx === 0 ? { initialFocus: true } : {})} />

- {purposeToDescription[purpose]?.description} + {formatMessage( + purposeToDescriptionKey[purpose], + globalUiVariables, + )}

))} diff --git a/src/components/constants.ts b/src/components/constants.ts index 83dab18..dee21c2 100644 --- a/src/components/constants.ts +++ b/src/components/constants.ts @@ -1,23 +1,31 @@ import { DefinedMessage } from '@transcend-io/internationalization'; -import { - completeOptionsMessages, - completeOptionsInvertedMessages, -} from '../messages'; +import { completeOptionsInvertedMessages, purposeMessages } from '../messages'; // Mapping of purposes to the message translation key export const DEFAULT_PURPOSE_TO_MESSAGE_KEY: Record = { - Essential: completeOptionsMessages.essentialLabel, - Functional: completeOptionsMessages.functionalLabel, - Analytics: completeOptionsMessages.analyticsLabel, - Advertising: completeOptionsMessages.advertisingLabel, - SaleOfInfo: completeOptionsMessages.saleOfInfoLabel, + Essential: purposeMessages['Essential.title'], + Functional: purposeMessages['Functional.title'], + Analytics: purposeMessages['Analytics.title'], + Advertising: purposeMessages['Advertising.title'], + SaleOfInfo: purposeMessages['SaleOfInfo.title'], +}; + +export const DEFAULT_PURPOSE_TO_DESCRIPTION_KEY: Record< + string, + DefinedMessage +> = { + Essential: purposeMessages['Essential.description'], + Functional: purposeMessages['Functional.description'], + Analytics: purposeMessages['Analytics.description'], + Advertising: purposeMessages['Advertising.description'], + SaleOfInfo: purposeMessages['SaleOfInfo.description'], }; export const DEFAULT_PURPOSE_TO_INVERTED_MESSAGE_KEY: Record< string, DefinedMessage > = { - Essential: completeOptionsMessages.essentialLabel, + Essential: purposeMessages['Essential.title'], Functional: completeOptionsInvertedMessages.functionalLabel, Analytics: completeOptionsInvertedMessages.analyticsLabel, Advertising: completeOptionsInvertedMessages.advertisingLabel, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index fcf6bf7..249de7a 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,4 +2,6 @@ export * from './useLanguage'; export * from './useStickyState'; export * from './useViewState'; export * from './useAirgap'; +export * from './useGetInvertedPurposeMessageKeys'; +export * from './useGetPurposeDescriptionKeys'; export * from './useGetPurposeMessageKeys'; diff --git a/src/hooks/useGetInvertedPurposeMessageKeys.ts b/src/hooks/useGetInvertedPurposeMessageKeys.ts new file mode 100644 index 0000000..3d566ea --- /dev/null +++ b/src/hooks/useGetInvertedPurposeMessageKeys.ts @@ -0,0 +1,43 @@ +import { ConsentSelection } from '../types'; + +import { useMemo } from 'preact/hooks'; + +import { DefinedMessage } from '@transcend-io/internationalization'; + +const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType'; + +export const useGetInvertedPurposeMessageKeys = ({ + consentSelection, + defaultPurposeToMessageKey, +}: { + /** The configured airgap purpose types */ + consentSelection: ConsentSelection; + /** The lookup of messages for default purpose types */ + defaultPurposeToMessageKey: Record; +}): Record => { + const purposeToMessageKey: Record = useMemo( + () => + // the purpose type is unique for the bundle + [...Object.keys(consentSelection ?? {}), 'Essential'].reduce( + (allMessages, purposeType) => { + if (allMessages[purposeType]) { + return allMessages; + } + const purposeMessageLabel = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.title`; + return { + ...allMessages, + [purposeType]: { + id: purposeMessageLabel, + defaultMessage: defaultPurposeToMessageKey[purposeType]?.defaultMessage + || purposeType, + description: `Translatable name for purpose '${purposeType}'`, + } as DefinedMessage, + }; + }, + defaultPurposeToMessageKey as Record, + ), + [consentSelection, defaultPurposeToMessageKey], + ); + + return purposeToMessageKey; +}; diff --git a/src/hooks/useGetPurposeDescriptionKeys.ts b/src/hooks/useGetPurposeDescriptionKeys.ts new file mode 100644 index 0000000..1eb521d --- /dev/null +++ b/src/hooks/useGetPurposeDescriptionKeys.ts @@ -0,0 +1,48 @@ +import type { + TrackingPurposesTypes, +} from '@transcend-io/airgap.js-types'; + +import { ConsentSelection } from '../types'; + +import { useMemo } from 'preact/hooks'; + +import { DefinedMessage } from '@transcend-io/internationalization'; + +const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType'; + +export const useGetPurposeDescriptionKeys = ({ + consentSelection, + defaultPurposeToDescriptionKey, + airgapPurposes, +}: { + /** The configured airgap purpose types */ + consentSelection: ConsentSelection; + /** The lookup of messages for default purpose types */ + defaultPurposeToDescriptionKey: Record; + /** Airgap purposes data */ + airgapPurposes: TrackingPurposesTypes; +}): Record => { + const purposeToDescriptionKey: Record = useMemo( + () => + // hard-coding Essential since it's not provided by consentSelection + [...Object.keys(consentSelection ?? {}), 'Essential'].reduce((allMessages, purposeType) => { + // making sure default message for Essential is not overwritten + // by missing Essential message from airgap + if (airgapPurposes[purposeType]?.description) { + const purposeMessageDescriptionId = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.description`; + return { + ...allMessages, + [purposeType]: { + id: purposeMessageDescriptionId, + defaultMessage: airgapPurposes[purposeType]?.description, + description: `Translatable description for purpose '${purposeType}'`, + } as DefinedMessage, + }; + } + return {...allMessages}; + }, defaultPurposeToDescriptionKey as Record), + [consentSelection, defaultPurposeToDescriptionKey], + ); + + return purposeToDescriptionKey; +}; diff --git a/src/hooks/useGetPurposeMessageKeys.ts b/src/hooks/useGetPurposeMessageKeys.ts index 348df3b..892b33b 100644 --- a/src/hooks/useGetPurposeMessageKeys.ts +++ b/src/hooks/useGetPurposeMessageKeys.ts @@ -4,7 +4,7 @@ import { useMemo } from 'preact/hooks'; import { DefinedMessage } from '@transcend-io/internationalization'; -const CUSTOM_PURPOSE_MESSAGE_PREFIX = 'cm-ui.purpose'; +const PURPOSE_MESSAGE_PREFIX = 'purpose.trackingType'; export const useGetPurposeMessageKeys = ({ consentSelection, @@ -18,20 +18,20 @@ export const useGetPurposeMessageKeys = ({ const purposeToMessageKey: Record = useMemo( () => // the purpose type is unique for the bundle - Object.keys(consentSelection ?? {}).reduce((allMessages, purposeType) => { - if (allMessages[purposeType]) { - return allMessages; - } - const customPurposeMessageLabel = `${CUSTOM_PURPOSE_MESSAGE_PREFIX}.${purposeType}`; - return { - ...allMessages, - [purposeType]: { - id: customPurposeMessageLabel, - defaultMessage: purposeType, - description: `Translatable name for custom purpose '${purposeType}'`, - } as DefinedMessage, - }; - }, defaultPurposeToMessageKey as Record), + [...Object.keys(consentSelection ?? {}), 'Essential'].reduce( + (allMessages, purposeType) => { + const purposeMessageLabel = `${PURPOSE_MESSAGE_PREFIX}.${purposeType}.title`; + return { + ...allMessages, + [purposeType]: { + id: purposeMessageLabel, + defaultMessage: purposeType, + description: `Translatable name for purpose '${purposeType}'`, + } as DefinedMessage, + }; + }, + defaultPurposeToMessageKey as Record, + ), [consentSelection, defaultPurposeToMessageKey], ); diff --git a/src/messages.ts b/src/messages.ts index 388709d..a6af0fc 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -456,32 +456,60 @@ export const bottomMenuMessages = defineMessages('ui.src.bottomMenu', { }, }); +export const purposeMessages = defineMessages('purpose.trackingType', { + 'Essential.title': { + defaultMessage: 'Essential', + description: 'Text for essential purposes in CompleteOptions view state.', + }, + 'Essential.description': { + defaultMessage: 'No consent needed.', + description: + 'Text for essential purposes description in CompleteOptions view state.', + }, + 'Functional.title': { + defaultMessage: 'Functional', + description: + 'Text for functional purposes in CompleteOptions view state.', + }, + 'Functional.description': { + defaultMessage: 'Personalization, autofilled forms, etc.', + description: + 'Text for functional purposes description in CompleteOptions view state.', + }, + 'Analytics.title': { + defaultMessage: 'Analytics', + description: 'Text for analytics purposes in CompleteOptions view state.', + }, + 'Analytics.description': { + defaultMessage: 'Help us learn how our site is used and how it performs.', + description: + 'Text for analytics purposes description in CompleteOptions view state.', + }, + 'Advertising.title': { + defaultMessage: 'Advertising', + description: + 'Text for advertising purposes in CompleteOptions view state.', + }, + 'Advertising.description': { + defaultMessage: 'Helps us and others serve ads relevant to you.', + description: + 'Text for advertising purposes description in CompleteOptions view state.', + }, + 'SaleOfInfo.title': { + defaultMessage: 'SaleOfInfo', + description: + 'Text for sale of information purposes in CompleteOptions view state.', + }, + 'SaleOfInfo.description': { + defaultMessage: 'Sale of personal information.', + description: + 'Text for advertising purposes description in CompleteOptions view state.', + }, +}); + export const completeOptionsMessages = defineMessages( 'ui.src.completeOptions', { - essentialLabel: { - defaultMessage: 'Essential purposes', - description: 'Text for essential purposes in CompleteOptions view state.', - }, - functionalLabel: { - defaultMessage: 'Functionality', - description: - 'Text for functional purposes in CompleteOptions view state.', - }, - analyticsLabel: { - defaultMessage: 'Analytics', - description: 'Text for analytics purposes in CompleteOptions view state.', - }, - advertisingLabel: { - defaultMessage: 'Advertising', - description: - 'Text for advertising purposes in CompleteOptions view state.', - }, - saleOfInfoLabel: { - defaultMessage: 'Sale of personal information', - description: - 'Text for sale of information purposes in CompleteOptions view state.', - }, saveButtonPrimary: { defaultMessage: 'Confirm', description: 'Confirm button text in CompleteOptions view state.',