chore(SW-3665): Upgrade to Next 16 * Upgrade partner-sas * Upgrade scandic-web to next 16 * Update peerDep versions * Fix revalidateTag * Remove comment * Merge branch 'master' into chore/upgrade-to-next16 * Update netlify adapter * Build with webpack instead of turbopack * Revert from proxy to middleware * Merge branch 'master' into chore/upgrade-to-next16 * Revert proxy type * Fix react types versions * 16.0.9 * Bump to 16.0.10 Approved-by: Linus Flood
264 lines
9.1 KiB
TypeScript
264 lines
9.1 KiB
TypeScript
"use client"
|
|
|
|
import { AnimatePresence, motion } from "motion/react"
|
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
import { Dialog, Modal, ModalOverlay } from "react-aria-components"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { Button } from "@scandic-hotels/design-system/Button"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import ScandicLogoIcon from "@scandic-hotels/design-system/Icons/ScandicLogoIcon"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { trpc } from "@scandic-hotels/trpc/client"
|
|
|
|
import { useUpdateProfilingConsent } from "@/hooks/useUpdateProfilingConsent"
|
|
import {
|
|
profilingConsentOpenEvent,
|
|
readDismissed,
|
|
setDismissed as persistDismissed,
|
|
} from "@/utils/profilingConsent"
|
|
import { trackConsentAction } from "@/utils/tracking/profilingConsent"
|
|
|
|
import ProfilingConsentAccordion from "../Accordion"
|
|
import { GetMainIconByCSIdentifier } from "../utils"
|
|
import BenefitCards from "./BenefitCards"
|
|
|
|
import styles from "./profilingConsentModal.module.css"
|
|
|
|
import type { ProfilingConsentModal as ProfilingConsentModalType } from "@scandic-hotels/trpc/types/profilingConsent"
|
|
|
|
type ProfilingConsentModalProps = {
|
|
memberKey?: string
|
|
content: ProfilingConsentModalType
|
|
iconIdentifier: string
|
|
readOnly?: boolean
|
|
}
|
|
|
|
const MotionModal = motion.create(Modal)
|
|
|
|
function usePromptInitialization(memberKey: string | undefined) {
|
|
const utils = trpc.useUtils()
|
|
|
|
const updateConsentPromptDate =
|
|
trpc.user.profilingConsentPromptDate.update.useMutation({
|
|
onSuccess: () => utils.user.get.invalidate(),
|
|
})
|
|
|
|
const mutationRef = useRef(updateConsentPromptDate)
|
|
// eslint-disable-next-line react-hooks/refs
|
|
mutationRef.current = updateConsentPromptDate
|
|
|
|
const [shouldOpenInitially, setShouldOpenInitially] = useState(false)
|
|
const hasSentPromptDate = useRef(false)
|
|
|
|
const sendPromptDate = useCallback((date: string) => {
|
|
mutationRef.current.mutate({
|
|
profilingConsentPromptDate: date,
|
|
})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (!memberKey) return
|
|
|
|
const dismissed = readDismissed(memberKey)
|
|
const firstOpen = !dismissed
|
|
|
|
setShouldOpenInitially(firstOpen)
|
|
|
|
if (firstOpen && !hasSentPromptDate.current) {
|
|
hasSentPromptDate.current = true
|
|
sendPromptDate(new Date().toISOString())
|
|
}
|
|
}, [memberKey, sendPromptDate])
|
|
|
|
return shouldOpenInitially
|
|
}
|
|
|
|
function useModalEvents(setOpen: (s: boolean) => void) {
|
|
useEffect(() => {
|
|
const handleOpen: EventListener = () => setOpen(true)
|
|
window.addEventListener(profilingConsentOpenEvent, handleOpen)
|
|
return () =>
|
|
window.removeEventListener(profilingConsentOpenEvent, handleOpen)
|
|
}, [setOpen])
|
|
}
|
|
|
|
export default function ProfilingConsentModal({
|
|
memberKey,
|
|
content,
|
|
iconIdentifier,
|
|
readOnly = false,
|
|
}: ProfilingConsentModalProps) {
|
|
const intl = useIntl()
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [activeChoice, setActiveChoice] = useState<boolean | null>(null)
|
|
|
|
const shouldOpenInitially = usePromptInitialization(memberKey)
|
|
|
|
const { initiateUpdateConsent, isLoading, isSuccess } =
|
|
useUpdateProfilingConsent()
|
|
|
|
useEffect(() => {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
if (shouldOpenInitially) setOpen(true)
|
|
}, [shouldOpenInitially])
|
|
|
|
useModalEvents(setOpen)
|
|
|
|
const onClose = useCallback(() => {
|
|
if (memberKey) {
|
|
persistDismissed(memberKey)
|
|
}
|
|
setOpen(false)
|
|
}, [memberKey])
|
|
|
|
useEffect(() => {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
if (isSuccess) onClose()
|
|
}, [isSuccess, onClose])
|
|
|
|
if (!memberKey && !readOnly) return null
|
|
|
|
const handleConsentClick = (consent: boolean) => {
|
|
setActiveChoice(consent)
|
|
initiateUpdateConsent(consent)
|
|
}
|
|
|
|
return (
|
|
<ModalOverlay
|
|
className={styles.overlay}
|
|
isOpen={open}
|
|
onOpenChange={(state) => !state && onClose()}
|
|
isKeyboardDismissDisabled
|
|
isDismissable={false}
|
|
>
|
|
<AnimatePresence mode="wait">
|
|
{open && (
|
|
<MotionModal
|
|
className={styles.modal}
|
|
initial={{ y: 32, opacity: 0 }}
|
|
animate={{ y: 0, opacity: 1 }}
|
|
exit={{ y: 32, opacity: 0 }}
|
|
transition={{
|
|
y: { duration: 0.4, ease: "easeInOut" },
|
|
opacity: { duration: 0.4, ease: "easeInOut" },
|
|
}}
|
|
>
|
|
<Dialog
|
|
aria-label={intl.formatMessage({
|
|
id: "profilingConsent.profilingConsent",
|
|
defaultMessage: "Profiling consent",
|
|
})}
|
|
className={styles.dialog}
|
|
>
|
|
<header className={styles.header}>
|
|
<div className={styles.logoWrap}>
|
|
<ScandicLogoIcon height={20} width={94} />
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="Text"
|
|
size="Large"
|
|
color="Primary"
|
|
className={styles.closeBtn}
|
|
onClick={() => {
|
|
trackConsentAction({ position: "modal", name: "close" })
|
|
onClose()
|
|
}}
|
|
aria-label={intl.formatMessage({
|
|
id: "common.close",
|
|
defaultMessage: "Close",
|
|
})}
|
|
>
|
|
<MaterialIcon color="CurrentColor" icon="close" />
|
|
</Button>
|
|
</header>
|
|
|
|
<main className={styles.content}>
|
|
<GetMainIconByCSIdentifier identifier={iconIdentifier} />
|
|
<div className={styles.textContent}>
|
|
<Typography className={styles.heading} variant="Title/md">
|
|
<h2>{content.header}</h2>
|
|
</Typography>
|
|
<Typography className={styles.text} variant="Body/Lead text">
|
|
<p>{content.sub_header}</p>
|
|
</Typography>
|
|
</div>
|
|
<BenefitCards cards={content.cards} />
|
|
|
|
<section className={styles.container}>
|
|
<header className={styles.header}>
|
|
<Typography variant="Title/Subtitle/lg">
|
|
<h3>
|
|
{intl.formatMessage({
|
|
id: "profilingConsent.personalization&privacy",
|
|
defaultMessage: "Personalization & privacy",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "profilingConsent.byAcceptingThisIConsent",
|
|
defaultMessage:
|
|
"By accepting this 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.",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
</header>
|
|
<ProfilingConsentAccordion component="modal" />
|
|
</section>
|
|
</main>
|
|
|
|
{!readOnly && (
|
|
<footer className={styles.actions}>
|
|
<Button
|
|
variant="Primary"
|
|
color="Primary"
|
|
size="Large"
|
|
typography="Body/Supporting text (caption)/smRegular"
|
|
type="button"
|
|
isDisabled={isLoading}
|
|
isPending={isLoading && activeChoice === true}
|
|
onClick={() => {
|
|
trackConsentAction({
|
|
position: "modal",
|
|
name: "accept personalization",
|
|
})
|
|
handleConsentClick(true)
|
|
}}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "profilingConsent.acceptPersonalization",
|
|
defaultMessage: "Accept Personalization",
|
|
})}
|
|
</Button>
|
|
<Button
|
|
variant="Secondary"
|
|
size="Large"
|
|
color="Primary"
|
|
typography="Body/Supporting text (caption)/smRegular"
|
|
type="button"
|
|
isDisabled={isLoading}
|
|
isPending={isLoading && activeChoice === false}
|
|
onClick={() => {
|
|
trackConsentAction({ position: "modal", name: "decline" })
|
|
handleConsentClick(false)
|
|
}}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "profilingConsent.decline",
|
|
defaultMessage: "Decline",
|
|
})}
|
|
</Button>
|
|
</footer>
|
|
)}
|
|
</Dialog>
|
|
</MotionModal>
|
|
)}
|
|
</AnimatePresence>
|
|
</ModalOverlay>
|
|
)
|
|
}
|