Merged in feat/LOY-490-cleanup-profiling-consent (pull request #3237)

Feat/LOY-490 cleanup profiling consent

* chore(LOY-490): cleanup accordion

* xhore(LOY-490): cleanup signup

* fix(LOY-490): use correct tokens

* fi(LOY-490): change button to correct color

* fix(LOY-490): switch deprecated Link


Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
Matilda Landström
2025-11-27 07:48:34 +00:00
parent 42b039c4ef
commit b122f2ff4f
8 changed files with 39 additions and 338 deletions

View File

@@ -27,12 +27,6 @@
gap: var(--Space-x1);
}
.personalizationMoreInfo {
display: grid;
grid-template-columns: auto 1fr;
align-items: start;
gap: var(--Space-x2);
}
.personalizationButton {
width: fit-content;
}

View File

@@ -18,8 +18,8 @@ import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import DateSelect from "@scandic-hotels/design-system/Form/Date"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/OldDSLink"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { TextLinkButton } from "@scandic-hotels/design-system/TextLinkButton"
import { toast } from "@scandic-hotels/design-system/Toast"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { useFormTracking } from "@scandic-hotels/tracking/useFormTracking"
@@ -39,7 +39,6 @@ import { getErrorMessage } from "@/utils/getErrorMessage"
import { requestOpen } from "@/utils/profilingConsent"
import { trackLinkClick } from "@/utils/tracking/profilingConsent"
// import { type SignUpSchema, signUpSchema } from "./schema"
import styles from "./form.module.css"
import type { SignUpFormProps } from "@/types/components/form/signupForm"
@@ -300,31 +299,7 @@ export default function SignupForm({ title }: SignUpFormProps) {
"I consent to Scandic using my information to give me even more personalized travel inspiration and offers from Scandic and trusted Scandic Friends partners. This means Scandic may use information about my interactions with Scandic Friends partners, and share details of my interactions with Scandic with those partners, to make the experience even more relevant to me.",
})}
</Checkbox>
<div className={styles.personalizationMoreInfo}>
<MaterialIcon icon="person" color="Icon/Interactive/Default" />
<p>
{intl.formatMessage({
id: "signup.GetATailoredProfile",
defaultMessage:
"Get a tailored profile that adapts to your interests.",
})}
</p>
</div>
<div className={styles.personalizationMoreInfo}>
<MaterialIcon
icon="featured_seasonal_and_gifts"
color="Icon/Interactive/Default"
/>
<p>
{intl.formatMessage({
id: "signup.PersonalizedOffersEarlyAccess",
defaultMessage:
"Personalized offers, early access to campaigns, and deals you won't find anywhere else!",
})}
</p>
</div>
<Button
variant="Text"
<TextLinkButton
typography="Link/sm"
color="Primary"
className={styles.personalizationButton}
@@ -334,7 +309,7 @@ export default function SignupForm({ title }: SignUpFormProps) {
id: "signup.ReadMoreAboutPersonalization",
defaultMessage: "Read more about personalization at Scandic",
})}
</Button>
</TextLinkButton>
</section>
<section className={styles.terms}>
@@ -375,24 +350,24 @@ export default function SignupForm({ title }: SignUpFormProps) {
},
{
termsAndConditionsLink: (str) => (
<Link
textDecoration="underline"
<TextLink
color="Text/Interactive/Secondary"
target="_blank"
href={membershipTermsAndConditions[lang]}
isInline
>
{str}
</Link>
</TextLink>
),
privacyPolicy: (str) => (
<Link
textDecoration="underline"
<TextLink
color="Text/Interactive/Secondary"
target="_blank"
href={privacyPolicyRoutes[lang]}
isInline
>
{str}
</Link>
</TextLink>
),
}
)}

View File

