Skip to content

Commit

Permalink
Back button for languages and new AcceptAllRejectAllToggle view state (
Browse files Browse the repository at this point in the history
…#134)

* first version back button

* Configured view state

* PR candidate for the new view state

* Remove superfluous debug changes

* Added README entry. Need to figure out image.

* Removed unsused import

* update types and fixing ci errors

* Merges dev

* Updating imagine in the README, as well as translations

* Adding customizable footer link message to AcceptAllOrMoreChoices

* Updating translations

* Updating version

---------

Co-authored-by: michaelfarrell76 <[email protected]>
  • Loading branch information
banzzai and michaelfarrell76 authored Aug 10, 2023
1 parent 2c4454a commit 113e52b
Show file tree
Hide file tree
Showing 49 changed files with 472 additions and 71 deletions.
10 changes: 5 additions & 5 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,20 @@ A banner that handles opting in or out of the `Advertising` tag.
| More choices | Redirects to the `CompleteOptions` view state. No purposes change. |
| See our Privacy Policy | Redirects to the privacy policy link specified in [Consent Display Settings](https://app.transcend.io/consent-manager/display-settings) or the [`data-privacy-policy`](https://docs.transcend.io/docs/consent/faq#how-can-i-customize-the-privacy-policy-link-when-hosting-on-multiple-domains?) data attribute. |

### `AcceptAllRejectAllToggle`

**WARNING: In some jurisdictions this UI may be considered a dark pattern. Use at your own risk.** To learn more, head over to our blog post: [Demystifying dark patterns: A practical primer for CPRA compliance](https://transcend.io/blog/dark-patterns-cpra/).

![ViewState = AcceptAllRejectAllToggle](https://github.com/transcend-io/consent-manager-ui/assets/10264973/a8a74188-5464-48fc-9720-d84e7f23c300)

#### Button Mapping

| Button Title | Callback Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Toggle Switch | Opts the user in or out of all purpose. |
| X - Icon | Closes the modal with no changes to purposes and no changes to consent confirmation. |
| See our Privacy Policy | Redirects to the privacy policy link specified in [Consent Display Settings](https://app.transcend.io/consent-manager/display-settings) or the [`data-privacy-policy`](https://docs.transcend.io/docs/consent/faq#how-can-i-customize-the-privacy-policy-link-when-hosting-on-multiple-domains?) data attribute. |

### `LanguageOptions`

This is the view state that allows the user to select their language.
Expand Down
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@

<body style="height: 100%">
<h1>Transcend Consent Manager Playground!</h1>

<button id="cpra_button" style="display: none">
<img src="./privacy-choices-icon.svg" />
<span>Your Privacy Choices</span>
</button>

<script>
if (airgap && airgap.getRegimes().has('CPRA')) {
var cpraButton = document.getElementById('cpra_button');
cpraButton.onclick = function () {
transcend.showConsentManager({ viewState: 'AcceptOrRejectAll' });
};

cpraButton.style.display = 'block';
}
</script>

<h2>Do Not Sell/Share</h2>
<br />
<button id="do-not-sell">Do Not Sell My Personal Information</button>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.0",
"version": "4.3.0",
"license": "MIT",
"main": "build/ui",
"files": [
Expand Down Expand Up @@ -44,7 +44,7 @@
},
"devDependencies": {
"@monaco-editor/react": "^4.4.5",
"@transcend-io/airgap.js-types": "^10.2.0",
"@transcend-io/airgap.js-types": "^10.3.0",
"@transcend-io/type-utils": "^1.0.7",
"@types/node": "^17.0.21",
"@typescript-eslint/eslint-plugin": "^5.12.1",
Expand Down
123 changes: 123 additions & 0 deletions src/components/AcceptAllRejectAllToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { h, JSX } from 'preact';
import { useState } from 'preact/hooks';
import { useIntl } from 'react-intl';
import { useAirgap } from '../hooks';
import { messages } from '../messages';
import type { HandleSetViewState } from '../types';
import { GPCIndicator } from './GPCIndicator';
import { Switch } from './Switch';

// Timer for save state
let savingTimeout: ReturnType<typeof setTimeout>;

/**
* Component showing explanatory text before offering a way
* to opt out of the sale or share of data
*/
export function AcceptAllRejectAllToggle({
handleSetViewState,
fontColor,
}: {
/** Function to change viewState */
handleSetViewState: HandleSetViewState;
/** Font color */
fontColor: string;
}): JSX.Element {
const { airgap } = useAirgap();
const { formatMessage } = useIntl();
const [saving, setSaving] = useState<boolean | null>(null);
const [consentLocal, setConsentLocal] = useState(
!!airgap.getConsent().purposes.SaleOfInfo,
);
const switchId = `all-purposes-${consentLocal}`;

const handleSwitch = (
checked: boolean,
event: JSX.TargetedEvent<HTMLInputElement, Event>,
): void => {
if (checked) {
event.preventDefault();
airgap.optIn(event);
} else {
event.preventDefault();
airgap.optOut(event);
}

setConsentLocal(checked);
setSaving(true);

// Clear any existing timeouts still running
if (savingTimeout) {
clearTimeout(savingTimeout);
}
savingTimeout = setTimeout(() => {
setSaving(false);
}, 500);
};

return (
<div className="column-content">
<button
type="button"
aria-label={formatMessage(messages.close)}
className="do-not-sell-explainer-close"
onClick={() => {
handleSetViewState('close');
}}
>
<svg width="24" height="24" viewBox="0 0 32 32" aria-hidden="true">
<path
fill={fontColor}
// eslint-disable-next-line max-len
d="M25.71 24.29a.996.996 0 1 1-1.41 1.41L16 17.41 7.71 25.7a.996.996 0 1 1-1.41-1.41L14.59 16l-8.3-8.29A.996.996 0 1 1 7.7 6.3l8.3 8.29 8.29-8.29a.996.996 0 1 1 1.41 1.41L17.41 16l8.3 8.29z"
/>
</svg>
<span className="screen-reader">{formatMessage(messages.close)}</span>
</button>
<div>
<div>
<p className="text-title text-title-left">
{formatMessage(messages.consentTitleAcceptAllRejectAllToggle)}
</p>
</div>
<div>
<p className="paragraph">
<div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: formatMessage(
messages.acceptAllRejectAllToggleDescription,
),
}}
/>
</p>
</div>
<div className="margin-tops do-not-sell-explainer-interface">
<GPCIndicator />
<Switch
id={switchId}
checked={consentLocal}
handleSwitch={handleSwitch}
label={formatMessage(
consentLocal
? messages.doNotSellOptedIn
: messages.doNotSellOptedOut,
)}
/>

<p className="paragraph">
{typeof saving === 'boolean'
? formatMessage(
saving
? messages.saving
: consentLocal
? messages.preferencesSavedOptedIn
: messages.preferencesSaved,
)
: '\u200b'}
</p>
</div>
</div>
</div>
);
}
42 changes: 29 additions & 13 deletions src/components/BottomMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,31 @@ export function BottomMenu({

return (
<div className="bottom-menu-container">
{![
'NoticeAndDoNotSell',
'DoNotSellDisclosure',
'OptOutDisclosure',
'PrivacyPolicyNotice',
'AcceptOrRejectAnalytics',
'AcceptAllOrMoreChoices',
'AcceptOrRejectAllOrMoreChoices',
'CompleteOptionsInverted',
'DoNotSellExplainer',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
].includes(viewState as any) &&
{viewState === 'LanguageOptions' ? (
<div className="bottom-menu-item-container">
<MenuItem
label={formatMessage(bottomMenuMessages.backButtonTooltip)}
type="button"
onClick={(e) => handleSetViewState('back', e)}
>
{formatMessage(bottomMenuMessages.backButtonText)}
</MenuItem>
</div>
) : (
![
'NoticeAndDoNotSell',
'DoNotSellDisclosure',
'OptOutDisclosure',
'PrivacyPolicyNotice',
'AcceptOrRejectAnalytics',
'AcceptAllOrMoreChoices',
'AcceptOrRejectAllOrMoreChoices',
'CompleteOptionsInverted',
'DoNotSellExplainer',
'LanguageOptions',
'AcceptAllRejectAllToggle',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
].includes(viewState as any) &&
(viewState === 'CompleteOptions' ? (
!firstSelectedViewState ||
firstSelectedViewState === 'CompleteOptions' ? null : (
Expand All @@ -67,7 +80,8 @@ export function BottomMenu({
{formatMessage(bottomMenuMessages.moreChoicesButtonPrimary)}
</MenuItem>
</div>
))}
))
)}

{viewState === 'NoticeAndDoNotSell' && (
<div className="bottom-menu-item-container">
Expand Down Expand Up @@ -108,6 +122,8 @@ export function BottomMenu({
{formatMessage(
viewState === 'CompleteOptionsInverted'
? bottomMenuMessages.showPolicyButtonCompleteOptionsInverted
: viewState === 'AcceptAllOrMoreChoices'
? bottomMenuMessages.showPolicyButtonsAcceptAllOrMoreChoices
: bottomMenuMessages.showPolicyButtonPrimary,
)}
</MenuItem>
Expand Down
8 changes: 8 additions & 0 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { QuickOptions3 } from './QuickOptions3';
import { PrivacyPolicyNotice } from './PrivacyPolicyNotice';
import { AcceptAllOrMoreChoices } from './AcceptAllOrMoreChoices';
import { AcceptOrRejectAllOrMoreChoices } from './AcceptOrRejectAllOrMoreChoices';
import { AcceptAllRejectAllToggle } from './AcceptAllRejectAllToggle';

/**
* Presents view states (collapsed, GDPR-mode, CCPA-mode etc)
Expand Down Expand Up @@ -106,6 +107,13 @@ export function Main({
<AcceptOrRejectAll handleSetViewState={handleSetViewState} />
)}

{viewState === 'AcceptAllRejectAllToggle' && (
<AcceptAllRejectAllToggle
handleSetViewState={handleSetViewState}
fontColor={config.theme.fontColor}
/>
)}

{viewState === 'AcceptAllOrMoreChoices' && (
<AcceptAllOrMoreChoices handleSetViewState={handleSetViewState} />
)}
Expand Down
32 changes: 30 additions & 2 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ export const messages = defineMessages('ui.src.messages', {
description:
'Button text for redirecting the user to more granular consent choices.',
},
consentTitleAcceptAllRejectAllToggle: {
defaultMessage: 'Your Privacy Choices',
description:
'The title displayed in the AcceptAllRejectAllToggle view state.',
},
acceptAllRejectAllToggleDescription: {
defaultMessage:
/* eslint-disable max-len */
'By opting in below, you agree to the storing of cookies on your device for functional, analytics, and advertising purposes.',
/* eslint-enable max-len */
description:
'The description text for the AcceptAllRejectAllToggle view state.',
},
});

export const quickOptionsMessages = defineMessages('ui.src.quickOptions', {
Expand Down Expand Up @@ -263,6 +276,16 @@ export const optOutDisclosureMessages = defineMessages(
);

export const bottomMenuMessages = defineMessages('ui.src.bottomMenu', {
backButtonText: {
defaultMessage: 'Go back',
description:
'Hover text the back button in the footer of the consent banner language options.',
},
backButtonTooltip: {
defaultMessage: 'Go back',
description:
'Main text for the back button in the footer of the consent banner language options.',
},
moreChoicesButtonPrimary: {
defaultMessage: 'More choices',
description: 'Text for selecting specific more opt out choices.',
Expand All @@ -280,14 +303,19 @@ export const bottomMenuMessages = defineMessages('ui.src.bottomMenu', {
description: 'Hover/alt text for selecting simpler opt out choices.',
},
showPolicyButtonPrimary: {
defaultMessage: 'See Our Privacy Policy',
defaultMessage: 'See our privacy policy',
description: 'Text for linking out to privacy policy.',
},
showPolicyButtonCompleteOptionsInverted: {
defaultMessage: 'See Our Privacy Policy',
defaultMessage: 'See our privacy policy',
description:
'Text for linking out to privacy policy in CompleteOptionsInverted UI.',
},
showPolicyButtonsAcceptAllOrMoreChoices: {
defaultMessage: 'See our privacy policy',
description:
'Text for linking out to privacy policy in AcceptAllOrMoreChoices UI.',
},
showPolicyButtonLabel: {
defaultMessage: 'Visit our privacy policy',
description: 'Hover/alt for linking out to privacy policy.',
Expand Down
9 changes: 7 additions & 2 deletions src/translations/af-ZA.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"ui.src.bottomMenu.backButtonText": "Gaan terug",
"ui.src.bottomMenu.backButtonTooltip": "Gaan terug",
"ui.src.bottomMenu.moreChoicesButtonLabel": "Kliek om meer keuses te wys",
"ui.src.bottomMenu.moreChoicesButtonPrimary": "Meer keuses",
"ui.src.bottomMenu.showPolicyButtonCompleteOptionsInverted": "Sien Ons Privaatheidsbeleid",
"ui.src.bottomMenu.showPolicyButtonCompleteOptionsInverted": "Sien ons privaatheidsbeleid",
"ui.src.bottomMenu.showPolicyButtonLabel": "Besoek ons privaatheidsbeleid",
"ui.src.bottomMenu.showPolicyButtonPrimary": "Sien Ons Privaatheidsbeleid",
"ui.src.bottomMenu.showPolicyButtonPrimary": "Sien ons privaatheidsbeleid",
"ui.src.bottomMenu.showPolicyButtonsAcceptAllOrMoreChoices": "Sien ons privaatheidsbeleid",
"ui.src.bottomMenu.showSecondaryPolicyButton": "Lees meer",
"ui.src.bottomMenu.showSecondaryPolicyButtonLabel": "Lees meer oor jou privaatheidskeuses",
"ui.src.bottomMenu.simplerChoicesButtonLabel": "Kliek om eenvoudiger keuses te wys",
Expand All @@ -26,6 +29,7 @@
"ui.src.messages.acceptAdvertising": "Goed",
"ui.src.messages.acceptAllButtonPrimary": "Aanvaar alle",
"ui.src.messages.acceptAllDescription": "Deur te kliek op “Aanvaar alles”, stem jy in tot die stoor van koekies op jou toestel vir funksionele, analise en advertensiedoeleindes.",
"ui.src.messages.acceptAllRejectAllToggleDescription": "Deur hieronder in te kies, stem jy in tot die stoor van koekies op jou toestel vir funksionele, analitiese en advertensiedoeleindes.",
"ui.src.messages.acceptAnalytics": "Goed",
"ui.src.messages.acceptOrRejectAdvertisingDescription": "Deur op “Goed” te klik, stem jy in tot die gebruik van jou persoonlike inligting vir advertensiedoeleindes.",
"ui.src.messages.acceptOrRejectAnalyticsDescription": "Deur op “Goed” te klik, stem jy in tot die gebruik van jou sensitiewe inligting vir analitiese doeleindes.",
Expand All @@ -34,6 +38,7 @@
"ui.src.messages.completeOptionsInvertedTitle": "Jou privaatheid keuses",
"ui.src.messages.consentTitle": "Waarvoor kan ons data gebruik?",
"ui.src.messages.consentTitleAcceptAll": "Ons gebruik koekies",
"ui.src.messages.consentTitleAcceptAllRejectAllToggle": "Jou privaatheid keuses",
"ui.src.messages.consentTitleAcceptOrRejectAdvertising": "Hierdie webwerf gebruik advertensies",
"ui.src.messages.consentTitleAcceptOrRejectAnalytics": "Hierdie webwerf maak gebruik van analise",
"ui.src.messages.consentTitleDoNotSellExplainer": "Moenie my persoonlike inligting verkoop of deel nie",
Expand Down
Loading

1 comment on commit 113e52b

@vercel
Copy link

@vercel vercel bot commented on 113e52b Aug 10, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.