feat(SW-1773): add proper validation to form and query

This commit is contained in:
Christian Andolf
2025-02-27 16:28:58 +01:00
parent 21255f8557
commit c98ac88ac0
12 changed files with 74 additions and 26 deletions

View File

@@ -14,6 +14,7 @@ import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"
import { type FindMyBookingFormSchema, findMyBookingFormSchema } from "./schema"
@@ -25,12 +26,6 @@ export default function Form() {
const intl = useIntl()
const lang = useLang()
const form = useForm<FindMyBookingFormSchema>({
defaultValues: {
reservationNumber: "",
firstName: "",
lastName: "",
email: "",
},
resolver: zodResolver(findMyBookingFormSchema),
mode: "all",
criteriaMode: "all",
@@ -39,23 +34,30 @@ export default function Form() {
const update = trpc.booking.createRefId.useMutation({
onSuccess: (result) => {
const values = form.getValues()
const value = new URLSearchParams(values).toString()
document.cookie = `bv=${value}; Path=/; Max-Age=30; Secure; SameSite=Strict`
router.push(
`/${lang}/hotelreservation/my-stay/${encodeURIComponent(result.refId)}`
)
},
onError: (error) => {
console.log("Failed to create ref id", error)
console.error("Failed to create ref id", error)
toast.error(
intl.formatMessage({
id: "Failed to submit form, please try again later.",
})
)
},
})
async function onSubmit(data: FindMyBookingFormSchema) {
const value = new URLSearchParams(data).toString()
document.cookie = `bv=${value}; Path=/; Max-Age=30; Secure; SameSite=Strict`
update.mutate({
confirmationNumber: data.reservationNumber,
bookingNumber: data.bookingNumber,
lastName: data.lastName,
})
}
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className={styles.form}>
@@ -71,8 +73,8 @@ export default function Form() {
</div>
<div className={styles.inputs}>
<Input
label="Reservation number"
name="reservationNumber"
label="Booking number"
name="bookingNumber"
placeholder="XXXXXX"
registerOptions={{ required: true }}
/>
@@ -123,7 +125,7 @@ export default function Form() {
type="submit"
intent="primary"
theme="base"
disabled={form.formState.isSubmitting}
disabled={form.formState.isSubmitting || update.isPending}
>
{intl.formatMessage({ id: "Find" })}
</Button>

View File

@@ -1,14 +1,22 @@
import { z } from "zod"
export const findMyBookingFormSchema = z.object({
reservationNumber: z.string(),
firstName: z.string().max(250).trim().min(1, {
bookingNumber: z
.string()
.trim()
.regex(/^[0-9]+(-[0-9])?$/, {
message: "Invalid booking number",
})
.min(1, {
message: "Booking number is required",
}),
firstName: z.string().trim().max(250).min(1, {
message: "First name is required",
}),
lastName: z.string().max(250).trim().min(1, {
lastName: z.string().trim().max(250).min(1, {
message: "Last name is required",
}),
email: z.string().max(250).email(),
email: z.string().max(250).email({ message: "Email address is required" }),
})
export interface FindMyBookingFormSchema

View File

@@ -1,6 +1,7 @@
"use client"
import { Text, TextField } from "react-aria-components"
import { Controller, useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
@@ -25,6 +26,7 @@ export default function Input({
registerOptions = {},
type = "text",
}: InputProps) {
const intl = useIntl()
const { control } = useFormContext()
let numberAttributes: HTMLAttributes<HTMLInputElement> = {}
if (type === "number") {
@@ -73,7 +75,7 @@ export default function Input({
{fieldState.error ? (
<Caption className={styles.error} fontOnly>
<InfoCircleIcon color="red" />
{fieldState.error.message}
{intl.formatMessage({ id: fieldState.error.message })}
</Caption>
) : null}
</TextField>

View File

@@ -89,6 +89,7 @@
"Booking code": "Bookingkode",
"Booking confirmation": "Booking bekræftelse",
"Booking number": "Bookingnummer",
"Booking number is required": "Bookingnummer er påkrævet",
"Booking policy": "Booking politik",
"Booking summary": "Opsummering",
"Breakfast": "Morgenmad",
@@ -209,7 +210,7 @@
"Elevator preference": "Elevatorpræference",
"Email": "E-mail",
"Email address": "E-mailadresse",
"Email address is required": "Email address is required",
"Email address is required": "E-mailadresse er påkrævet",
"Enjoy relaxed restaurant experiences": "Enjoy relaxed restaurant experiences",
"Enter destination or hotel": "Indtast destination eller hotel",
"Enter your details": "Indtast dine oplysninger",
@@ -225,6 +226,7 @@
"FAQ": "Ofte stillede spørgsmål",
"Failed to add to calendar": "Fejl ved tilføjelse til kalender",
"Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Medlemskab ikke verificeret",
@@ -318,6 +320,7 @@
"Indoor windows facing the hotel": "Indoor windows facing the hotel",
"Insufficient points": "Utilstrækkelige point",
"Invalid booking code": "Ugyldig reservationskode",
"Invalid booking number": "Ugyldigt reservationsnummer",
"Is there anything else you would like us to know before your arrival?": "Er der andet, du gerne vil have os til at vide, før din ankomst?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke muligt at administrere dine kommunikationspræferencer lige nu, prøv venligst igen senere eller kontakt support, hvis problemet fortsætter.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ud til, at ingen hoteller matcher dine filtre. Prøv at justere din søgning for at finde det perfekte ophold.",
@@ -466,9 +469,9 @@
"Password": "Adgangskode",
"Pay later": "Betal senere",
"Pay now": "Betal nu",
"Pay the member price of {amount} for Room {roomNr}": "Betal medlemsprisen på {amount} til værelse {roomNr}",
"Pay with card": "Betal med kort",
"Pay with points": "Betal med point",
"Pay the member price of {amount} for Room {roomNr}": "Betal medlemsprisen på {amount} til værelse {roomNr}",
"Payment": "Betaling",
"Payment Guarantee": "Garanti betaling",
"Payment details": "Payment details",

View File

@@ -90,6 +90,7 @@
"Booking code": "Buchungscode",
"Booking confirmation": "Buchungsbestätigung",
"Booking number": "Buchungsnummer",
"Booking number is required": "Buchungsnummer ist erforderlich",
"Booking policy": "Buchungsbedingungen",
"Booking summary": "Zusammenfassung",
"Breakfast": "Frühstück",
@@ -226,6 +227,7 @@
"FAQ": "Häufig gestellte Fragen",
"Failed to add to calendar": "Fehler beim Hinzufügen zum Kalender",
"Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Medlemskab nicht verifiziert",
@@ -319,6 +321,7 @@
"Indoor windows facing the hotel": "Indoor windows facing the hotel",
"Insufficient points": "Zu wenig Punkte",
"Invalid booking code": "Ungültiger Buchungscode",
"Invalid booking number": "Ungültige Buchungsnummer",
"Is there anything else you would like us to know before your arrival?": "Gibt es noch etwas, das Sie uns vor Ihrer Ankunft mitteilen möchten?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Es ist derzeit nicht möglich, Ihre Kommunikationseinstellungen zu verwalten. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Es scheint, dass keine Hotels Ihren Filtern entsprechen. Versuchen Sie, Ihre Suche anzupassen, um den perfekten Aufenthalt zu finden.",
@@ -468,9 +471,9 @@
"Password": "Passwort",
"Pay later": "Später bezahlen",
"Pay now": "Jetzt bezahlen",
"Pay the member price of {amount} for Room {roomNr}": "Zahlen Sie den Mitgliedspreis von {amount} für Zimmer {roomNr}",
"Pay with Card": "Mit Karte bezahlen",
"Pay with points": "Mit Punkten bezahlen",
"Pay the member price of {amount} for Room {roomNr}": "Zahlen Sie den Mitgliedspreis von {amount} für Zimmer {roomNr}",
"Payment": "Zahlung",
"Payment Guarantee": "Zahlungsgarantie",
"Payment details": "Payment details",

View File

@@ -89,6 +89,7 @@
"Booking code": "Booking code",
"Booking confirmation": "Booking confirmation",
"Booking number": "Booking number",
"Booking number is required": "Booking number is required",
"Booking policy": "Booking policy",
"Booking summary": "Booking summary",
"Breakfast": "Breakfast",
@@ -227,6 +228,7 @@
"FAQ": "FAQ",
"Failed to add to calendar": "Failed to add to calendar",
"Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Failed to verify membership",
@@ -320,6 +322,7 @@
"Indoor windows facing the hotel": "Indoor windows facing the hotel",
"Insufficient points": "Insufficient points",
"Invalid booking code": "Invalid booking code",
"Invalid booking number": "Invalid booking number",
"Is there anything else you would like us to know before your arrival?": "Is there anything else you would like us to know before your arrival?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.",

View File

@@ -88,6 +88,7 @@
"Booking code": "Varauskoodi",
"Booking confirmation": "Varausvahvistus",
"Booking number": "Varausnumero",
"Booking number is required": "Varausnumero vaaditaan",
"Booking policy": "Varauskäytäntö",
"Booking summary": "Yhteenveto",
"Breakfast": "Aamiainen",
@@ -225,6 +226,7 @@
"FAQ": "Usein kysytyt kysymykset",
"Failed to add to calendar": "Virhe kalenteriin lisäämisessä",
"Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Jäsenyys ei verifioitu",
@@ -318,6 +320,7 @@
"Indoor windows facing the hotel": "Indoor windows facing the hotel",
"Insufficient points": "Riittämättä pisteitä",
"Invalid booking code": "Virheellinen varauskoodi",
"Invalid booking number": "Virheellinen varausnumero",
"Is there anything else you would like us to know before your arrival?": "Onko jotain muuta, mitä haluaisit meidän tietävän ennen saapumistasi?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Viestintäasetuksiasi ei voi hallita juuri nyt. Yritä myöhemmin uudelleen tai ota yhteyttä tukeen, jos ongelma jatkuu.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Näyttää siltä, että mikään hotelli ei vastaa suodattimiasi. Yritä muokata hakuasi löytääksesi täydellisen oleskelun.",
@@ -467,9 +470,9 @@
"Password": "Salasana",
"Pay later": "Maksa myöhemmin",
"Pay now": "Maksa nyt",
"Pay the member price of {amount} for Room {roomNr}": "Maksa jäsenhinta {amount} varten Huone {roomNr}",
"Pay with Card": "Maksa kortilla",
"Pay with points": "Maksa pisteillä",
"Pay the member price of {amount} for Room {roomNr}": "Maksa jäsenhinta {amount} varten Huone {roomNr}",
"Payment": "Maksu",
"Payment Guarantee": "Varmistusmaksu",
"Payment details": "Payment details",

View File

@@ -88,6 +88,7 @@
"Booking code": "Bestillingskode",
"Booking confirmation": "Bestillingsbekreftelse",
"Booking number": "Bestillingsnummer",
"Booking number is required": "Bookingnummer er påkrevd",
"Booking policy": "Bestillingsbetingelser",
"Booking summary": "Sammendrag",
"Breakfast": "Frokost",
@@ -224,6 +225,7 @@
"FAQ": "Ofte stilte spørsmål",
"Failed to add to calendar": "Feil ved tilføyelse til kalender",
"Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Medlemskap ikke verifisert",
@@ -317,6 +319,7 @@
"Indoor windows facing the hotel": "Indoor windows facing the hotel",
"Insufficient points": "Utilstrekklig poeng",
"Invalid booking code": "Ugyldig bookingkode",
"Invalid booking number": "Ugyldig bestillingsnummer",
"Is there anything else you would like us to know before your arrival?": "Er det noe annet du vil at vi skal vite før ankomsten din?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke mulig å administrere kommunikasjonspreferansene dine akkurat nå, prøv igjen senere eller kontakt support hvis problemet vedvarer.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det ser ut til at ingen hoteller samsvarer med filtrene dine. Prøv å justere søket for å finne det perfekte oppholdet.",

View File

@@ -88,6 +88,7 @@
"Booking code": "Bokningskod",
"Booking confirmation": "Bokningsbekräftelse",
"Booking number": "Bokningsnummer",
"Booking number is required": "Bokningsnummer krävs",
"Booking policy": "Bokningsvillkor",
"Booking summary": "Sammanfattning",
"Breakfast": "Frukost",
@@ -224,6 +225,7 @@
"FAQ": "FAQ",
"Failed to add to calendar": "Misslyckades att lägga till i kalender",
"Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.",
"Failed to submit form, please try again later.": "Failed to submit form, please try again later.",
"Failed to unlink account": "Failed to unlink account",
"Failed to upgrade level": "Failed to upgrade level",
"Failed to verify membership": "Medlemskap inte verifierat",
@@ -317,6 +319,7 @@
"Indoor windows facing the hotel": "Inomhusfönster mot hotellet",
"Insufficient points": "Otillräckliga poäng",
"Invalid booking code": "Ogiltig bokningskod",
"Invalid booking number": "Ogiltigt bokningsnummer",
"Is there anything else you would like us to know before your arrival?": "Är det något mer du vill att vi ska veta innan din ankomst?",
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det gick inte att hantera dina kommunikationsinställningar just nu, försök igen senare eller kontakta supporten om problemet kvarstår.",
"It looks like no hotels match your filters. Try adjusting your search to find the perfect stay.": "Det verkar som att inga hotell matchar dina filter. Prova att justera din sökning för att hitta den perfekta vistelsen.",

View File

@@ -40,6 +40,14 @@ export function notFound(cause?: unknown) {
})
}
export function unprocessableContent(cause?: unknown) {
return new TRPCError({
code: "UNPROCESSABLE_CONTENT",
message: "Unprocessable content",
cause,
})
}
export function internalServerError(cause?: unknown) {
return new TRPCError({
code: "INTERNAL_SERVER_ERROR",
@@ -78,6 +86,8 @@ export function serverErrorByStatus(status: number, cause?: unknown) {
return notFound(cause)
case 409:
return conflictError(cause)
case 422:
return unprocessableContent(cause)
case 500:
default:
return internalServerError(cause)

View File

@@ -109,8 +109,12 @@ export const cancelBookingInput = z.object({
})
export const createRefIdInput = z.object({
confirmationNumber: z.string(),
lastName: z.string(),
bookingNumber: z
.string()
.trim()
.regex(/^\s*[0-9]+(-[0-9])?\s*$/)
.min(1),
lastName: z.string().trim().max(250).min(1),
})
// Query

View File

@@ -236,8 +236,12 @@ export const bookingQueryRouter = router({
createRefId: serviceProcedure
.input(createRefIdInput)
.mutation(async function ({ input }) {
const { confirmationNumber, lastName } = input
const encryptedRefId = encryptValue(`${confirmationNumber},${lastName}`)
const { bookingNumber, lastName } = input
const encryptedRefId = encryptValue(`${bookingNumber},${lastName}`)
if (!encryptedRefId) {
throw serverErrorByStatus(422, "Was not able to encrypt ref id")
}
return {
refId: encryptedRefId,