Merged in feat/sw-1314-transfer-sas-points (pull request #1508)
SW-1314 Transfer SAS points Approved-by: Linus Flood
This commit is contained in:
+247
@@ -0,0 +1,247 @@
|
||||
"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 { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { SAS_TRANSFER_POINT_KEY } from "@/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/sasUtils"
|
||||
import SwipeIcon from "@/components/Icons/Swipe"
|
||||
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} />
|
||||
<SwipeIcon color="white" />
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user