Merged in feat/LOY-203-connected-state-employee-benefits (pull request #2496)

feat(LOY-203): Enable Opening Team Member Card from Employee Benefits Page When Connected

* feat(LOY-203): add support for opening team member card modal from employee benefits page when connected

* fix(LOY-203): add id_card to  material symbol icons

* fix(LOY-203): remove uneeded dtmc btb style


Approved-by: Erik Tiekstra
Approved-by: Linus Flood
This commit is contained in:
Chuma Mcphoy (We Ahead)
2025-07-02 11:50:27 +00:00
parent 7aed74611f
commit a9868dac9c
10 changed files with 122 additions and 77 deletions

View File

@@ -1,13 +1,16 @@
import { Divider } from "@scandic-hotels/design-system/Divider" import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { env } from "@/env/server" import { env } from "@/env/server"
import { getProfile } from "@/lib/trpc/memoizedRequests" import { getProfile } from "@/lib/trpc/memoizedRequests"
import { TeamMemberCardTrigger } from "@/components/DigitalTeamMemberCard/Trigger"
import DigitalTeamMemberCard from "@/components/MyPages/DigitalTeamMemberCard" import DigitalTeamMemberCard from "@/components/MyPages/DigitalTeamMemberCard"
import DigitalTeamMemberCardAlert from "@/components/MyPages/DigitalTeamMemberCard/Alert" import DigitalTeamMemberCardAlert from "@/components/MyPages/DigitalTeamMemberCard/Alert"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link" import SectionLink from "@/components/Section/Link"
import { getIntl } from "@/i18n"
import Hero from "./Friend/Hero" import Hero from "./Friend/Hero"
import MembershipNumber from "./Friend/MembershipNumber" import MembershipNumber from "./Friend/MembershipNumber"
@@ -23,6 +26,7 @@ export default async function Overview({
subtitle, subtitle,
title, title,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const intl = await getIntl()
const user = await getProfile() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user) {
return null return null
@@ -38,7 +42,21 @@ export default async function Overview({
headingAs="h3" headingAs="h3"
headingLevel="h1" headingLevel="h1"
/> />
<DigitalTeamMemberCard user={user} /> <DigitalTeamMemberCard user={user}>
<TeamMemberCardTrigger
className={styles.teamMemberCardButton}
variant="Tertiary"
color="Primary"
typography="Body/Paragraph/mdBold"
>
<>
<MaterialIcon icon="id_card" size={24} color="CurrentColor" />
{intl.formatMessage({
defaultMessage: "Show Team Member Card",
})}
</>
</TeamMemberCardTrigger>
</DigitalTeamMemberCard>
<Hero color="red"> <Hero color="red">
<Friend membership={user.membership} name={user.name}> <Friend membership={user.membership} name={user.name}>
<MembershipNumber color="burgundy" membership={user.membership} /> <MembershipNumber color="burgundy" membership={user.membership} />

View File

@@ -2,6 +2,16 @@
margin-top: var(--Spacing-x2); margin-top: var(--Spacing-x2);
} }
.teamMemberCardButton {
border-radius: var(--Corner-radius-md);
color: var(--Text-Brand-OnPrimary-3-Accent);
&:focus,
&:not(:disabled):hover {
color: var(--Text-Brand-OnPrimary-3-Accent);
}
}
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.container { .container {
/* Full-width override styling */ /* Full-width override styling */

View File

@@ -2,26 +2,28 @@ import React from "react"
import { signup } from "@scandic-hotels/common/constants/routes/signup" import { signup } from "@scandic-hotels/common/constants/routes/signup"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
import { dtmcLogin } from "@/constants/routes/dtmc" import { dtmcLogin } from "@/constants/routes/dtmc"
import { login } from "@/constants/routes/handleAuth" import { login } from "@/constants/routes/handleAuth"
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth"
import ButtonLink from "@/components/ButtonLink" import ButtonLink from "@/components/ButtonLink"
import { TeamMemberCardTrigger } from "@/components/DigitalTeamMemberCard/Trigger"
import DigitalTeamMemberCard from "@/components/MyPages/DigitalTeamMemberCard"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { isEmployeeLinked } from "@/utils/user"
import styles from "./callToActions.module.css" import styles from "./callToActions.module.css"
export default async function EmployeeBenefitsCallToActions() { export default async function EmployeeBenefitsCallToActions() {
const session = await auth() const user = await getProfileSafely()
const intl = await getIntl() const intl = await getIntl()
const lang = await getLang() const lang = await getLang()
const loginAndLinkURL = `${login[lang]}?redirectTo=${encodeURIComponent(dtmcLogin[lang])}` const loginAndLinkURL = `${login[lang]}?redirectTo=${encodeURIComponent(dtmcLogin[lang])}`
if (!isValidSession(session)) { if (!user) {
return ( return (
<> <>
<div className={styles.container}> <div className={styles.container}>
@@ -57,9 +59,24 @@ export default async function EmployeeBenefitsCallToActions() {
) )
} }
// -- TODO [LOY-196] -- if (isEmployeeLinked(user)) {
// Handle case of authed user and already connected work account. return (
// Show member card modal. <div className={styles.container}>
<DigitalTeamMemberCard user={user}>
<TeamMemberCardTrigger
size="Medium"
variant="Tertiary"
color="Primary"
typography="Body/Paragraph/mdBold"
>
{intl.formatMessage({
defaultMessage: "Show Team Member Card",
})}
</TeamMemberCardTrigger>
</DigitalTeamMemberCard>
</div>
)
}
return ( return (
<div className={styles.container}> <div className={styles.container}>

View File

@@ -0,0 +1,16 @@
"use client"
import { type ComponentProps } from "react"
import { Button } from "@scandic-hotels/design-system/Button"
interface TeamMemberCardTriggerProps extends ComponentProps<typeof Button> {
children: React.ReactNode
}
export function TeamMemberCardTrigger({
children,
...props
}: TeamMemberCardTriggerProps) {
return <Button {...props}>{children}</Button>
}

View File

@@ -136,6 +136,7 @@ export default function Modal({
trigger, trigger,
isOpen, isOpen,
onToggle, onToggle,
onOpenChange,
title, title,
subtitle, subtitle,
children, children,
@@ -185,11 +186,12 @@ export default function Modal({
return ( return (
<DialogTrigger <DialogTrigger
onOpenChange={(isOpen) => onOpenChange={(isOpen) => {
setAnimation( setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
) )
} onOpenChange?.(isOpen)
}}
> >
{trigger} {trigger}
<AnimatePresence> <AnimatePresence>

View File

@@ -16,11 +16,17 @@ export type ModalProps = {
hideHeader?: boolean hideHeader?: boolean
className?: string className?: string
} & ( } & (
| { trigger: JSX.Element; isOpen?: never; onToggle?: never } | {
trigger: JSX.Element
isOpen?: never
onToggle?: never
onOpenChange?: (open: boolean) => void
}
| { | {
trigger?: never trigger?: never
isOpen: boolean isOpen: boolean
onToggle: (open: boolean) => void onToggle: (open: boolean) => void
onOpenChange?: never
} }
) )

View File

@@ -1,9 +1,7 @@
"use client" "use client"
import { useState } from "react"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
@@ -19,68 +17,56 @@ import type { User } from "@scandic-hotels/trpc/types/user"
interface DigitalTeamMemberCardClientProps { interface DigitalTeamMemberCardClientProps {
user: User user: User
children: React.ReactElement
} }
export default function DigitalTeamMemberCardClient({ export default function DigitalTeamMemberCardClient({
user, user,
children,
}: DigitalTeamMemberCardClientProps) { }: DigitalTeamMemberCardClientProps) {
const intl = useIntl() const intl = useIntl()
const [isOpen, setIsOpen] = useState(false)
const { release, request } = useWakeLock({ const { release, request } = useWakeLock({
reacquireOnPageVisible: true, reacquireOnPageVisible: true,
}) })
function onToggle(modalState: boolean) { function onOpenChange(isOpen: boolean) {
setIsOpen(modalState) if (isOpen) {
if (modalState) { request() // Acquire wake lock when modal opens
request()
} else { } else {
release() release() // Release wake lock when modal closes
} }
} }
return ( return (
<> <Modal
<Button trigger={children}
className={styles.button} className={styles.modal}
onPress={() => onToggle(true)} onOpenChange={onOpenChange}
variant="Tertiary" >
typography="Body/Paragraph/mdBold" <DigitalTeamMemberCardContent user={user} />
>
<span className={styles.text}>
{/* @ts-expect-error Icon is supported in font, just not in React Material Symbols package */}
<MaterialIcon icon="id_card" size={24} color="CurrentColor" />
{intl.formatMessage({
defaultMessage: "Show Team Member Card",
})}
</span>
</Button>
<Modal onToggle={onToggle} isOpen={isOpen} className={styles.modal}>
<DigitalTeamMemberCardContent user={user} />
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<p className={styles.footer}> <p className={styles.footer}>
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: defaultMessage:
"Book discounted stays for yourself, family and friends!", "Book discounted stays for yourself, family and friends!",
})} })}
</p> </p>
</Typography> </Typography>
<ButtonLink <ButtonLink
href="https://scandic.fuseuniversal.com/topics/73727" href="https://scandic.fuseuniversal.com/topics/73727"
target="_blank" target="_blank"
variant="Tertiary" variant="Tertiary"
typography="Body/Supporting text (caption)/smBold" typography="Body/Supporting text (caption)/smBold"
> >
<span className={styles.link}> <span className={styles.link}>
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "Check out all your benefits", defaultMessage: "Check out all your benefits",
})} })}
<MaterialIcon icon="open_in_new" size={20} color="CurrentColor" /> <MaterialIcon icon="open_in_new" size={20} color="CurrentColor" />
</span> </span>
</ButtonLink> </ButtonLink>
</Modal> </Modal>
</>
) )
} }

