Merged in feat/LOY-197-screen-wake-dtmc (pull request #1996)

feat(LOY-197): add screen wake lock to dtmc

Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
Christian Andolf
2025-05-07 13:18:19 +00:00
2 changed files with 171 additions and 85 deletions

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import { useState } from "react"
import { Button } from "react-aria-components" import { Button } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
@@ -7,6 +8,7 @@ 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"
import Modal from "@/components/Modal" import Modal from "@/components/Modal"
import useWakeLock from "@/hooks/useWakeLock"
import styles from "./digitalTeamMemberCard.module.css" import styles from "./digitalTeamMemberCard.module.css"
@@ -20,97 +22,109 @@ export default function DigitalTeamMemberCardClient({
user, user,
}: DigitalTeamMemberCardClientProps) { }: DigitalTeamMemberCardClientProps) {
const intl = useIntl() const intl = useIntl()
const [isOpen, setIsOpen] = useState(false)
const { release, request } = useWakeLock({
reacquireOnPageVisible: true,
})
function onToggle(modalState: boolean) {
setIsOpen(modalState)
if (modalState) {
request()
} else {
release()
}
}
return ( return (
<Modal <>
trigger={ <Button className={styles.button} onPress={() => onToggle(true)}>
<Button className={styles.button}> <Typography variant="Body/Paragraph/mdBold">
<Typography variant="Body/Paragraph/mdBold"> <span className={styles.text}>
<span className={styles.text}> {/* @ts-expect-error Icon is supported in font, just not in React Material Symbols package */}
{/* @ts-expect-error Icon is supported in font, just not in React Material Symbols package */} <MaterialIcon icon="id_card" size={24} color="CurrentColor" />
<MaterialIcon icon="id_card" size={24} color="CurrentColor" /> {intl.formatMessage({
{intl.formatMessage({ defaultMessage: "Show Team Member Card",
defaultMessage: "Show Team Member Card", })}
})} </span>
</span> </Typography>
</Typography> </Button>
</Button> <Modal onToggle={onToggle} isOpen={isOpen} className={styles.modal}>
} <Typography variant="Title/xs">
className={styles.modal} <h2 className={styles.title}>
> {intl.formatMessage({ defaultMessage: "Scandic Family" })}
<Typography variant="Title/xs"> </h2>
<h2 className={styles.title}> </Typography>
{intl.formatMessage({ defaultMessage: "Scandic Family" })} <div className={styles.card}>
</h2> <div className={styles.content}>
</Typography> <div className={styles.top}>
<div className={styles.card}> <Typography variant="Tag/sm">
<div className={styles.content}> <span>
<div className={styles.top}> {intl.formatMessage({ defaultMessage: "Team Member" })}
<Typography variant="Tag/sm"> </span>
<span> </Typography>
{intl.formatMessage({ defaultMessage: "Team Member" })} <Typography variant="Tag/sm">
</span> {/* TODO: Should display country of employment */}
</Typography> {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<Typography variant="Tag/sm"> <span>SWE</span>
{/* TODO: Should display country of employment */}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>SWE</span>
</Typography>
</div>
<div className={styles.middle}>
<div className={styles.employeeNumber}>
<Typography variant="Title/sm">
{/* TODO: Should display employee number */}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<div>123 456</div>
</Typography> </Typography>
<svg
width="42"
height="42"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={styles.icon}
>
<path
d="M3.5 35V12.288C3.5 8.933 3.5 7 4.268 7c.912 0 1.569 1.156 3.17 3.978l11.374 20.044C20.413 33.844 21.041 35 21.982 35c.768 0 .768-1.933.768-5.288V7M28 22.75h10.5M29.016 8.016c1.355-1.355 7.113-1.355 8.468 0 1.355 1.355 1.355 7.113 0 8.468-1.355 1.355-7.114 1.355-8.468 0-1.355-1.355-1.355-7.113 0-8.468Z"
strokeWidth="2.625"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div> </div>
<div className={styles.middle}>
<Typography variant="Title/md"> <div className={styles.employeeNumber}>
<div> <Typography variant="Title/sm">
{user.firstName} {user.lastName} {/* TODO: Should display employee number */}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<div>123 456</div>
</Typography>
<svg
width="42"
height="42"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={styles.icon}
>
<path
d="M3.5 35V12.288C3.5 8.933 3.5 7 4.268 7c.912 0 1.569 1.156 3.17 3.978l11.374 20.044C20.413 33.844 21.041 35 21.982 35c.768 0 .768-1.933.768-5.288V7M28 22.75h10.5M29.016 8.016c1.355-1.355 7.113-1.355 8.468 0 1.355 1.355 1.355 7.113 0 8.468-1.355 1.355-7.114 1.355-8.468 0-1.355-1.355-1.355-7.113 0-8.468Z"
strokeWidth="2.625"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div> </div>
</Typography>
</div> <Typography variant="Title/md">
<div className={styles.bottom}> <div>
<Typography variant="Tag/sm"> {user.firstName} {user.lastName}
<span> </div>
{/* TODO: Should display department of employment */} </Typography>
{/* eslint-disable formatjs/no-literal-string-in-jsx */} </div>
Haymarket by Scandic <div className={styles.bottom}>
{/* eslint-enable */} <Typography variant="Tag/sm">
</span> <span>
</Typography> {/* TODO: Should display department of employment */}
<Typography variant="Tag/sm"> {/* eslint-disable formatjs/no-literal-string-in-jsx */}
{/* TODO: Should display current state of employment */} Haymarket by Scandic
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */} {/* eslint-enable */}
<span>Employee</span> </span>
</Typography> </Typography>
<Typography variant="Tag/sm">
{/* TODO: Should display current state of employment */}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<span>Employee</span>
</Typography>
</div>
</div> </div>
</div> </div>
</div> <Typography variant="Body/Paragraph/mdRegular">
<Typography variant="Body/Paragraph/mdRegular"> <div className={styles.footer}>
<div 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!", })}
})} </div>
</div> </Typography>
</Typography> </Modal>
</Modal> </>
) )
} }

View File

@@ -0,0 +1,72 @@
"use client"
import { useCallback, useEffect, useRef } from "react"
let isSupported = false
if ("wakeLock" in navigator) {
isSupported = true
}
interface Options {
reacquireOnPageVisible?: boolean
onRequest?: () => void
onRelease?: () => void
}
/**
* Uses the Wake Lock API to keep the screen from dimming or locking.
* https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
*/
export default function useWakeLock({
reacquireOnPageVisible = false,
onRequest = () => undefined,
onRelease = () => undefined,
}: Options = {}) {
const wakeLock = useRef<WakeLockSentinel | null>(null)
const request = useCallback(async () => {
if (!isSupported) {
return
}
try {
wakeLock.current = await navigator.wakeLock.request("screen")
onRequest()
wakeLock.current.onrelease = () => {
onRelease()
}
} catch (_err) {
// ignore
}
}, [onRelease, onRequest])
const release = useCallback(() => {
if (!isSupported) {
return
}
wakeLock.current?.release().then(() => {
wakeLock.current = null
})
}, [])
useEffect(() => {
const handleVisibilityChange = () => {
if (wakeLock.current !== null && document.visibilityState === "visible") {
request()
}
}
if (reacquireOnPageVisible) {
document.addEventListener("visibilitychange", handleVisibilityChange)
}
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange)
}
}, [reacquireOnPageVisible, request])
return { isSupported, request, release }
}