Files
web/apps/scandic-web/components/Blocks/DynamicContent/SAS/TransferPoints/TransferPointsFormClient.tsx
Matilda Landström 1239f0c662 Merged in feat/SW-1711-SW-2077-icons (pull request #1709)
Fix(SW-1711)/(SW-2077): Export icons individually

* fix(SW-1711): export icons individually


Approved-by: Michael Zetterberg
Approved-by: Erik Tiekstra
2025-04-07 07:25:25 +00:00

248 lines
7.6 KiB
TypeScript

"use client"
import Link from "next/link"
import { useParams } from "next/navigation"
import { useContext, useState } from "react"
import {
I18nProvider,
Slider,
SliderOutput,
SliderStateContext,
SliderThumb,
SliderTrack,
TextField,
} from "react-aria-components"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { SAS_TRANSFER_POINT_KEY } from "@/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/sasUtils"
import Image from "@/components/Image"
import Modal from "@/components/Modal"
import Button from "@/components/TempDesignSystem/Button"
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
import styles from "./transferPoints.module.css"
import type { LangParams } from "@/types/params"
import type { Lang } from "@/constants/languages"
type TransferPointsFormClientProps = {
sasPoints: number | null
exchangeRate: number | null
lang: Lang
}
export function TransferPointsFormClient({
sasPoints,
exchangeRate,
lang,
}: TransferPointsFormClientProps) {
const intl = useIntl()
const formMethods = useForm()
const [pointState, setPointState] = useState<number | null>(0)
const selectedPoints = pointState ?? 0
const disabled = !exchangeRate
const parsedPoints = Math.min(selectedPoints, sasPoints ?? 0)
const calculatedPoints = parsedPoints * (exchangeRate ?? 0)
const handleUpdatePoints = (points: number | null) => {
setPointState(points)
}
const hasNoSasPoints = !sasPoints || sasPoints === 0
return (
<FormProvider {...formMethods}>
<I18nProvider locale={lang}>
<Slider
value={parsedPoints}
onChange={handleUpdatePoints}
className={styles.slider}
// Set max value to 1 if sasPoints is 0 since slider requires a range
maxValue={hasNoSasPoints ? 1 : sasPoints}
aria-label={intl.formatMessage({ id: "EB points to transfer" })}
formatOptions={{
useGrouping: true,
maximumFractionDigits: 0,
}}
isDisabled={disabled || hasNoSasPoints}
>
<SliderTrack className={styles.sliderTrack}>
<SliderFill />
<SliderThumb className={styles.sliderThumb}>
<SliderOutput className={styles.sliderOutput} />
<MaterialIcon icon="swipe" color="Icon/Inverted" />
</SliderThumb>
</SliderTrack>
</Slider>
</I18nProvider>
<div className={styles.inputsWrapper}>
<TextField type="number" isDisabled={disabled}>
<AriaInputWithLabel
label={intl.formatMessage({ id: "EB points to transfer" })}
type="number"
min={0}
value={pointState ?? ""}
className={styles.pointsInput}
disabled={disabled}
onChange={(e) => {
const value = parseInt(e.target.value, 10)
handleUpdatePoints(isNaN(value) ? null : value)
}}
onBlur={() => {
handleUpdatePoints(parsedPoints)
}}
/>
</TextField>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.conversionRate}>
{/* TODO maybe dynamic string based on exchange rate */}
{intl.formatMessage({
id: "1 EuroBonus point = 2 Scandic Friends points",
})}
</p>
</Typography>
<div className={styles.pointsOutput}>
<Typography variant="Label/xsRegular">
<p>{intl.formatMessage({ id: "SF points to receive" })}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>{intl.formatNumber(calculatedPoints)}</p>
</Typography>
</div>
</div>
<ConfirmModal
disabled={disabled || calculatedPoints === 0}
sasPoints={parsedPoints}
scandicPoints={calculatedPoints}
/>
</FormProvider>
)
}
function SliderFill() {
const state = useContext(SliderStateContext)!
return (
<div
style={{
width: `${state.getThumbPercent(0) * 100}%`,
}}
className={styles.sliderFill}
/>
)
}
type ConfirmModalProps = {
sasPoints: number
scandicPoints: number
disabled?: boolean
}
function ConfirmModal({
sasPoints,
scandicPoints,
disabled,
}: ConfirmModalProps) {
const { lang } = useParams<LangParams>()
const [isOpen, setIsOpen] = useState(false)
const intl = useIntl()
const handleToggle = (open: boolean) => {
setIsOpen(open)
if (open) {
const expireIn15Minutes = new Date(
Date.now() + 15 * 60 * 1000
).toUTCString()
document.cookie = `${SAS_TRANSFER_POINT_KEY}=${JSON.stringify(sasPoints)};path=/;expires=${expireIn15Minutes}`
} else {
document.cookie = `${SAS_TRANSFER_POINT_KEY}=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT`
}
}
return (
<>
<Button
className={styles.transferButton}
onClick={() => handleToggle(true)}
disabled={disabled}
>
{intl.formatMessage({ id: "Transfer points" })}
</Button>
<Modal isOpen={isOpen} onToggle={handleToggle}>
<div className={styles.modalContainer}>
<Image
src="/_static/img/scandic-money-hand.svg"
alt=""
width="133"
height="119"
/>
<Typography variant="Title/Subtitle/lg">
<h3>
{intl.formatMessage({ id: "Proceed with point transfer?" })}
</h3>
</Typography>
<div>
<Typography variant="Body/Paragraph/mdRegular">
<p>{intl.formatMessage({ id: "You are about to exchange:" })}</p>
</Typography>
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage(
{
id: "<bold>{sasPoints, number} EuroBonus points</bold> to <bold>{scandicPoints, number} Scandic Friends points</bold>",
},
{
sasPoints,
scandicPoints,
bold: (text) => (
<Typography variant="Body/Paragraph/mdBold">
<span>{text}</span>
</Typography>
),
}
)}
</p>
</Typography>
</div>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p className={styles.expiryText}>
{intl.formatMessage({
id: "Your exchanged points will retain their original expiry date with a maximum validity of 12 months.",
})}
</p>
</Typography>
<div className={styles.divider} />
<div className={styles.buttonContainer}>
<Button asChild theme="base" fullWidth>
<Link
href={`/${lang}/sas-x-scandic/login?intent=transfer`}
color="none"
>
{intl.formatMessage({
id: "Yes, I want to transfer my points",
})}
</Link>
</Button>
<Button
fullWidth
intent="text"
theme="base"
onClick={() => handleToggle(false)}
>
{intl.formatMessage({ id: "Cancel" })}
</Button>
</div>
</div>
</Modal>
</>
)
}