-
Notifications
You must be signed in to change notification settings - Fork 92
ENG-2338 - Configure when Banners resurface #7292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9701797
d316699
5c62756
b0c50fe
564a26c
b560e0b
14b77b2
b2b0665
ab71470
15fdd3d
336e548
59e5845
0571640
0ca86ff
bb033da
6452c62
d7418d3
573e908
9818e5e
8ec5a31
b82715a
035f469
205685d
63ff087
ed19e84
83040cd
5db3ce5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| type: Added | ||
| description: Added resurface_behavior configuration to privacy experience configs to control when consent banners are reshown after user interaction | ||
| pr: 7292 | ||
| labels: ["db-migration"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,13 @@ import { | |
| Button, | ||
| ChakraArrowForwardIcon as ArrowForwardIcon, | ||
| ChakraBox as Box, | ||
| ChakraCheckbox as Checkbox, | ||
| ChakraCheckboxGroup as CheckboxGroup, | ||
| ChakraDivider as Divider, | ||
| ChakraFlex as Flex, | ||
| ChakraFormLabel as FormLabel, | ||
| ChakraHeading as Heading, | ||
| ChakraStack as Stack, | ||
| ChakraText as Text, | ||
| formatIsoLocation, | ||
| isoStringToEntry, | ||
|
|
@@ -50,6 +53,7 @@ import { | |
| PrivacyNoticeFramework, | ||
| Property, | ||
| RejectAllMechanism, | ||
| ResurfaceBehavior, | ||
| StagedResourceTypeValue, | ||
| SupportedLanguage, | ||
| } from "~/types/api"; | ||
|
|
@@ -96,6 +100,19 @@ const tcfRejectAllMechanismOptions: SelectProps["options"] = [ | |
| }, | ||
| ]; | ||
|
|
||
| const resurfaceBehaviorOptions = [ | ||
| { | ||
| label: "Reject", | ||
| value: ResurfaceBehavior.REJECT, | ||
| description: "Show the banner again when user rejects", | ||
| }, | ||
| { | ||
| label: "Dismiss", | ||
| value: ResurfaceBehavior.DISMISS, | ||
| description: "Show the banner again when user dismisses", | ||
| }, | ||
| ]; | ||
|
|
||
| const tcfBannerButtonOptions: SelectProps["options"] = [ | ||
| { | ||
| label: "Banner and modal", | ||
|
|
@@ -449,6 +466,52 @@ export const PrivacyExperienceForm = ({ | |
| /> | ||
| </Box> | ||
| )} | ||
| {(values.component === ComponentType.BANNER_AND_MODAL || | ||
| values.component === ComponentType.TCF_OVERLAY) && ( | ||
| <Box> | ||
| <FormLabel fontSize="sm" fontWeight="semibold" mb={2}> | ||
| Resurface banner | ||
| </FormLabel> | ||
| <Text fontSize="sm" color="gray.600" mb={3}> | ||
| Choose when to show the banner again after the user has interacted | ||
| with it. Leave unchecked for default behavior (only resurface on | ||
| cookie expiration, vendor changes, and other mandatory updates.) | ||
| </Text> | ||
| <CheckboxGroup | ||
| value={values.resurface_behavior ?? []} | ||
| onChange={(selectedValues) => { | ||
| setFieldValue( | ||
| "resurface_behavior", | ||
| selectedValues.length > 0 ? selectedValues : null, | ||
| ); | ||
| }} | ||
| > | ||
| <Stack spacing={2}> | ||
| {resurfaceBehaviorOptions.map((option) => { | ||
| const isDisabled = | ||
| option.value === ResurfaceBehavior.DISMISS && | ||
| !values.dismissable; | ||
| return ( | ||
| <Checkbox | ||
| key={option.value} | ||
| value={option.value} | ||
| isDisabled={isDisabled} | ||
| > | ||
| <Box> | ||
| <Text fontSize="sm" fontWeight="medium"> | ||
| {option.label} | ||
| </Text> | ||
| <Text fontSize="xs" color="gray.600"> | ||
| {option.description} | ||
| </Text> | ||
| </Box> | ||
| </Checkbox> | ||
| ); | ||
| })} | ||
| </Stack> | ||
| </CheckboxGroup> | ||
| </Box> | ||
| )} | ||
|
Comment on lines
+469
to
+514
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize
💡 Suggested fix {values.component !== ComponentType.PRIVACY_CENTER &&
values.component !== ComponentType.HEADLESS && (
<Box p="1px">
<CustomSwitch
name="dismissable"
id="dismissable"
label="Allow user to dismiss"
variant="stacked"
+ onChange={(checked) => {
+ setFieldValue("dismissable", checked);
+ if (!checked) {
+ const nextValues =
+ values.resurface_behavior?.filter(
+ (value) => value !== ResurfaceBehavior.DISMISS,
+ ) ?? [];
+ setFieldValue(
+ "resurface_behavior",
+ nextValues.length ? nextValues : null,
+ );
+ }
+ }}
/>
</Box>
)}🤖 Prompt for AI Agents |
||
| <Divider /> | ||
| <Heading fontSize="md" fontWeight="semibold"> | ||
| Privacy notices | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /* istanbul ignore file */ | ||
| /* tslint:disable */ | ||
| /* eslint-disable */ | ||
|
|
||
| /** | ||
| * Resurface behavior options - controls when to re-show the banner/modal. | ||
| * Used to configure whether the experience resurfaces after rejection or dismissal. | ||
| */ | ||
| export enum ResurfaceBehavior { | ||
| REJECT = "reject", | ||
| DISMISS = "dismiss", | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -87,6 +87,20 @@ export interface FidesExperienceConfig { | |||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| reject_all_mechanism: string; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * This corresponds with the "Resurface banner" configuration option. | ||||||||||||||||||||||||||||||||||||||||
| * Controls when to show the consent banner again after the user has interacted with it. | ||||||||||||||||||||||||||||||||||||||||
| * Can include "reject", "dismiss", both, or be empty/null for default behavior (only resurface on cookie expiration or vendor changes). | ||||||||||||||||||||||||||||||||||||||||
| * @example | ||||||||||||||||||||||||||||||||||||||||
| * ```ts | ||||||||||||||||||||||||||||||||||||||||
| * ["reject"] // Resurface only on reject | ||||||||||||||||||||||||||||||||||||||||
| * ["dismiss"] // Resurface only on dismiss | ||||||||||||||||||||||||||||||||||||||||
| * ["reject", "dismiss"] // Resurface on both | ||||||||||||||||||||||||||||||||||||||||
| * null // Default behavior (no resurfacing) | ||||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| resurface_behavior?: Array<string>; | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allow The example block says Suggested fix- resurface_behavior?: Array<string>;
+ resurface_behavior?: Array<"reject" | "dismiss"> | null;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * @internal | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -311,6 +311,15 @@ export const shouldResurfaceBanner = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cookie?.fides_meta.consentMethod === ConsentMethod.GPC) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Resurface if configured for this consent method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cookie?.fides_meta.consentMethod && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| experience.experience_config?.resurface_behavior?.includes( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cookie.fides_meta.consentMethod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+314
to
+321
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This branch never runs for Suggested fix if (
experience.experience_config?.component === ComponentType.TCF_OVERLAY &&
!!cookie
) {
if (!!options && isConsentOverride(options)) {
return false;
}
+ if (
+ cookie.fides_meta.consentMethod &&
+ experience.experience_config?.resurface_behavior?.includes(
+ cookie.fides_meta.consentMethod,
+ )
+ ) {
+ return true;
+ }
if (experience.meta?.version_hash) {
return experience.meta.version_hash !== cookie.tcf_version_hash;
}
return true;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Lastly, if we do have a prior consent state, resurface if we find *any* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // notices that don't have prior consent in that state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hasConsentInCookie = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale "dismiss" value when
dismissableis toggled offWhen
dismissableis set tofalse, the "Dismiss" checkbox in theresurface_behaviorgroup becomes disabled — but the value is not cleared fromvalues.resurface_behavior. This means:dismissableoff, the checkbox appears checked-and-grayed (confusing UX).resurface_behaviorstill includes"dismiss"even thoughdismissableisfalse— storing a logically inconsistent configuration in the database.The form should clear
"dismiss"fromresurface_behaviorwhendismissableis set tofalse. One approach: in theonChangehandler for thedismissableswitch (or in auseEffectwatchingvalues.dismissable), removeResurfaceBehavior.DISMISSfrom the array if it is present: