diff --git a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx index 3feed3122..3bbf2990a 100644 --- a/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx +++ b/apps/scandic-web/app/[lang]/(live)/(public)/hotelreservation/(standard)/details/page.tsx @@ -119,6 +119,7 @@ export default async function DetailsPage( - + - ) -} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Phone/Number.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Phone/Number.tsx deleted file mode 100644 index 3a742aebc..000000000 --- a/apps/scandic-web/components/TempDesignSystem/Form/Phone/Number.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useIntl } from "react-intl" - -import Input from "@/components/TempDesignSystem/Form/Input" - -export default function PhoneNumber() { - const intl = useIntl() - return ( - - ) -} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx index f6021b64c..a795b93e0 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/Phone/index.tsx @@ -1,12 +1,8 @@ "use client" import "react-international-phone/style.css" -import { - isValidPhoneNumber, - parsePhoneNumberWithError, -} from "libphonenumber-js" import { TextField } from "react-aria-components" -import { useController, useFormContext, useWatch } from "react-hook-form" +import { useFormContext, useWatch } from "react-hook-form" import { CountrySelector, DialCodePreview, @@ -17,6 +13,8 @@ import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { getDefaultCountryFromLang } from "@/constants/languages" + import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage" import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel" import Label from "@/components/TempDesignSystem/Form/Label" @@ -25,17 +23,12 @@ import useLang from "@/hooks/useLang" import styles from "./phone.module.css" -import type { ChangeEvent } from "react" - -import type { - LowerCaseCountryCode, - PhoneProps, -} from "@/types/components/form/phone" -import type { Lang } from "@/constants/languages" +import type { PhoneProps } from "@/types/components/form/phone" export default function Phone({ ariaLabel = "Phone number input", className = "", + countrySelectorName = "phoneNumberCC", disabled = false, label, name = "phoneNumber", @@ -47,48 +40,27 @@ export default function Phone({ }: PhoneProps) { const intl = useIntl() const lang = useLang() - const { control, setValue, trigger } = useFormContext() - const phone = useWatch({ name }) + const { formState, getFieldState, register, setValue } = useFormContext() + const fieldState = getFieldState(name) + const [phoneNumber, phoneNumberCC] = useWatch({ + name: [name, countrySelectorName], + }) as [string, string] - const { field, fieldState, formState } = useController({ - control, - disabled, - name, - rules: registerOptions, + const { country, setCountry } = usePhoneInput({ + defaultCountry: phoneNumberCC + ? phoneNumberCC + : getDefaultCountryFromLang(lang), }) - const defaultPhoneNumber = formState.defaultValues?.phoneNumber ?? "" - - // If defaultPhoneNumber exists and is valid, parse it to get the country code, - // otherwise set the default country from the lang. - const defaultCountry = isValidPhoneNumber(defaultPhoneNumber) - ? parsePhoneNumberWithError(defaultPhoneNumber).country?.toLowerCase() - : getDefaultCountryFromLang(lang) - - const { country, handlePhoneValueChange, inputValue, setCountry } = - usePhoneInput({ - defaultCountry, - disableDialCodeAndPrefix: true, - forceDialCode: true, - value: phone, - onChange: (value) => { - // If not checked trigger(name) forces validation on mount - // which shows error message before user even can see the form - if (value.inputValue) { - setValue(name, value.phone) - trigger(name) - } else { - setValue(name, "") - } - }, - }) - function handleSelectCountry(value: ParsedCountry) { setCountry(value.iso2) - } - - function handleChange(evt: ChangeEvent) { - handlePhoneValueChange(evt) + setValue(countrySelectorName, value.iso2, { + shouldDirty: true, + shouldValidate: true, + }) + if (registerOptions.onBlur) { + registerOptions.onBlur(value) + } } return ( @@ -134,41 +106,24 @@ export default function Phone({ /> - + ) } - -function getDefaultCountryFromLang(lang: Lang): LowerCaseCountryCode { - const countryMap: Record = { - sv: "se", - da: "dk", - fi: "fi", - no: "no", - de: "de", - en: "se", // Default to Sweden for English - } - return countryMap[lang] || "se" -} diff --git a/apps/scandic-web/constants/countryPhoneCodes.ts b/apps/scandic-web/constants/countryPhoneCodes.ts deleted file mode 100644 index 1a3443daa..000000000 --- a/apps/scandic-web/constants/countryPhoneCodes.ts +++ /dev/null @@ -1,252 +0,0 @@ -export const countryPhoneCodes = { - Sweden: "+46", - Norway: "+47", - Denmark: "+45", - Finland: "+358", - Germany: "+49", - Afghanistan: "+93", - Albania: "+355", - Algeria: "+213", - "American Samoa": "+1684", - Andorra: "+376", - Angola: "+244", - Anguilla: "+1264", - Antarctica: "+672", - "Antigua and Barbuda": "+1268", - Argentina: "+54", - Armenia: "+374", - Aruba: "+297", - Australia: "+61", - Austria: "+43", - Azerbaijan: "+994", - Bahamas: "+1242", - Bahrain: "+973", - Bangladesh: "+880", - Barbados: "+1246", - Belarus: "+375", - Belgium: "+32", - Belize: "+501", - Benin: "+229", - Bermuda: "+1441", - Bhutan: "+975", - Bolivia: "+591", - Bonaire: "+599", - "Bosnia and Herzegovina": "+387", - Botswana: "+267", - "Bouvet Island": "+47", - Brazil: "+55", - "British Indian Ocean Territory": "+246", - "Brunei Darussalam": "+673", - Bulgaria: "+359", - "Burkina Faso": "+226", - Burundi: "+257", - Cambodia: "+855", - Cameroon: "+237", - Canada: "+1", - "Cape Verde": "+238", - "Cayman Islands": "+1345", - "Central African Republic": "+236", - Chad: "+235", - Chile: "+56", - China: "+86", - "Christmas Island": "+61", - "Cocos (Keeling) Islands": "+61", - Colombia: "+57", - Comoros: "+269", - Congo: "+242", - "Congo, The Democratic Republic of the": "+243", - "Cook Islands": "+682", - "Costa Rica": "+506", - "Côte d'Ivoire": "+225", - Croatia: "+385", - Cuba: "+53", - Curacao: "+599", - Cyprus: "+357", - "Czech Republic": "+420", - Djibouti: "+253", - Dominica: "+1767", - "Dominican Republic": "+1809", - Ecuador: "+593", - Egypt: "+20", - "El Salvador": "+503", - "Equatorial Guinea": "+240", - Eritrea: "+291", - Estonia: "+372", - Eswatini: "+268", - Ethiopia: "+251", - "Falkland Islands (Malvinas)": "+500", - "Faroe Islands": "+298", - Fiji: "+679", - France: "+33", - "French Guiana": "+594", - "French Polynesia": "+689", - "French Southern Territories": "+262", - Gabon: "+241", - Gambia: "+220", - Georgia: "+995", - Ghana: "+233", - Gibraltar: "+350", - Greece: "+30", - Greenland: "+299", - Grenada: "+1473", - Guadeloupe: "+590", - Guam: "+1671", - Guatemala: "+502", - Guernsey: "+44", - Guinea: "+224", - "Guinea-Bissau": "+245", - Guyana: "+592", - Haiti: "+509", - "Heard Island and Mcdonald Islands": "+61", - "Holy See (Vatican City State)": "+379", - Honduras: "+504", - "Hong Kong": "+852", - Hungary: "+36", - Iceland: "+354", - India: "+91", - Indonesia: "+62", - "Iran, Islamic Republic Of": "+98", - Iraq: "+964", - Ireland: "+353", - "Isle of Man": "+44", - Israel: "+972", - Italy: "+39", - Jamaica: "+1876", - Japan: "+81", - Jersey: "+44", - Jordan: "+962", - Kazakhstan: "+7", - Kenya: "+254", - Kiribati: "+686", - 'Korea, Democratic People"S Republic of': "+850", - "Korea, Republic of": "+82", - Kuwait: "+965", - Kyrgyzstan: "+996", - Laos: "+856", - Latvia: "+371", - Lebanon: "+961", - Lesotho: "+266", - Liberia: "+231", - "Libyan Arab Jamahiriya": "+218", - Liechtenstein: "+423", - Lithuania: "+370", - Luxembourg: "+352", - Macao: "+853", - "Macedonia, The Former Yugoslav Republic of": "+389", - Madagascar: "+261", - Malawi: "+265", - Malaysia: "+60", - Maldives: "+960", - Mali: "+223", - Malta: "+356", - "Marshall Islands": "+692", - Martinique: "+596", - Mauritania: "+222", - Mauritius: "+230", - Mayotte: "+262", - Mexico: "+52", - "Micronesia, Federated States of": "+691", - "Moldova, Republic of": "+373", - Monaco: "+377", - Mongolia: "+976", - Montenegro: "+382", - Montserrat: "+1664", - Morocco: "+212", - Mozambique: "+258", - Myanmar: "+95", - Namibia: "+264", - Nauru: "+674", - Nepal: "+977", - Netherlands: "+31", - "Netherlands Antilles": "+599", - "New Caledonia": "+687", - "New Zealand": "+64", - Nicaragua: "+505", - Niger: "+227", - Nigeria: "+234", - Niue: "+683", - "Norfolk Island": "+672", - "Northern Mariana Islands": "+1670", - Oman: "+968", - Pakistan: "+92", - Palau: "+680", - Palestine: "+970", - Panama: "+507", - "Papua New Guinea": "+675", - Paraguay: "+595", - Peru: "+51", - Philippines: "+63", - Pitcairn: "+64", - Poland: "+48", - Portugal: "+351", - "Puerto Rico": "+1787", - Qatar: "+974", - RWANDA: "+250", - Reunion: "+262", - Romania: "+40", - "Russian Federation": "+7", - "Saint Barthelemy": "+590", - "Saint Helena": "+290", - "Saint Kitts and Nevis": "+1869", - "Saint Lucia": "+1758", - "Saint Martin": "+590", - "Saint Pierre and Miquelon": "+508", - "Saint Vincent and the Grenadines": "+1784", - Samoa: "+685", - "San Marino": "+378", - "Sao Tome and Principe": "+239", - "Saudi Arabia": "+966", - Senegal: "+221", - Serbia: "+381", - Seychelles: "+248", - "Sierra Leone": "+232", - Singapore: "+65", - "Sint Maarten": "+1721", - Slovakia: "+421", - Slovenia: "+386", - "Solomon Islands": "+677", - Somalia: "+252", - "South Africa": "+27", - "South Georgia and the South Sandwich Islands": "+500", - "South Sudan": "+211", - Spain: "+34", - "Sri Lanka": "+94", - Sudan: "+249", - Suriname: "+597", - "Svalbard and Jan Mayen": "+47", - Switzerland: "+41", - "Syrian Arab Republic": "+963", - Taiwan: "+886", - Tajikistan: "+992", - "Tanzania, United Republic of": "+255", - Thailand: "+66", - "Timor-Leste": "+670", - Togo: "+228", - Tokelau: "+690", - Tonga: "+676", - "Trinidad and Tobago": "+1868", - Tunisia: "+216", - Turkey: "+90", - Turkmenistan: "+993", - "Turks and Caicos Islands": "+1649", - Tuvalu: "+688", - Uganda: "+256", - Ukraine: "+380", - "United Arab Emirates": "+971", - "United Kingdom": "+44", - "United States": "+1", - "United States Minor Outlying Islands": "+1", - Uruguay: "+598", - Uzbekistan: "+998", - Vanuatu: "+678", - Venezuela: "+58", - Vietnam: "+84", - "Virgin Islands, British": "+1284", - "Virgin Islands, U.S.": "+1340", - "Wallis and Futuna": "+681", - "Western Sahara": "+212", - Yemen: "+967", - Zambia: "+260", - Zimbabwe: "+263", - "Åland Islands": "+358", -} diff --git a/apps/scandic-web/constants/languages.ts b/apps/scandic-web/constants/languages.ts index ceb7a061e..9d4ca065e 100644 --- a/apps/scandic-web/constants/languages.ts +++ b/apps/scandic-web/constants/languages.ts @@ -1,3 +1,5 @@ +import type { LowerCaseCountryCode } from "@/types/components/form/phone" + export enum Lang { da = "da", de = "de", @@ -106,3 +108,15 @@ export function getLocalizedLanguageOptions(currentLang: Lang) { } }) } + +export function getDefaultCountryFromLang(lang: Lang): LowerCaseCountryCode { + const countryMap: Record = { + sv: "se", + da: "dk", + fi: "fi", + no: "no", + de: "de", + en: "se", // Default to Sweden for English + } + return countryMap[lang] || "se" +} diff --git a/apps/scandic-web/providers/EnterDetailsProvider.tsx b/apps/scandic-web/providers/EnterDetailsProvider.tsx index 1cd1ee724..1b4dbac04 100644 --- a/apps/scandic-web/providers/EnterDetailsProvider.tsx +++ b/apps/scandic-web/providers/EnterDetailsProvider.tsx @@ -27,6 +27,7 @@ export default function EnterDetailsProvider({ booking, breakfastPackages, children, + lang, rooms, searchParamsStr, user, @@ -73,7 +74,8 @@ export default function EnterDetailsProvider({ initialData, searchParamsStr, user, - breakfastPackages + breakfastPackages, + lang ) } diff --git a/apps/scandic-web/stores/enter-details/helpers.ts b/apps/scandic-web/stores/enter-details/helpers.ts index cbfb33440..063ef322d 100644 --- a/apps/scandic-web/stores/enter-details/helpers.ts +++ b/apps/scandic-web/stores/enter-details/helpers.ts @@ -1,4 +1,5 @@ import isEqual from "fast-deep-equal" +import { parsePhoneNumberFromString } from "libphonenumber-js" import { sumPackages, @@ -16,6 +17,13 @@ import type { PersistedState, RoomState } from "@/types/stores/enter-details" import type { SafeUser } from "@/types/user" export function extractGuestFromUser(user: NonNullable) { + let phoneNumberCC = "" + if (user.phoneNumber) { + const parsedPhoneNumber = parsePhoneNumberFromString(user.phoneNumber) + if (parsedPhoneNumber?.country) { + phoneNumberCC = parsedPhoneNumber.country.toLowerCase() + } + } return { countryCode: user.address.countryCode?.toString(), email: user.email, @@ -24,6 +32,7 @@ export function extractGuestFromUser(user: NonNullable) { join: false, membershipNo: user.membership?.membershipNumber, phoneNumber: user.phoneNumber ?? "", + phoneNumberCC, } } diff --git a/apps/scandic-web/stores/enter-details/index.ts b/apps/scandic-web/stores/enter-details/index.ts index e6f5ba4c5..e74b49563 100644 --- a/apps/scandic-web/stores/enter-details/index.ts +++ b/apps/scandic-web/stores/enter-details/index.ts @@ -4,6 +4,7 @@ import { useContext } from "react" import { create, useStore } from "zustand" import { REDEMPTION } from "@/constants/booking" +import { getDefaultCountryFromLang } from "@/constants/languages" import { dt } from "@/lib/dt" import { @@ -34,6 +35,7 @@ import type { RoomState, } from "@/types/stores/enter-details" import type { SafeUser } from "@/types/user" +import type { Lang } from "@/constants/routes/hotelReservation" const defaultGuestState = { countryCode: "", @@ -44,6 +46,7 @@ const defaultGuestState = { lastName: "", membershipNo: "", phoneNumber: "", + phoneNumberCC: "", zipCode: "", } @@ -53,7 +56,8 @@ export function createDetailsStore( initialState: InitialState, searchParams: string, user: SafeUser, - breakfastPackages: BreakfastPackages + breakfastPackages: BreakfastPackages, + lang: Lang ) { const isMember = !!user const isRedemption = @@ -328,6 +332,8 @@ export function createDetailsStore( currentRoom.guest.firstName = data.firstName currentRoom.guest.join = data.join currentRoom.guest.lastName = data.lastName + currentRoom.guest.phoneNumber = data.phoneNumber + currentRoom.guest.phoneNumberCC = data.phoneNumberCC if (data.specialRequest?.comment) { currentRoom.specialRequest.comment = @@ -339,7 +345,6 @@ export function createDetailsStore( } else { currentRoom.guest.membershipNo = data.membershipNo } - currentRoom.guest.phoneNumber = data.phoneNumber // Only valid for room 1 if (idx === 0 && data.join && !isMember) { @@ -397,7 +402,10 @@ export function createDetailsStore( guest: isMember && idx === 0 ? deepmerge(defaultGuestState, extractGuestFromUser(user)) - : defaultGuestState, + : { + ...defaultGuestState, + phoneNumberCC: getDefaultCountryFromLang(lang), + }, roomPrice: getRoomPrice(room.roomRate, isMember && idx === 0), specialRequest: { comment: "", diff --git a/apps/scandic-web/types/components/form/phone.ts b/apps/scandic-web/types/components/form/phone.ts index 537c17cc1..332741cb2 100644 --- a/apps/scandic-web/types/components/form/phone.ts +++ b/apps/scandic-web/types/components/form/phone.ts @@ -6,6 +6,7 @@ export type LowerCaseCountryCode = Lowercase export interface PhoneProps { ariaLabel?: string className?: string + countrySelectorName?: string disabled?: boolean label: string name?: string diff --git a/apps/scandic-web/types/providers/enter-details.ts b/apps/scandic-web/types/providers/enter-details.ts index 9c47e0de4..73e74987e 100644 --- a/apps/scandic-web/types/providers/enter-details.ts +++ b/apps/scandic-web/types/providers/enter-details.ts @@ -1,11 +1,13 @@ import type { Room } from "@/types/providers/details/room" import type { SafeUser } from "@/types/user" +import type { Lang } from "@/constants/routes/hotelReservation" import type { BreakfastPackages } from "../components/hotelReservation/breakfast" import type { DetailsBooking } from "../components/hotelReservation/enterDetails/details" export interface DetailsProviderProps extends React.PropsWithChildren { booking: DetailsBooking breakfastPackages: BreakfastPackages + lang: Lang rooms: Room[] searchParamsStr: string user: SafeUser diff --git a/apps/scandic-web/utils/zod/phoneValidator.ts b/apps/scandic-web/utils/zod/phoneValidator.ts index 6a512d996..80e697806 100644 --- a/apps/scandic-web/utils/zod/phoneValidator.ts +++ b/apps/scandic-web/utils/zod/phoneValidator.ts @@ -1,86 +1,20 @@ -import { - isPossiblePhoneNumber, - ParseError, - parsePhoneNumberWithError, - validatePhoneNumberLength, -} from "libphonenumber-js" import { z } from "zod" -const enum ParseErrorMessage { - INVALID_COUNTRY = "INVALID_COUNTRY", - INVALID_LENGTH = "INVALID_LENGTH", - NOT_A_NUMBER = "NOT_A_NUMBER", - TOO_LONG = "TOO_LONG", - TOO_SHORT = "TOO_SHORT", -} - export function phoneValidator( msg = "Required field", invalidMsg = "Invalid type" ) { return z .string({ invalid_type_error: invalidMsg, required_error: msg }) - .min(1, { message: msg }) + .min(5, { message: "The number you have entered is too short" }) .superRefine((value, ctx) => { if (value) { - try { - const phoneNumber = parsePhoneNumberWithError(value) - if (phoneNumber) { - if (isPossiblePhoneNumber(value, phoneNumber.country)) { - return validatePhoneNumberLength(value, phoneNumber.country) - } else { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Please enter a valid phone number", - }) - } - } - } catch (error) { - if (error instanceof ParseError) { - /** - * Only setup for when we need proper validation, - * should probably move to .superRefine to be able - * to return different messages depending on error. - */ - switch (error.message) { - case ParseErrorMessage.INVALID_COUNTRY: - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: - "The country selected and country code doesn't match", - }) - break - case ParseErrorMessage.INVALID_LENGTH: - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Please enter a valid phone number", - }) - break - case ParseErrorMessage.NOT_A_NUMBER: - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Please enter a number", - }) - break - case ParseErrorMessage.TOO_LONG: - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "The number you have entered is too long", - }) - break - case ParseErrorMessage.TOO_SHORT: - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "The number you have entered is too short", - }) - break - } - } else { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "The number you have entered is not valid", - }) - } + const containsAlphabeticChars = /[a-z]/gi.test(value) + if (containsAlphabeticChars) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Please enter a valid phone number", + }) } } })