feat(SW-3659): Use new input component * Use new input component * Update error formatter * Merged master into feat/use-new-input-component * Merged master into feat/use-new-input-component * Merge branch 'master' into feat/use-new-input-component * Merged master into feat/use-new-input-component * Update Input stories * Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component * Update Storybook logo * Add some new demo icon input story * Fix the clear content button position * Fix broken password input icon * Merged master into feat/use-new-input-component * Merged master into feat/use-new-input-component * Add aria-hidden to required asterisk * Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component * Merge branch 'master' into feat/use-new-input-component Approved-by: Bianca Widstam Approved-by: Matilda Landström
280 lines
8.7 KiB
TypeScript
280 lines
8.7 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 { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
|
import Image from "@scandic-hotels/design-system/Image"
|
|
import Modal from "@scandic-hotels/design-system/Modal"
|
|
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
|
import { SAS_TRANSFER_POINT_KEY } from "@scandic-hotels/trpc/constants/partnerSAS"
|
|
|
|
import styles from "./transferPoints.module.css"
|
|
|
|
import type { Lang } from "@scandic-hotels/common/constants/language"
|
|
|
|
import type { LangParams } from "@/types/params"
|
|
|
|
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 ? Math.min(Math.max(points, 0), sasPoints ?? 0) : null)
|
|
}
|
|
|
|
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: "partnerSas.ebPointsToExchange",
|
|
defaultMessage: "EB points to exchange",
|
|
})}
|
|
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}>
|
|
<FormInput
|
|
name="ebPointsToExchange"
|
|
label={intl.formatMessage({
|
|
id: "partnerSas.ebPointsToExchange",
|
|
defaultMessage: "EB points to exchange",
|
|
})}
|
|
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: "partnerSas.conversionRateInfo",
|
|
defaultMessage: "1 EuroBonus point = 2 Scandic Friends points",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<div className={styles.pointsOutput}>
|
|
<Typography variant="Label/xsRegular">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "partnerSas.sfPointsToReceive",
|
|
defaultMessage: "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: "partnerSas.exchangePoints",
|
|
defaultMessage: "Exchange 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: "partnerSas.proceedWithPointExchange",
|
|
defaultMessage: "Proceed with point exchange?",
|
|
})}
|
|
</h3>
|
|
</Typography>
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage({
|
|
id: "partnerSas.youAreAboutToExchange",
|
|
defaultMessage: "You are about to exchange:",
|
|
})}
|
|
</p>
|
|
</Typography>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>
|
|
{intl.formatMessage(
|
|
{
|
|
id: "partnerSas.exchangePointsDetails",
|
|
defaultMessage:
|
|
"<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: "partnerSas.exchangePointsExpiry",
|
|
defaultMessage:
|
|
"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: "partnerSas.yesIWantToExchangeMyPoints",
|
|
defaultMessage: "Yes, I want to exchange my points",
|
|
})}
|
|
</Link>
|
|
</Button>
|
|
<Button
|
|
fullWidth
|
|
intent="text"
|
|
theme="base"
|
|
onClick={() => handleToggle(false)}
|
|
>
|
|
{intl.formatMessage({
|
|
id: "common.cancel",
|
|
defaultMessage: "Cancel",
|
|
})}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</>
|
|
)
|
|
}
|