@@ -2,23 +2,18 @@
import { useIntl } from "react-intl"
import { privacy } from "@scandic-hotels/common/constants/routes/customerService"
import Accordion from "@scandic-hotels/design-system/Accordion"
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
import { IconByIconName } from "@scandic-hotels/design-system/Icons/IconByIconName"
import { IconName } from "@scandic-hotels/design-system/Icons/iconName"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { Typography } from "@scandic-hotels/design-system/Typography"
import useLang from "@/hooks/useLang"
import { trackLinkClick } from "@/utils/tracking/profilingConsent"
import { getLegalText } from "./personalizationLegalText"
import styles from "./profilingConsentAccordion.module.css"
interface ProfilingConsentAccordionProps {
export interface ProfilingConsentAccordionProps {
component: "modal" | "profile"
}
export default function ProfilingConsentAccordion({
@@ -27,216 +22,8 @@ export default function ProfilingConsentAccordion({
const intl = useIntl()
const lang = useLang()
const dataWeProcess = [
{
icon: IconName.Person,
title: intl.formatMessage({
id: "profilingConsent.NameAndContactInformation",
defaultMessage: "Name and contact information",
}),
subtitle: intl.formatMessage({
id: "profilingConsent.NameEmailPhoneAddress",
defaultMessage:
"Your name, email address, phone number and postal address",
}),
},
{
icon: IconName.Luggage,
title: intl.formatMessage({
id: "profilingConsent.YourBookingsWithScandic",
defaultMessage: "Your bookings with Scandic",
}),
subtitle: intl.formatMessage({
id: "profilingConsent.BookingsDatesDestinationsPreferences",
defaultMessage: "Hotel bookings, dates, destinations and preferences",
}),
},
{
icon: IconName.Diamond,
title: intl.formatMessage({
id: "profilingConsent.YourMembershipInformation",
defaultMessage: "Your membership information",
}),
subtitle: intl.formatMessage({
id: "profilingConsent.ScandicFriendsPointsBenefits",
defaultMessage: "Scandic Friends status, points and member benefits",
}),
},
{
icon: IconName.Business,
title: intl.formatMessage({
id: "profilingConsent.CompanyInformation",
defaultMessage: "Company information",
}),
subtitle: intl.formatMessage({
id: "profilingConsent.InformationAboutYourCompany",
defaultMessage:
"Information about the company you work for (if available)",
}),
},
{
icon: IconName.Globe,
title: intl.formatMessage({
id: "profilingConsent.UserGeneratedData",
defaultMessage: "User-generated data",
}),
subtitle: intl.formatMessage({
id: "profilingConsent.ClicksBrowsingPurchasingInteractions",
defaultMessage:
"Clicks, browsing, purchase history and website interactions",
}),
},
]
const rights = [
intl.formatMessage({
id: "profilingConsent.GetInformation",
defaultMessage: "Get information about how we process your data",
}),
intl.formatMessage({
id: "profilingConsent.RequestDeletion",
defaultMessage: "Request deletion of your personal data",
}),
intl.formatMessage({
id: "profilingConsent.WithdrawConsent",
defaultMessage: "Withdraw your consent at any time",
}),
]
const security = [
intl.formatMessage({
id: "profilingConsent.EncryptedSecureDataStorage",
defaultMessage: "Encrypted & secure data storage",
}),
intl.formatMessage({
id: "profilingConsent.WeWillNeverSellYourData",
defaultMessage: "We will never sell your data",
}),
intl.formatMessage({
id: "profilingConsent.RegularSecurityAudits",
defaultMessage: "Regular security audits",
}),
intl.formatMessage({
id: "profilingConsent.GDPRCompliantProcessing",
defaultMessage: "GDPR-compliant processing",
}),
]
return (
<Accordion type="sidepeek" className={styles.accordion}>
<AccordionItem
title={intl.formatMessage({
id: "profilingConsent.whatDoWeProcess",
defaultMessage: "What data do we process?",
})}
iconName={IconName.InfoCircle}
className={styles.accordionItem}
openedOnRender
>
<ul className={styles.list}>
{dataWeProcess.map((item, i) => (
<li key={i} className={styles.row}>
<span className={styles.rowIcon} aria-hidden>
<IconByIconName
iconName={item.icon}
color="Icon/Interactive/Default"
size={24}
/>
</span>
<span className={styles.rowText}>
<Typography variant="Body/Paragraph/mdBold">
<p>{item.title}</p>
</Typography>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p>{item.subtitle}</p>
</Typography>
</span>
</li>
))}
</ul>
</AccordionItem>
<AccordionItem
title={intl.formatMessage({
id: "profilingConsent.transparency&processing",
defaultMessage: "Transparency & data processing",
})}
iconName={IconName.EyeShow}
className={styles.accordionItem}
openedOnRender
>
<div className={styles.columns}>
<div className={styles.column}>
<Typography variant="Title/Subtitle/md">
<h4>
{intl.formatMessage({
id: "profilingConsent.youHaveTheRightTo",
defaultMessage: "You have the right to",
})}
</h4>
</Typography>
<ul className={styles.bullets}>
{rights.map((text, i) => (
<li key={i} className={styles.bulletRow}>
<MaterialIcon
icon="favorite"
size={20}
color="Icon/Accent"
isFilled
/>
<Typography variant="Body/Paragraph/mdRegular">
<p>{text}</p>
</Typography>
</li>
))}
</ul>
</div>
<div className={styles.column}>
<Typography variant="Title/Subtitle/md">
<h4>
{intl.formatMessage({
id: "profilingConsent.dataSecurity",
defaultMessage: "Data security",
})}
</h4>
</Typography>
<ul className={styles.bullets}>
{security.map((text, i) => (
<li key={i} className={styles.bulletRow}>
<MaterialIcon icon="lock" size={20} color="Icon/Accent" />
<Typography variant="Body/Paragraph/mdRegular">
<p>{text}</p>
</Typography>
</li>
))}
</ul>
</div>
</div>
<TextLink
href={privacy[lang]}
color="Text/Interactive/Secondary"
typography="Link/md"
className={styles.learnMoreLink}
target="_blank"
onClick={() =>
trackLinkClick({
position: component,
name: "learn more about how we process your data",
})
}
>
{intl.formatMessage({
id: "profilingConsent.learnMoreAboutProcessing",
defaultMessage: "Learn more about how we process your data",
})}
<MaterialIcon
icon="open_in_new"
size={20}
color="Icon/Interactive/Secondary"
/>
</TextLink>
</AccordionItem>
<AccordionItem
title={intl.formatMessage({
id: "profilingConsent.readMoreAboutPersonalization",
@@ -246,7 +33,7 @@ export default function ProfilingConsentAccordion({
className={styles.accordionItem}
>
<Typography variant="Body/Paragraph/mdRegular">
<p>{getLegalText(intl, lang)}</p>
<p>{getLegalText(intl, lang, component)}</p>
</Typography>
</AccordionItem>
</Accordion>

View File

@@ -3,10 +3,18 @@ import { TextLink } from "@scandic-hotels/design-system/TextLink"
import { partners } from "@/constants/webHrefs"
import { trackLinkClick } from "@/utils/tracking/profilingConsent"
import type { Lang } from "@scandic-hotels/common/constants/language"
import type { IntlShape } from "react-intl"
export function getLegalText(intl: IntlShape, lang: Lang) {
import type { ProfilingConsentAccordionProps } from "."
export function getLegalText(
intl: IntlShape,
lang: Lang,
component: ProfilingConsentAccordionProps["component"]
) {
const partnerLink = partners[lang]
const privacyPolicyLink = privacy[lang]
@@ -28,6 +36,12 @@ export function getLegalText(intl: IntlShape, lang: Lang) {
{
partnerLink: (linkText) => (
<TextLink
onClick={() =>
trackLinkClick({
position: component,
name: linkText[0],
})
}
href={partnerLink}
color="Text/Interactive/Secondary"
typography="Link/md"
@@ -39,6 +53,12 @@ export function getLegalText(intl: IntlShape, lang: Lang) {
),
privacyPolicyLink: (linkText) => (
<TextLink
onClick={() =>
trackLinkClick({
position: component,
name: linkText[0],
})
}
href={privacyPolicyLink}
color="Text/Interactive/Secondary"
typography="Link/md"

View File

@@ -1,79 +1,7 @@
.divider {
margin: var(--Space-x1) 0;
}
.accordion {
width: 100%;
}
.list {
display: grid;
gap: var(--Space-x15);
}
.row {
display: grid;
grid-template-columns: 40px 1fr;
gap: var(--Space-x2);
align-items: start;
border: 0.696px solid var(--Border-Divider-Subtle);
border-radius: var(--Corner-radius-md);
background: var(--Surface-Primary-OnSurface-Default);
padding: var(--Space-x1);
}
.accordionItem:first-child {
border-top: 1px solid var(--Border-Default);
}
.rowIcon {
display: flex;
align-items: center;
justify-content: center;
place-self: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--Surface-Primary-Default);
}
.rowText {
display: grid;
gap: 2px;
}
.columns {
display: grid;
gap: var(--Space-x3);
margin-bottom: var(--Space-x3);
}
.column {
display: grid;
gap: var(--Space-x1);
height: fit-content;
}
.bullets {
display: grid;
gap: var(--Space-x1);
}
.bulletRow {
display: grid;
grid-template-columns: 20px 1fr;
gap: var(--Space-x1);
align-items: start;
}
.learnMoreLink {
display: flex;
gap: var(--Space-x05);
justify-content: left;
}
@media (min-width: 768px) {
.columns {
grid-template-columns: 1fr 1fr;
}
}

View File

@@ -32,7 +32,7 @@
box-shadow: var(--modal-box-shadow);
width: 100%;
max-width: 100%;
border-radius: var(--Corner-radius-xLarge) var(--Corner-radius-xLarge) 0 0;
border-radius: var(--Corner-radius-xl) var(--Corner-radius-xl) 0 0;
}
.dialog {
@@ -90,8 +90,8 @@
padding: var(--Space-x2) var(--Space-x3) var(--Space-x3) var(--Space-x3);
border-top: 1px solid var(--Border-Divider-Subtle);
background: var(--Base-Surface-Primary-light-Normal);
border-bottom-right-radius: var(--Corner-radius-xLarge);
border-bottom-left-radius: var(--Corner-radius-xLarge);
border-bottom-right-radius: var(--Corner-radius-xl);
border-bottom-left-radius: var(--Corner-radius-xl);
}
.container {
@@ -119,7 +119,7 @@
.modal {
width: 95%;
max-width: 95%;
border-radius: var(--Corner-radius-xLarge);
border-radius: var(--Corner-radius-xl);
}
.actions {

View File

@@ -4,11 +4,7 @@ import type { TrackingSDKPageData } from "@scandic-hotels/tracking/types"
interface trackLinkClickProps {
position: "modal" | "banner" | "signup" | "profile"
name:
| "learn more about how we process your data"
| "edit preference in my profile"
| "manage profiling consent"
| "read more about personalization at scandic"
name: string
}
export function trackLinkClick({ position, name }: trackLinkClickProps) {
trackEvent({

View File

@@ -32,6 +32,7 @@ export function TextLinkButton({
return (
<button
{...props}
type="button"
disabled={isDisabled}
className={cx(classNames, styles.button)}
/>