View File

@@ -1,20 +1,3 @@
.button {
border-radius: var(--Corner-radius-md);
color: var(--Text-Brand-OnPrimary-3-Accent);
&:focus,
&:not(:disabled):hover {
color: var(--Text-Brand-OnPrimary-3-Accent);
}
}
.text {
display: flex;
justify-content: center;
align-items: center;
gap: var(--Space-x1);
}
.title { .title {
color: var(--Text-Accent-Primary); color: var(--Text-Accent-Primary);
} }

View File

@@ -8,10 +8,12 @@ import type { User } from "@scandic-hotels/trpc/types/user"
interface DigitalTeamMemberCardProps { interface DigitalTeamMemberCardProps {
user: User user: User
children: React.ReactElement
} }
export default async function DigitalTeamMemberCard({ export default async function DigitalTeamMemberCard({
user, user,
children,
}: DigitalTeamMemberCardProps) { }: DigitalTeamMemberCardProps) {
if (!env.ENABLE_DTMC) { if (!env.ENABLE_DTMC) {
return null return null
@@ -21,5 +23,9 @@ export default async function DigitalTeamMemberCard({
if (!hasEmploymentData) { if (!hasEmploymentData) {
return null return null
} }
return <DigitalTeamMemberCardClient user={user} /> return (
<DigitalTeamMemberCardClient user={user}>
{children}
</DigitalTeamMemberCardClient>
)
} }

View File

@@ -1551,6 +1551,7 @@ export const SymbolCodepointsArray = [
'hvac', 'hvac',
'ice_skating', 'ice_skating',
'icecream', 'icecream',
'id_card',
'ifl', 'ifl',
'iframe', 'iframe',
'iframe_off', 'iframe_off',