fix: extrapolate phone number parsing for re-usage in edit profile
move error messages to message handler
This commit is contained in:
@@ -1,13 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import {
|
|
||||||
isValidPhoneNumber,
|
|
||||||
parsePhoneNumberWithError,
|
|
||||||
} from "libphonenumber-js"
|
|
||||||
import { useParams, useRouter } from "next/navigation"
|
import { useParams, useRouter } from "next/navigation"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { usePhoneInput } from "react-international-phone"
|
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { type Lang, langToApiLang } from "@/constants/languages"
|
import { type Lang, langToApiLang } from "@/constants/languages"
|
||||||
@@ -21,6 +16,7 @@ import ChangeNameDisclaimer from "@/components/MyPages/Profile/ChangeNameDisclai
|
|||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
||||||
|
|
||||||
import FormContent from "./FormContent"
|
import FormContent from "./FormContent"
|
||||||
import { type EditProfileSchema, editProfileSchema } from "./schema"
|
import { type EditProfileSchema, editProfileSchema } from "./schema"
|
||||||
@@ -47,14 +43,7 @@ export default function Form({ user }: EditFormProps) {
|
|||||||
*/
|
*/
|
||||||
const [isValid, setIsValid] = useState(true)
|
const [isValid, setIsValid] = useState(true)
|
||||||
|
|
||||||
const { inputValue: phoneInput } = usePhoneInput({
|
const { phoneNumber, phoneNumberCC } = usePhoneNumberParsing(user.phoneNumber)
|
||||||
defaultCountry:
|
|
||||||
user.phoneNumber && isValidPhoneNumber(user.phoneNumber)
|
|
||||||
? parsePhoneNumberWithError(user.phoneNumber).country?.toLowerCase()
|
|
||||||
: "se",
|
|
||||||
forceDialCode: true,
|
|
||||||
value: user.phoneNumber,
|
|
||||||
})
|
|
||||||
|
|
||||||
const methods = useForm<EditProfileSchema>({
|
const methods = useForm<EditProfileSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -67,7 +56,8 @@ export default function Form({ user }: EditFormProps) {
|
|||||||
dateOfBirth: user.dateOfBirth,
|
dateOfBirth: user.dateOfBirth,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
language: user.language ?? langToApiLang[lang],
|
language: user.language ?? langToApiLang[lang],
|
||||||
phoneNumber: phoneInput,
|
phoneNumber: phoneNumber,
|
||||||
|
phoneNumberCC: phoneNumberCC,
|
||||||
password: "",
|
password: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
retypeNewPassword: "",
|
retypeNewPassword: "",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const editProfileSchema = z
|
|||||||
editProfileErrors.PHONE_REQUIRED,
|
editProfileErrors.PHONE_REQUIRED,
|
||||||
editProfileErrors.PHONE_REQUESTED
|
editProfileErrors.PHONE_REQUESTED
|
||||||
),
|
),
|
||||||
|
phoneNumberCC: z.string(),
|
||||||
|
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
newPassword: z.literal("").optional().or(passwordValidator()),
|
newPassword: z.literal("").optional().or(passwordValidator()),
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export default function SignupForm({ title }: SignUpFormProps) {
|
|||||||
lastName: "",
|
lastName: "",
|
||||||
email: "",
|
email: "",
|
||||||
phoneNumber: "",
|
phoneNumber: "",
|
||||||
|
phoneNumberCC: "",
|
||||||
dateOfBirth: "",
|
dateOfBirth: "",
|
||||||
address: {
|
address: {
|
||||||
countryCode: "",
|
countryCode: "",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const signUpSchema = z.object({
|
|||||||
signupErrors.PHONE_REQUIRED,
|
signupErrors.PHONE_REQUIRED,
|
||||||
signupErrors.PHONE_REQUESTED
|
signupErrors.PHONE_REQUESTED
|
||||||
),
|
),
|
||||||
|
phoneNumberCC: z.string(),
|
||||||
dateOfBirth: z.string().min(1, {
|
dateOfBirth: z.string().min(1, {
|
||||||
message: signupErrors.BIRTH_DATE_REQUIRED,
|
message: signupErrors.BIRTH_DATE_REQUIRED,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { parsePhoneNumberFromString } from "libphonenumber-js"
|
|
||||||
import { useCallback, useEffect, useMemo } from "react"
|
import { useCallback, useEffect, useMemo } from "react"
|
||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -14,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone"
|
|||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
|
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
||||||
|
|
||||||
import MemberPriceModal from "../MemberPriceModal"
|
import MemberPriceModal from "../MemberPriceModal"
|
||||||
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
||||||
@@ -54,14 +54,11 @@ export default function Details() {
|
|||||||
[idx, rooms]
|
[idx, rooms]
|
||||||
)
|
)
|
||||||
|
|
||||||
const initialPhoneNumber = initialData.phoneNumber
|
const { phoneNumber, phoneNumberCC } = usePhoneNumberParsing(
|
||||||
const parsedInitialPhoneNumber = initialPhoneNumber
|
initialData.phoneNumber,
|
||||||
? parsePhoneNumberFromString(initialPhoneNumber)
|
initialData.phoneNumberCC
|
||||||
: undefined
|
)
|
||||||
let initialPhoneNumberCC = initialData.phoneNumberCC
|
|
||||||
if (parsedInitialPhoneNumber && !initialPhoneNumberCC) {
|
|
||||||
initialPhoneNumberCC = parsedInitialPhoneNumber.country ?? ""
|
|
||||||
}
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
countryCode: initialData.countryCode,
|
countryCode: initialData.countryCode,
|
||||||
@@ -70,10 +67,8 @@ export default function Details() {
|
|||||||
join: initialData.join,
|
join: initialData.join,
|
||||||
lastName: initialData.lastName,
|
lastName: initialData.lastName,
|
||||||
membershipNo: initialData.membershipNo,
|
membershipNo: initialData.membershipNo,
|
||||||
phoneNumber: parsedInitialPhoneNumber?.isValid()
|
phoneNumber,
|
||||||
? parsedInitialPhoneNumber.nationalNumber
|
phoneNumberCC,
|
||||||
: initialPhoneNumber,
|
|
||||||
phoneNumberCC: initialPhoneNumberCC,
|
|
||||||
specialRequest: {
|
specialRequest: {
|
||||||
comment: room.specialRequest.comment,
|
comment: room.specialRequest.comment,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { parsePhoneNumberFromString } from "libphonenumber-js"
|
|
||||||
import { useCallback, useEffect } from "react"
|
import { useCallback, useEffect } from "react"
|
||||||
import { FormProvider, useForm } from "react-hook-form"
|
import { FormProvider, useForm } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
@@ -14,6 +13,7 @@ import Phone from "@/components/TempDesignSystem/Form/Phone"
|
|||||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||||
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
import { useFormTracking } from "@/components/TrackingSDK/hooks"
|
||||||
import { useRoomContext } from "@/contexts/Details/Room"
|
import { useRoomContext } from "@/contexts/Details/Room"
|
||||||
|
import usePhoneNumberParsing from "@/hooks/usePhoneNumberParsing"
|
||||||
|
|
||||||
import MemberPriceModal from "../MemberPriceModal"
|
import MemberPriceModal from "../MemberPriceModal"
|
||||||
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
import JoinScandicFriendsCard from "./JoinScandicFriendsCard"
|
||||||
@@ -46,14 +46,11 @@ export default function Details({ user }: DetailsProps) {
|
|||||||
|
|
||||||
const memberRate = "member" in room.roomRate ? room.roomRate.member : null
|
const memberRate = "member" in room.roomRate ? room.roomRate.member : null
|
||||||
|
|
||||||
const initialPhoneNumber = user?.phoneNumber || initialData.phoneNumber
|
const { phoneNumber, phoneNumberCC } = usePhoneNumberParsing(
|
||||||
const parsedInitialPhoneNumber = initialPhoneNumber
|
user?.phoneNumber || initialData.phoneNumber,
|
||||||
? parsePhoneNumberFromString(initialPhoneNumber)
|
initialData.phoneNumberCC
|
||||||
: undefined
|
)
|
||||||
let initialPhoneNumberCC = initialData.phoneNumberCC
|
|
||||||
if (parsedInitialPhoneNumber && !initialPhoneNumberCC) {
|
|
||||||
initialPhoneNumberCC = parsedInitialPhoneNumber.country ?? ""
|
|
||||||
}
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
countryCode: user?.address?.countryCode || initialData.countryCode,
|
countryCode: user?.address?.countryCode || initialData.countryCode,
|
||||||
@@ -64,10 +61,8 @@ export default function Details({ user }: DetailsProps) {
|
|||||||
join: initialData.join,
|
join: initialData.join,
|
||||||
lastName: user?.lastName || initialData.lastName,
|
lastName: user?.lastName || initialData.lastName,
|
||||||
membershipNo: initialData.membershipNo,
|
membershipNo: initialData.membershipNo,
|
||||||
phoneNumber: parsedInitialPhoneNumber?.isValid()
|
phoneNumber,
|
||||||
? parsedInitialPhoneNumber.nationalNumber
|
phoneNumberCC,
|
||||||
: initialPhoneNumber,
|
|
||||||
phoneNumberCC: initialPhoneNumberCC,
|
|
||||||
zipCode: "zipCode" in initialData ? initialData.zipCode : undefined,
|
zipCode: "zipCode" in initialData ? initialData.zipCode : undefined,
|
||||||
specialRequest: {
|
specialRequest: {
|
||||||
comment: room.specialRequest.comment,
|
comment: room.specialRequest.comment,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { signupErrors } from "@/components/Forms/Signup/schema"
|
|||||||
import { multiroomErrors } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema"
|
import { multiroomErrors } from "@/components/HotelReservation/EnterDetails/Details/Multiroom/schema"
|
||||||
import { roomOneErrors } from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema"
|
import { roomOneErrors } from "@/components/HotelReservation/EnterDetails/Details/RoomOne/schema"
|
||||||
import { findMyBookingErrors } from "@/components/HotelReservation/FindMyBooking/schema"
|
import { findMyBookingErrors } from "@/components/HotelReservation/FindMyBooking/schema"
|
||||||
|
import { phoneErrors } from "@/utils/zod/phoneValidator"
|
||||||
|
|
||||||
import type { IntlShape } from "react-intl"
|
import type { IntlShape } from "react-intl"
|
||||||
|
|
||||||
@@ -75,6 +76,11 @@ export function getErrorMessage(intl: IntlShape, errorCode?: string) {
|
|||||||
return intl.formatMessage({
|
return intl.formatMessage({
|
||||||
defaultMessage: "Phone is required",
|
defaultMessage: "Phone is required",
|
||||||
})
|
})
|
||||||
|
case phoneErrors.PHONE_NUMBER_TOO_SHORT:
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: "The number you have entered is too short",
|
||||||
|
})
|
||||||
|
case phoneErrors.PHONE_REQUESTED:
|
||||||
case signupErrors.PHONE_REQUESTED:
|
case signupErrors.PHONE_REQUESTED:
|
||||||
case multiroomErrors.PHONE_REQUESTED:
|
case multiroomErrors.PHONE_REQUESTED:
|
||||||
case roomOneErrors.PHONE_REQUESTED:
|
case roomOneErrors.PHONE_REQUESTED:
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ export default function Phone({
|
|||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const { formState, getFieldState, register, setValue } = useFormContext()
|
const { formState, getFieldState, register, setValue } = useFormContext()
|
||||||
const fieldState = getFieldState(name)
|
const fieldState = getFieldState(name)
|
||||||
const [phoneNumber, phoneNumberCC] = useWatch({
|
const [phoneNumber, phoneNumberCC]: [string, string] = useWatch({
|
||||||
name: [name, countrySelectorName],
|
name: [name, countrySelectorName],
|
||||||
}) as [string, string]
|
})
|
||||||
|
|
||||||
const { country, setCountry } = usePhoneInput({
|
const { country, setCountry } = usePhoneInput({
|
||||||
defaultCountry: phoneNumberCC
|
defaultCountry: phoneNumberCC
|
||||||
|
|||||||
23
apps/scandic-web/hooks/usePhoneNumberParsing.ts
Normal file
23
apps/scandic-web/hooks/usePhoneNumberParsing.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import parsePhoneNumberFromString from "libphonenumber-js"
|
||||||
|
|
||||||
|
export default function usePhoneNumberParsing(
|
||||||
|
initialPhoneNumber?: string,
|
||||||
|
initialPhoneNumberCC?: string
|
||||||
|
) {
|
||||||
|
const parsedInitialPhoneNumber = initialPhoneNumber
|
||||||
|
? parsePhoneNumberFromString(initialPhoneNumber)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
let phoneNumberCC = initialPhoneNumberCC
|
||||||
|
if (parsedInitialPhoneNumber && !phoneNumberCC) {
|
||||||
|
phoneNumberCC = parsedInitialPhoneNumber.country ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const phoneNumber = parsedInitialPhoneNumber?.isValid()
|
||||||
|
? parsedInitialPhoneNumber.nationalNumber
|
||||||
|
: initialPhoneNumber
|
||||||
|
|
||||||
|
return { phoneNumber, phoneNumberCC: phoneNumberCC?.toLowerCase() }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { CountryCode } from "libphonenumber-js/min"
|
import type { CountryCode } from "libphonenumber-js"
|
||||||
import type { RegisterOptions } from "react-hook-form"
|
import type { RegisterOptions } from "react-hook-form"
|
||||||
|
|
||||||
export type LowerCaseCountryCode = Lowercase<CountryCode>
|
export type LowerCaseCountryCode = Lowercase<CountryCode>
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const phoneErrors = {
|
||||||
|
PHONE_NUMBER_TOO_SHORT: "PHONE_NUMBER_TOO_SHORT",
|
||||||
|
PHONE_REQUESTED: "PHONE_REQUESTED",
|
||||||
|
} as const
|
||||||
|
|
||||||
export function phoneValidator(
|
export function phoneValidator(
|
||||||
msg = "Required field",
|
msg = "Required field",
|
||||||
invalidMsg = "Invalid type"
|
invalidMsg = "Invalid type"
|
||||||
) {
|
) {
|
||||||
return z
|
return z
|
||||||
.string({ invalid_type_error: invalidMsg, required_error: msg })
|
.string({ invalid_type_error: invalidMsg, required_error: msg })
|
||||||
.min(5, { message: "The number you have entered is too short" })
|
.min(5, phoneErrors.PHONE_NUMBER_TOO_SHORT)
|
||||||
.superRefine((value, ctx) => {
|
.superRefine((value, ctx) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const containsAlphabeticChars = /[a-z]/gi.test(value)
|
const containsAlphabeticChars = /[a-z]/gi.test(value)
|
||||||
if (containsAlphabeticChars) {
|
if (containsAlphabeticChars) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: "Please enter a valid phone number",
|
message: phoneErrors.PHONE_REQUESTED,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user