feat (SW-2864): Move booking router to trpc package * Add env to trpc package * Add eslint to trpc package * Apply lint rules * Use direct imports from trpc package * Add lint-staged config to trpc * Move lang enum to common * Restructure trpc package folder structure * WIP first step * update internal imports in trpc * Fix most errors in scandic-web Just 100 left... * Move Props type out of trpc * Fix CategorizedFilters types * Move more schemas in hotel router * Fix deps * fix getNonContentstackUrls * Fix import error * Fix entry error handling * Fix generateMetadata metrics * Fix alertType enum * Fix duplicated types * lint:fix * Merge branch 'master' into feat/sw-2863-move-contentstack-router-to-trpc-package * Fix broken imports * Move booking router to trpc package * Merge branch 'master' into feat/sw-2864-move-hotels-router-to-trpc-package Approved-by: Linus Flood
172 lines
5.2 KiB
TypeScript
172 lines
5.2 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useRef, useState } from "react"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
|
|
import { debounce } from "@/utils/debounce"
|
|
|
|
import styles from "./digitalTeamMemberCard.module.css"
|
|
|
|
import type { User } from "@scandic-hotels/trpc/types/user"
|
|
|
|
interface DigitalTeamMemberCardCardProps {
|
|
user: User
|
|
}
|
|
|
|
export default function DigitalTeamMemberCardContent({
|
|
user,
|
|
}: DigitalTeamMemberCardCardProps) {
|
|
const intl = useIntl()
|
|
const cardRef = useRef<HTMLDivElement>(null)
|
|
const [isHovering, setIsHovering] = useState(false)
|
|
const [coords, setCoords] = useState({ x: 0, y: 0 })
|
|
const shimmerRef = useRef<HTMLDivElement>(null)
|
|
const [rect, setRect] = useState<DOMRect>({
|
|
top: 0,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
height: 0,
|
|
width: 0,
|
|
x: 0,
|
|
y: 0,
|
|
toJSON() {},
|
|
})
|
|
|
|
useEffect(() => {
|
|
const observer = new ResizeObserver(
|
|
debounce(() => {
|
|
const el = cardRef.current
|
|
if (!el) return
|
|
const rect = el.getBoundingClientRect()
|
|
setRect(rect)
|
|
})
|
|
)
|
|
observer.observe(document.body)
|
|
return () => {
|
|
observer.unobserve(document.body)
|
|
}
|
|
}, [])
|
|
|
|
function onInteractionMove(e: React.MouseEvent | React.TouchEvent) {
|
|
let x, y
|
|
if ("touches" in e) {
|
|
x = e.touches[0].clientX - rect.left
|
|
y = e.touches[0].clientY - rect.top
|
|
} else {
|
|
x = e.clientX - rect.left
|
|
y = e.clientY - rect.top
|
|
}
|
|
const centerX = rect.width / 2
|
|
const centerY = rect.height / 2
|
|
|
|
const rotateY = ((x - centerX) / centerX) * 8
|
|
const rotateX = ((centerY - y) / centerY) * 8
|
|
|
|
// Update shimmer position to be in the opposite corner
|
|
if (shimmerRef.current) {
|
|
// Calculate opposite position (invert percentage within bounds)
|
|
const oppositeX = 100 - (x / rect.width) * 100
|
|
const oppositeY = 100 - (y / rect.height) * 100
|
|
|
|
shimmerRef.current.style.background = `radial-gradient(
|
|
circle at ${oppositeX}% ${oppositeY}%,
|
|
rgba(233, 171, 163, 0.4) 0%,
|
|
rgba(255, 255, 255, 0) 50%
|
|
)`
|
|
}
|
|
setCoords({ x: rotateX, y: rotateY })
|
|
}
|
|
|
|
function onInteractionStart() {
|
|
setIsHovering(true)
|
|
}
|
|
|
|
function onInteractionEnd() {
|
|
setIsHovering(false)
|
|
setCoords({ x: 0, y: 0 })
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={styles.cardContainer}
|
|
ref={cardRef}
|
|
onTouchStart={onInteractionStart}
|
|
onTouchMove={onInteractionMove}
|
|
onTouchEnd={onInteractionEnd}
|
|
onMouseEnter={onInteractionStart}
|
|
onMouseMove={onInteractionMove}
|
|
onMouseLeave={onInteractionEnd}
|
|
>
|
|
<div
|
|
className={styles.card}
|
|
style={{
|
|
transform: `rotateX(${coords.x}deg) rotateY(${coords.y}deg)`,
|
|
}}
|
|
>
|
|
<div
|
|
className={styles.shimmer}
|
|
ref={shimmerRef}
|
|
style={{ opacity: isHovering ? 1 : 0 }}
|
|
/>
|
|
<div className={styles.content}>
|
|
<Typography variant="Tag/sm">
|
|
<div className={styles.top}>
|
|
<span>
|
|
{intl.formatMessage({ defaultMessage: "Team Member" })}
|
|
</span>
|
|
{/* TODO: Should display country of employment */}
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<span>SWE</span>
|
|
</div>
|
|
</Typography>
|
|
<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>
|
|
<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>
|
|
|
|
<Typography variant="Title/md">
|
|
<div>
|
|
{user.firstName} {user.lastName}
|
|
</div>
|
|
</Typography>
|
|
</div>
|
|
<Typography variant="Tag/sm">
|
|
<div className={styles.bottom}>
|
|
<span>
|
|
{/* TODO: Should display department of employment */}
|
|
{/* eslint-disable formatjs/no-literal-string-in-jsx */}
|
|
Haymarket by Scandic
|
|
{/* eslint-enable */}
|
|
</span>
|
|
{/* TODO: Should display current state of employment */}
|
|
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
<span>Employee</span>
|
|
</div>
|
|
</Typography>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|