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:
Anton Gunnarsson
2025-03-18 10:07:05 +00:00
parent d4fe8baa49
commit d0b6f3f8b3
32 changed files with 1799 additions and 12 deletions
@@ -0,0 +1,165 @@
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
import ArrowFromIcon from "@/components/Icons/ArrowFrom"
import ArrowToIcon from "@/components/Icons/ArrowTo"
import Image from "@/components/Image"
import SkeletonShimmer from "@/components/SkeletonShimmer"
import { getIntl } from "@/i18n"
import { TransferPointsFormClient } from "./TransferPointsFormClient"
import styles from "./transferPoints.module.css"
import type { Lang } from "@/constants/languages"
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
export async function TransferPointsForm({ lang }: { lang: Lang }) {
const profile = await getProfileSafely()
const scandicPoints = profile?.membership?.currentPoints ?? 0
// TODO get from api
await wait(1_000)
const sasPoints = 250_000
const exchangeRate = 2
return (
<TransferPointsFormContent
sasPoints={sasPoints}
scandicPoints={scandicPoints}
exchangeRate={exchangeRate}
lang={lang}
/>
)
}
export async function TransferPointsFormSkeleton({ lang }: { lang: Lang }) {
return (
<TransferPointsFormContent
sasPoints={null}
scandicPoints={null}
exchangeRate={null}
lang={lang}
/>
)
}
async function TransferPointsFormContent({
sasPoints,
scandicPoints,
exchangeRate,
lang,
}: {
sasPoints: number | null
scandicPoints: number | null
exchangeRate: number | null
lang: Lang
}) {
const intl = await getIntl()
return (
<div className={styles.container}>
<section className={styles.card}>
<div className={styles.highFive}>
<Image
src="/_static/img/scandic-high-five.svg"
alt=""
width="111"
height="139"
sizes="100vw"
/>
</div>
<div className={styles.transferContainer}>
<div className={styles.transferFrom}>
<div>
<div className={styles.labelWithIcon}>
<Typography variant="Tag/sm">
<p>{intl.formatMessage({ id: "Transfer from" })}</p>
</Typography>
<ArrowFromIcon />
</div>
<Typography variant="Title/Subtitle/md">
<p>{intl.formatMessage({ id: "SAS EuroBonus" })}</p>
</Typography>
</div>
<div>
<Typography variant="Tag/sm">
<p className={styles.balanceLabel}>
{intl.formatMessage({ id: "Balance" })}
</p>
</Typography>
{sasPoints === null ? (
<SkeletonShimmer width="10ch" height="20px" />
) : (
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage(
{ id: "{points, number} p" },
{ points: sasPoints }
)}
</p>
</Typography>
)}
</div>
</div>
<div className={styles.noPointsWarning}>
{sasPoints === 0 && (
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage({
id: "You have no points to transfer.",
})}
</p>
</Typography>
)}
</div>
<div className={styles.transferTo}>
<div>
<div className={styles.labelWithIcon}>
<ArrowToIcon />
<Typography variant="Tag/sm">
<p>{intl.formatMessage({ id: "Transfer to" })}</p>
</Typography>
</div>
<Typography variant="Title/Subtitle/md">
<p>{intl.formatMessage({ id: "Scandic Friends" })}</p>
</Typography>
</div>
<div>
<Typography variant="Tag/sm">
<p className={styles.balanceLabel}>
{intl.formatMessage({ id: "Balance" })}
</p>
</Typography>
{scandicPoints === null ? (
<SkeletonShimmer width="10ch" height="20px" />
) : (
<Typography variant="Body/Paragraph/mdRegular">
<p>
{intl.formatMessage(
{ id: "{points, number} p" },
{ points: scandicPoints }
)}
</p>
</Typography>
)}
</div>
</div>
</div>
<TransferPointsFormClient
sasPoints={sasPoints}
exchangeRate={exchangeRate}
lang={lang}
/>
</section>
<Typography variant="Body/Supporting text (caption)/smRegular">
<p style={{ color: "var(--Text-Tertiary)" }}>
{intl.formatMessage({
id: "Transferred points will not be level qualifying",
})}
</p>
</Typography>
</div>
)
}
@@ -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>
</>
)
}
@@ -0,0 +1,41 @@
import { Suspense } from "react"
import { env } from "@/env/server"
import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"
import { getLang } from "@/i18n/serverContext"
import {
TransferPointsForm,
TransferPointsFormSkeleton,
} from "./TransferPointsForm"
type Props = {
title?: string
link?: { href: string; text: string }
subtitle?: string
}
export default async function SASTransferPoints({
title,
subtitle,
link,
}: Props) {
if (!env.SAS_ENABLED) {
return null
}
const lang = getLang()
return (
<SectionContainer>
<SectionHeader link={link} preamble={subtitle} title={title} />
<SectionLink link={link} variant="mobile" />
<Suspense fallback={<TransferPointsFormSkeleton lang={lang} />}>
<TransferPointsForm lang={lang} />
</Suspense>
</SectionContainer>
)
}
@@ -0,0 +1,297 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--Spacing-x3);
}
.card {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
padding: var(--Spacing-x3) var(--Spacing-x9);
background-color: var(--Background-Secondary);
border-radius: var(--Corner-radius-Large);
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.1);
margin-top: var(--Spacing-x9);
}
.highFive {
height: 110px;
width: 110px;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
& > img {
position: absolute;
bottom: -5%;
width: 100%;
height: auto;
}
}
.labelWithIcon {
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
color: var(--Text-Tertiary);
margin-bottom: var(--Spacing-x-half);
}
.transferContainer {
display: grid;
grid-template-columns: 1fr auto 1fr;
}
.transferFrom {
display: flex;
flex-direction: column;
}
.transferTo {
display: flex;
flex-direction: column;
text-align: right;
.labelWithIcon {
justify-content: flex-end;
}
}
.noPointsWarning {
align-self: end;
color: var(--Surface-Feedback-Information-Accent);
}
.balanceLabel {
color: var(--Text-Tertiary);
margin-top: var(--Spacing-x3);
}
.formWrapper {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
width: 100%;
padding: var(--Spacing-x4);
padding-bottom: 0;
}
.slider {
color: var(--text-color);
width: 100%;
padding-top: 24px;
.sliderTrack {
position: relative;
height: 30px;
width: 100%;
&:before {
height: 10px;
width: 100%;
top: 50%;
transform: translateY(-50%);
content: "";
display: block;
position: absolute;
background-color: var(--Border-Divider-Accent);
border-radius: var(--Corner-radius-Small);
opacity: 0.3;
transition: background-color 300ms ease;
}
}
.sliderFill {
height: 10px;
background: linear-gradient(90deg, #8f4350 25.5%, #4d001b 100%);
position: relative;
top: 50%;
transform: translateY(-50%);
border-radius: var(--Corner-radius-Small);
}
.sliderOutput {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%) translateY(4px);
background-color: var(--Surface-Brand-Primary-1-OnSurface-Default);
color: var(--Text-Brand-OnPrimary-2-Accent);
padding: var(--Spacing-x-half) var(--Spacing-x1);
border-radius: var(--Corner-radius-Small);
opacity: 0;
transition:
opacity 0.15s,
transform 0.15s;
transition-delay: 0.1s;
}
.sliderThumb {
position: relative;
top: 50%;
width: 42px;
height: 42px;
border-radius: 50%;
background-color: var(--Surface-Brand-Primary-1-OnSurface-Default);
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
align-items: center;
transition: background-color 300ms ease;
& > svg {
width: 22px;
height: 22px;
transition: opacity 300ms ease;
}
&[data-focus-visible] {
outline: 2px solid var(--Surface-Feedback-Neutral);
}
&[data-dragging] .sliderOutput {
opacity: 1;
transform: translateX(-50%) translateY(0px);
}
}
&[data-disabled] {
.sliderTrack:before {
background-color: var(--Border-Interactive-Disabled);
}
.sliderThumb {
background-color: var(--Surface-UI-Fill-Disabled);
box-shadow: none;
& > svg {
opacity: 0;
}
}
}
}
.pointsInput {
width: 100%;
}
.conversionRate {
color: var(--Text-Tertiary);
font-style: italic;
}
.inputsWrapper {
display: grid;
gap: 36px;
width: 100%;
grid-template-columns: 1fr auto 1fr;
align-items: center;
}
.transferButton {
max-width: 260px;
width: 100%;
margin: 0 auto;
}
.modalContainer {
max-width: 512px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: var(--Spacing-x3);
padding-inline: var(--Spacing-x3);
}
.expiryText {
color: var(--Text-Tertiary);
}
.divider {
background-color: var(--Border-Divider-Subtle);
width: calc(100% + var(--Spacing-x6) + var(--Spacing-x6));
height: 1px;
margin-inline: calc(var(--Spacing-x6) * -1);
}
.buttonContainer {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
}
.pointsOutput {
border-radius: var(--Corner-radius-Medium);
background-color: var(--Surface-Primary-Disabled);
height: 100%;
color: var(--Text-Tertiary);
padding: var(--Spacing-x1) var(--Spacing-x2);
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
}
@media screen and (max-width: 767px) {
.card {
padding: var(--Spacing-x3);
padding-top: var(--Spacing-x6);
}
.highFive {
height: 75px;
width: 75px;
}
.transferContainer {
grid-template-columns: 1fr;
gap: var(--Spacing-x4);
}
.transferFrom {
flex-direction: row;
justify-content: space-between;
}
.transferTo {
flex-direction: row;
justify-content: space-between;
text-align: left;
.labelWithIcon {
flex-direction: row-reverse;
}
}
.noPointsWarning {
grid-row: 3;
}
.balanceLabel {
margin-top: 0;
text-align: right;
}
.inputsWrapper {
grid-template-columns: 1fr 1fr;
column-gap: var(--Spacing-x1);
}
.conversionRate {
grid-row: 2;
grid-column: span 2;
text-align: center;
}
.slider {
padding-top: 0;
}
.modalContainer {
padding-inline: var(--Spacing-x2);
}
}
@@ -12,6 +12,7 @@ import PointsOverview from "@/components/Blocks/DynamicContent/Points/Overview"
import CurrentRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/CurrentRewards"
import NextLevelRewardsBlock from "@/components/Blocks/DynamicContent/Rewards/NextLevel"
import SASLinkedAccount from "@/components/Blocks/DynamicContent/SAS/LinkedAccounts"
import SASTransferPoints from "@/components/Blocks/DynamicContent/SAS/TransferPoints"
import SASTierComparisonBlock from "@/components/Blocks/DynamicContent/SASTierComparison"
import SignupFormWrapper from "@/components/Blocks/DynamicContent/SignupFormWrapper"
import PreviousStays from "@/components/Blocks/DynamicContent/Stays/Previous"
@@ -67,6 +68,8 @@ function DynamicContentBlocks(props: DynamicContentProps) {
return <UpcomingStays {...dynamic_content} />
case DynamicContentEnum.Blocks.components.sas_linked_account:
return <SASLinkedAccount {...dynamic_content} />
case DynamicContentEnum.Blocks.components.sas_transfer_points:
return <SASTransferPoints {...dynamic_content} />
case DynamicContentEnum.Blocks.components.sas_tier_comparison:
return (
<SASTierComparisonBlock
@@ -0,0 +1,37 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ArrowFromIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 5V15M12 5C11.2998 5 9.99153 6.9943 9.5 7.5M12 5C12.7002 5 14.0085 6.9943 14.5 7.5"
stroke="black"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 19H19.0001"
stroke="black"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
@@ -0,0 +1,33 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function ArrowToIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
{...props}
>
<path
d="M5 5H19.0001"
stroke="black"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 9V19M12 9C11.2998 9 9.99153 10.9943 9.5 11.5M12 9C12.7002 9 14.0085 10.9943 14.5 11.5"
stroke="black"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
@@ -0,0 +1,36 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function SwipeIcon({ className, color, ...props }: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
id="mask0_4743_35236"
style={{ maskType: "alpha" }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_4743_35236)">
<path
d="M11.825 22C11.425 22 11.0417 21.925 10.675 21.775C10.3083 21.625 9.98333 21.4083 9.7 21.125L4.6 16L5.35 15.225C5.61667 14.9583 5.92917 14.7792 6.2875 14.6875C6.64583 14.5958 7 14.6 7.35 14.7L9 15.175V7C9 6.71667 9.09583 6.47917 9.2875 6.2875C9.47917 6.09583 9.71667 6 10 6C10.2833 6 10.5208 6.09583 10.7125 6.2875C10.9042 6.47917 11 6.71667 11 7V17.825L8.575 17.15L11.125 19.7C11.2083 19.7833 11.3125 19.8542 11.4375 19.9125C11.5625 19.9708 11.6917 20 11.825 20H16C16.55 20 17.0208 19.8042 17.4125 19.4125C17.8042 19.0208 18 18.55 18 18V14C18 13.7167 18.0958 13.4792 18.2875 13.2875C18.4792 13.0958 18.7167 13 19 13C19.2833 13 19.5208 13.0958 19.7125 13.2875C19.9042 13.4792 20 13.7167 20 14V18C20 19.1 19.6083 20.0417 18.825 20.825C18.0417 21.6083 17.1 22 16 22H11.825ZM12 15V11C12 10.7167 12.0958 10.4792 12.2875 10.2875C12.4792 10.0958 12.7167 10 13 10C13.2833 10 13.5208 10.0958 13.7125 10.2875C13.9042 10.4792 14 10.7167 14 11V15H12ZM15 15V12C15 11.7167 15.0958 11.4792 15.2875 11.2875C15.4792 11.0958 15.7167 11 16 11C16.2833 11 16.5208 11.0958 16.7125 11.2875C16.9042 11.4792 17 11.7167 17 12V15H15ZM22 7H17V5.5H19.9C18.8 4.53333 17.575 3.79167 16.225 3.275C14.875 2.75833 13.4667 2.5 12 2.5C10.5333 2.5 9.125 2.75833 7.775 3.275C6.425 3.79167 5.2 4.53333 4.1 5.5H7V7H2V2H3.5V4.025C4.7 3.04167 6.025 2.29167 7.475 1.775C8.925 1.25833 10.4333 1 12 1C13.5667 1 15.075 1.25833 16.525 1.775C17.975 2.29167 19.3 3.04167 20.5 4.025V2H22V7Z"
fill="white"
/>
</g>
</svg>
)
}
@@ -4,7 +4,9 @@ import {
AccountCircleIcon,
AirIcon,
AirplaneIcon,
ArrowFromIcon,
ArrowRightIcon,
ArrowToIcon,
BarIcon,
BedIcon,
BikingIcon,
@@ -106,6 +108,7 @@ import {
StarFilledIcon,
StreetIcon,
SwimIcon,
SwipeIcon,
ThermostatIcon,
TrainIcon,
TripAdvisorIcon,
@@ -135,8 +138,12 @@ export function getIconByIconName(
return AirIcon
case IconName.Airplane:
return AirplaneIcon
case IconName.ArrowFrom:
return ArrowFromIcon
case IconName.ArrowRight:
return ArrowRightIcon
case IconName.ArrowTo:
return ArrowToIcon
case IconName.Bar:
return BarIcon
case IconName.Bed:
@@ -339,6 +346,8 @@ export function getIconByIconName(
return StreetIcon
case IconName.Swim:
return SwimIcon
case IconName.Swipe:
return SwipeIcon
case IconName.Thermostat:
return ThermostatIcon
case IconName.Tshirt:
@@ -5,7 +5,9 @@ export { default as AccountCircleIcon } from "./AccountCircle"
export { default as AirIcon } from "./Air"
export { default as AirplaneIcon } from "./Airplane"
export { default as AllergyIcon } from "./Allergy"
export { default as ArrowFromIcon } from "./ArrowFrom"
export { default as ArrowRightIcon } from "./ArrowRight"
export { default as ArrowToIcon } from "./ArrowTo"
export { default as ArrowUpIcon } from "./ArrowUp"
export { default as BalconyIcon } from "./Balcony"
export { default as BarIcon } from "./Bar"
@@ -170,6 +172,7 @@ export { default as StoreIcon } from "./Store"
export { default as StreetIcon } from "./Street"
export { default as SurpriseIcon } from "./Surprise"
export { default as SwimIcon } from "./Swim"
export { default as SwipeIcon } from "./Swipe"
export { default as ThermostatIcon } from "./Thermostat"
export { default as TrainIcon } from "./Train"
export { default as TripAdvisorIcon } from "./TripAdvisor"
+1 -1
View File
@@ -19,7 +19,7 @@ export type ModalProps = {
| {
trigger?: never
isOpen: boolean
onToggle: Dispatch<SetStateAction<boolean>>
onToggle: (open: boolean) => void
}
)
@@ -1,3 +1,4 @@
import { cx } from "class-variance-authority"
import { type ForwardedRef, forwardRef, useId } from "react"
import { Input as AriaInput, Label as AriaLabel } from "react-aria-components"
@@ -18,7 +19,12 @@ const AriaInputWithLabel = forwardRef(function AriaInputWithLabelComponent(
return (
<AriaLabel className={styles.container} htmlFor={inputId}>
<Body asChild fontOnly>
<AriaInput {...props} className={styles.input} ref={ref} id={inputId} />
<AriaInput
{...props}
className={cx(styles.input, props.className)}
ref={ref}
id={inputId}
/>
</Body>
<Label required={!!props.required}>{label}</Label>
</AriaLabel>
@@ -34,6 +34,7 @@ export default function Input({
evt.currentTarget.blur()
}
}
return (
<Controller
disabled={disabled}