Merged in chore/SW-3145-move-phone (pull request #2549)

chore/SW-3145 Moved Phone to design system

* chore/SW-3145 Moved Phone to design system

* chore: SW-3145 Moved phone and removed intl direct dependency


Approved-by: Anton Gunnarsson
This commit is contained in:
Hrishikesh Vaipurkar
2025-07-28 08:28:07 +00:00
parent 58af469e22
commit 42ab6e58b3
12 changed files with 130 additions and 81 deletions

View File

@@ -4,24 +4,27 @@ import { useIntl } from "react-intl"
import { Divider } from "@scandic-hotels/design-system/Divider"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { Select } from "@scandic-hotels/design-system/Select"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getLocalizedLanguageOptions } from "@/constants/languages"
import {
getDefaultCountryFromLang,
getLocalizedLanguageOptions,
} from "@/constants/languages"
import DateSelect from "@/components/TempDesignSystem/Form/Date"
import Input from "@/components/TempDesignSystem/Form/Input"
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import useLang from "@/hooks/useLang"
import { getFormattedCountryList } from "@/utils/countries"
import { getErrorMessage } from "@/utils/getErrorMessage"
import { editProfileErrors } from "../schema"
import styles from "./formContent.module.css"
export default function FormContent() {
import type { FieldErrors } from "react-hook-form"
export default function FormContent({ errors }: { errors: FieldErrors }) {
const intl = useIntl()
const lang = useLang()
const languageOptions = getLocalizedLanguageOptions(lang)
@@ -66,7 +69,8 @@ export default function FormContent() {
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
editProfileErrors.COUNTRY_REQUIRED
// @ts-expect-error countryCode doesn't exist schema not available
errors?.address?.countryCode?.message?.toString()
)}
label={intl.formatMessage({
defaultMessage: "Country",
@@ -86,6 +90,15 @@ export default function FormContent() {
data-hj-suppress
/>
<Phone
countryLabel={intl.formatMessage({
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
errors?.phoneNumber?.message?.toString()
)}
label={intl.formatMessage({
defaultMessage: "Phone number",
})}

View File

@@ -185,7 +185,7 @@ export default function Form({ user }: EditFormProps) {
onSubmit={methods.handleSubmit(handleSubmit)}
>
<FormProvider {...methods}>
<FormContent />
<FormContent errors={methods.formState.errors} />
</FormProvider>
</form>
</section>

View File

@@ -10,6 +10,7 @@ import { logger } from "@scandic-hotels/common/logger"
import { Button } from "@scandic-hotels/design-system/Button"
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trpc } from "@scandic-hotels/trpc/client"
import {
@@ -27,7 +28,6 @@ import {
import DateSelect from "@/components/TempDesignSystem/Form/Date"
import Input from "@/components/TempDesignSystem/Form/Input"
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Link from "@/components/TempDesignSystem/Link"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { useFormTracking } from "@/components/TrackingSDK/hooks"
@@ -98,7 +98,11 @@ export default function SignupForm({ title }: SignUpFormProps) {
shouldFocusError: true,
})
const { control, subscribe } = methods
const {
control,
subscribe,
formState: { errors },
} = methods
const { trackFormSubmit } = useFormTracking("signup", subscribe, control)
@@ -174,7 +178,7 @@ export default function SignupForm({ title }: SignUpFormProps) {
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
signupErrors.COUNTRY_REQUIRED
errors.address?.countryCode?.message
)}
label={intl.formatMessage({
defaultMessage: "Country",
@@ -192,6 +196,15 @@ export default function SignupForm({ title }: SignUpFormProps) {
type="email"
/>
<Phone
countryLabel={intl.formatMessage({
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
errors.phoneNumber?.message
)}
label={intl.formatMessage({
defaultMessage: "Phone number",
})}

View File

@@ -6,12 +6,13 @@ import { useIntl } from "react-intl"
import Footnote from "@scandic-hotels/design-system/Footnote"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { getDefaultCountryFromLang } from "@/constants/languages"
import { useEnterDetailsStore } from "@/stores/enter-details"
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import { useFormTracking } from "@/components/TrackingSDK/hooks"
import { useRoomContext } from "@/contexts/Details/Room"
import useLang from "@/hooks/useLang"
@@ -21,7 +22,7 @@ import { getErrorMessage } from "@/utils/getErrorMessage"
import MemberPriceModal from "../MemberPriceModal"
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
import { getMultiroomDetailsSchema, multiroomErrors } from "./schema"
import { getMultiroomDetailsSchema } from "./schema"
import styles from "./details.module.css"
@@ -89,7 +90,7 @@ export default function Details() {
trigger,
control,
subscribe,
formState: { isValid },
formState: { isValid, errors },
setValue,
watch,
} = methods
@@ -192,10 +193,7 @@ export default function Details() {
<CountrySelect
className={styles.fullWidth}
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
multiroomErrors.COUNTRY_REQUIRED
)}
errorMessage={getErrorMessage(intl, errors.countryCode?.message)}
label={intl.formatMessage({
defaultMessage: "Country",
})}
@@ -212,6 +210,12 @@ export default function Details() {
registerOptions={{ required: true, onBlur: updateDetailsStore }}
/>
<Phone
countryLabel={intl.formatMessage({
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(intl, errors.phoneNumber?.message)}
className={styles.fullWidth}
label={intl.formatMessage({
defaultMessage: "Phone number",

View File

@@ -6,12 +6,13 @@ import { useIntl } from "react-intl"
import Footnote from "@scandic-hotels/design-system/Footnote"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { getDefaultCountryFromLang } from "@/constants/languages"
import { useEnterDetailsStore } from "@/stores/enter-details"
import SpecialRequests from "@/components/HotelReservation/EnterDetails/Details/SpecialRequests"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import { useFormTracking } from "@/components/TrackingSDK/hooks"
import { useRoomContext } from "@/contexts/Details/Room"
import useLang from "@/hooks/useLang"
@@ -21,11 +22,7 @@ import { getErrorMessage } from "@/utils/getErrorMessage"
import MemberPriceModal from "../MemberPriceModal"
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
import {
guestDetailsSchema,
roomOneErrors,
signedInDetailsSchema,
} from "./schema"
import { guestDetailsSchema, signedInDetailsSchema } from "./schema"
import Signup from "./Signup"
import styles from "./details.module.css"
@@ -178,7 +175,10 @@ export default function Details({ user }: DetailsProps) {
})}
lang={lang}
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(intl, roomOneErrors.COUNTRY_REQUIRED)}
errorMessage={getErrorMessage(
intl,
formState.errors.countryCode?.message
)}
name="countryCode"
readOnly={!!user}
registerOptions={{ required: true, onBlur: updateDetailsStore }}
@@ -195,6 +195,15 @@ export default function Details({ user }: DetailsProps) {
/>
<Phone
className={styles.fullWidth}
countryLabel={intl.formatMessage({
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
formState.errors.phoneNumber?.message
)}
label={intl.formatMessage({
defaultMessage: "Phone number",
})}

View File

@@ -5,10 +5,11 @@ import { useIntl } from "react-intl"
import Body from "@scandic-hotels/design-system/Body"
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
import { signupErrors } from "@scandic-hotels/trpc/routers/user/schemas"
import Phone from "@scandic-hotels/design-system/Form/Phone"
import { getDefaultCountryFromLang } from "@/constants/languages"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import useLang from "@/hooks/useLang"
import { getFormattedCountryList } from "@/utils/countries"
import { getErrorMessage } from "@/utils/getErrorMessage"
@@ -28,7 +29,11 @@ export default function ModifyContact({
}: ModifyContactProps) {
const intl = useIntl()
const lang = useLang()
const { getValues, setValue } = useFormContext()
const {
getValues,
setValue,
formState: { errors },
} = useFormContext()
useEffect(() => {
setValue("firstName", guest.firstName ?? "")
@@ -65,7 +70,7 @@ export default function ModifyContact({
countries={getFormattedCountryList(intl)}
errorMessage={getErrorMessage(
intl,
signupErrors.COUNTRY_REQUIRED
errors.countryCode?.message?.toString()
)}
label={intl.formatMessage({
defaultMessage: "Country",
@@ -86,6 +91,15 @@ export default function ModifyContact({
</div>
<div className={styles.row}>
<Phone
countryLabel={intl.formatMessage({
defaultMessage: "Country code",
})}
countriesWithTranslatedName={getFormattedCountryList(intl)}
defaultCountryCode={getDefaultCountryFromLang(lang)}
errorMessage={getErrorMessage(
intl,
errors.phoneNumber?.message?.toString()
)}
label={intl.formatMessage({
defaultMessage: "Phone number",
})}

View File

@@ -1,7 +1,7 @@
import { Lang } from "@scandic-hotels/common/constants/language"
import { ApiLang } from "@scandic-hotels/trpc/constants/apiLang"
import type { LowerCaseCountryCode } from "@/types/components/form/phone"
import type { CountryCode } from "libphonenumber-js"
export const languages: Record<Lang, string> = {
[Lang.da]: "Dansk",
@@ -82,8 +82,8 @@ export function getLocalizedLanguageOptions(currentLang: Lang) {
})
}
export function getDefaultCountryFromLang(lang: Lang): LowerCaseCountryCode {
const countryMap: Record<Lang, LowerCaseCountryCode> = {
export function getDefaultCountryFromLang(lang: Lang): Lowercase<CountryCode> {
const countryMap: Record<Lang, Lowercase<CountryCode>> = {
sv: "se",
da: "dk",
fi: "fi",

View File

@@ -1,9 +1,9 @@
"use client"
import "react-international-phone/style.css"
'use client'
import 'react-international-phone/style.css'
import { useEffect, useMemo } from "react"
import { TextField } from "react-aria-components"
import { useFormContext, useWatch } from "react-hook-form"
import { useEffect, useMemo } from 'react'
import { TextField } from 'react-aria-components'
import { useFormContext, useWatch } from 'react-hook-form'
import {
buildCountryData,
CountrySelector,
@@ -12,53 +12,48 @@ import {
parseCountry,
type ParsedCountry,
usePhoneInput,
} from "react-international-phone"
import { useIntl } from "react-intl"
} from 'react-international-phone'
import Body from "@scandic-hotels/design-system/Body"
import { ErrorMessage } from "@scandic-hotels/design-system/Form/ErrorMessage"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Input } from "@scandic-hotels/design-system/Input"
import { Label } from "@scandic-hotels/design-system/Label"
import Body from '../../Body'
import { ErrorMessage } from '../ErrorMessage'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Input } from '../../Input'
import { Label } from '../../Label'
import { getDefaultCountryFromLang } from "@/constants/languages"
import styles from './phone.module.css'
import useLang from "@/hooks/useLang"
import { getErrorMessage } from "@/utils/getErrorMessage"
import styles from "./phone.module.css"
import type { PhoneProps } from "@/types/components/form/phone"
import type { PhoneProps } from './phone'
export default function Phone({
ariaLabel = "Phone number input",
className = "",
countrySelectorName = "phoneNumberCC",
ariaLabel = 'Phone number input',
className = '',
countryLabel = 'Country code',
countrySelectorName = 'phoneNumberCC',
countriesWithTranslatedName,
defaultCountryCode,
disabled = false,
errorMessage,
label,
name = "phoneNumber",
name = 'phoneNumber',
placeholder,
readOnly = false,
registerOptions = {
required: true,
},
}: PhoneProps) {
const intl = useIntl()
const lang = useLang()
const countries = useMemo(() => {
return defaultCountries.map((dc) => {
const country = parseCountry(dc)
const translatedCountryName = intl.formatDisplayName(
country.iso2.toUpperCase(),
{ type: "region" }
const parsedCountry = parseCountry(dc)
const country = countriesWithTranslatedName?.find(
(country) => country.code === parsedCountry.iso2.toUpperCase()
)
const translatedCountryName = country?.displayName ?? country?.name
if (translatedCountryName) {
country.name = translatedCountryName
parsedCountry.name = translatedCountryName
}
return buildCountryData(country)
return buildCountryData(parsedCountry)
})
}, [intl])
}, [countriesWithTranslatedName])
const { formState, getFieldState, register, setValue } = useFormContext()
const fieldState = getFieldState(name)
@@ -69,9 +64,7 @@ export default function Phone({
const { country, setCountry } = usePhoneInput({
countries,
defaultCountry: phoneNumberCC
? phoneNumberCC
: getDefaultCountryFromLang(lang),
defaultCountry: phoneNumberCC ? phoneNumberCC : defaultCountryCode,
})
function handleSelectCountry(value: ParsedCountry) {
@@ -98,7 +91,7 @@ export default function Phone({
dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag}
onSelect={handleSelectCountry}
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
preferredCountries={['de', 'dk', 'fi', 'no', 'se', 'gb']}
selectedCountry={country.iso2}
renderButtonWrapper={(props) => (
<button
@@ -109,9 +102,7 @@ export default function Phone({
data-testid="country-selector"
>
<Label required={!!registerOptions.required} size="small">
{intl.formatMessage({
defaultMessage: "Country code",
})}
{countryLabel}
</Label>
<span className={styles.selectContainer}>
{props.children}
@@ -153,10 +144,7 @@ export default function Phone({
<ErrorMessage
errors={formState.errors}
name={name}
messageLabel={getErrorMessage(
intl,
formState.errors[name]?.message?.toString()
)}
messageLabel={errorMessage}
/>
</TextField>
</div>

View File

@@ -79,7 +79,7 @@
}
}
.select[aria-expanded="true"] .chevron {
.select[aria-expanded='true'] .chevron {
transform: rotate(180deg);
}

View File

@@ -1,14 +1,19 @@
import type { CountryCode } from "libphonenumber-js"
import type { RegisterOptions } from "react-hook-form"
export type LowerCaseCountryCode = Lowercase<CountryCode>
import type { RegisterOptions } from 'react-hook-form'
export interface PhoneProps {
ariaLabel?: string
className?: string
countryLabel?: string
countrySelectorName?: string
countriesWithTranslatedName: {
code: string
displayName?: string
name: string
}[]
disabled?: boolean
errorMessage?: string
label: string
defaultCountryCode: string
name?: string
placeholder?: string
readOnly?: boolean

View File

@@ -17,6 +17,7 @@
"./Form/Checkbox": "./dist/components/Form/Checkbox/index.js",
"./Form/Country": "./dist/components/Form/Country/index.js",
"./Form/ErrorMessage": "./dist/components/Form/ErrorMessage/index.js",
"./Form/Phone": "./dist/components/Form/Phone/index.js",
"./Form/RadioCard": "./dist/components/Form/RadioCard/index.js",
"./Input": "./dist/components/Input/index.js",
"./Label": "./dist/components/Label/index.js",
@@ -152,7 +153,8 @@
"react": "^19.1.0",
"react-aria-components": "^1.8.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.56.2"
"react-hook-form": "^7.56.2",
"react-international-phone": "^4.5.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",

View File

@@ -6734,6 +6734,7 @@ __metadata:
react-aria-components: ^1.8.0
react-dom: ^19.1.0
react-hook-form: ^7.56.2
react-international-phone: ^4.5.0
languageName: unknown
linkType: soft