feat(SW-360): Added register form with server action

This commit is contained in:
Tobias Johansson
2024-09-11 17:10:57 +02:00
committed by Chuma McPhoy
parent c69e4b4b29
commit e086857072
11 changed files with 382 additions and 1 deletions

82
actions/registerUser.ts Normal file
View File

@@ -0,0 +1,82 @@
"use server"
import { z } from "zod"
import * as api from "@/lib/api"
import { profileServiceServerActionProcedure } from "@/server/trpc"
import { registerSchema } from "@/components/Forms/Register/schema"
import { passwordValidator } from "@/utils/passwordValidator"
import { phoneValidator } from "@/utils/phoneValidator"
const registerUserPayload = z.object({
language: z.string(),
firstName: z.string(),
lastName: z.string(),
email: z.string(),
phoneNumber: phoneValidator("Phone is required"),
dateOfBirth: z.string(),
address: z.object({
city: z.string().default(""),
country: z.string().default(""),
countryCode: z.string().default(""),
zipCode: z.string().default(""),
streetAddress: z.string().default(""),
}),
password: passwordValidator("Password is required"),
})
export const registerUser = profileServiceServerActionProcedure
.input(registerSchema)
.mutation(async function ({ ctx, input }) {
const payload = {
...input,
language: ctx.lang,
phoneNumber: input.phoneNumber.replace(/\s+/g, ""),
}
const parsedPayload = registerUserPayload.safeParse(payload)
if (!parsedPayload.success) {
console.error(
"registerUser payload validation error",
JSON.stringify({
query: input,
error: parsedPayload.error,
})
)
return false
}
try {
const apiResponse = await api.post(api.endpoints.v1.profile, {
body: parsedPayload.data,
headers: {
Authorization: `Bearer ${ctx.serviceToken}`,
},
})
if (!apiResponse.ok) {
const text = apiResponse.text()
console.error(
"registerUser api error",
JSON.stringify({
query: input,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
return false
}
const json = await apiResponse.json()
console.log("json", json)
return true
} catch (error) {
return false
}
})

View File

@@ -0,0 +1,53 @@
.container {
display: grid;
gap: var(--Spacing-x3);
}
.title {
grid-area: title;
}
.form {
display: grid;
gap: var(--Spacing-x5);
grid-area: form;
}
.btnContainer {
display: flex;
flex-direction: column-reverse;
gap: var(--Spacing-x1);
grid-area: buttons;
}
@media screen and (min-width: 768px) {
.form {
grid-template-columns: 1fr;
}
.btnContainer {
align-self: center;
flex-direction: row;
gap: var(--Spacing-x2);
justify-self: flex-end;
}
}
.password,
.user,
.terms {
align-self: flex-start;
display: grid;
gap: var(--Spacing-x2);
}
.container {
display: grid;
gap: var(--Spacing-x2);
}
@media (min-width: 768px) {
.divider {
display: none;
}
}

View File

@@ -0,0 +1,156 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { registerUser } from "@/actions/registerUser"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Checkbox from "@/components/TempDesignSystem/Form/Checkbox"
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import DateSelect from "@/components/TempDesignSystem/Form/Date"
import Input from "@/components/TempDesignSystem/Form/Input"
import NewPassword from "@/components/TempDesignSystem/Form/NewPassword"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { RegisterSchema, registerSchema } from "./schema"
import styles from "./form.module.css"
export default function Form() {
const intl = useIntl()
const methods = useForm<RegisterSchema>({
defaultValues: {
firstName: "",
lastName: "",
email: "",
phoneNumber: "",
dateOfBirth: "",
address: {
countryCode: "",
zipCode: "",
},
password: "",
termsAccepted: false,
},
mode: "all",
criteriaMode: "all",
resolver: zodResolver(registerSchema),
reValidateMode: "onChange",
})
const country = intl.formatMessage({ id: "Country" })
const email = `${intl.formatMessage({ id: "Email" })} ${intl.formatMessage({ id: "Address" }).toLowerCase()}`
const phoneNumber = intl.formatMessage({ id: "Phone number" })
const zipCode = intl.formatMessage({ id: "Zip code" })
async function handleSubmit(data: RegisterSchema) {
console.log("submit", data)
const isSuccessResponse = await registerUser(data)
if (!isSuccessResponse) {
toast.error("Something went wrong!")
} else {
// TODO: Toast should be removed and we should show a different page
toast.success("Form submitted successfully")
// should we navigate to sub route like /signup/success ?
// router.push("/")
methods.reset()
}
}
return (
<section className={styles.container}>
<FormProvider {...methods}>
<form
/**
* Ignoring since ts doesn't recognize that tRPC
* parses FormData before reaching the route
* @ts-ignore */
action={registerUser}
className={styles.form}
id="register"
onSubmit={methods.handleSubmit(handleSubmit)}
>
<section className={styles.user}>
<div className={styles.container}>
<Input
label={"firstName"}
name="firstName"
registerOptions={{ required: true }}
/>
<Input
label={"lastName"}
name="lastName"
registerOptions={{ required: true }}
/>
</div>
<DateSelect
name="dateOfBirth"
registerOptions={{ required: true }}
/>
<div className={styles.container}>
<Input
label={zipCode}
name="address.zipCode"
registerOptions={{ required: true }}
/>
<CountrySelect
label={country}
name="address.countryCode"
registerOptions={{ required: true }}
/>
</div>
<Input
label={email}
name="email"
registerOptions={{ required: true }}
type="email"
/>
<Phone label={phoneNumber} name="phoneNumber" />
</section>
<Divider className={styles.divider} color="subtle" />
<section className={styles.password}>
<header>
<Body textTransform="bold">
{intl.formatMessage({ id: "Password" })}
</Body>
</header>
<NewPassword
name="password"
placeholder="Password"
label={intl.formatMessage({ id: "Password" })}
/>
</section>
<section className={styles.terms}>
<header>
<Body textTransform="bold">
{intl.formatMessage({ id: "Terms and conditions" })}
</Body>
</header>
<Checkbox name="termsAccepted" registerOptions={{ required: true }}>
<Body>
{intl.formatMessage({
id: "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with",
})}{" "}
<Link variant="underscored" color="peach80" href="/privacy">
{intl.formatMessage({ id: "Scandic's Privacy Policy." })}
</Link>
</Body>
</Checkbox>
</section>
<Button
type="submit"
intent="primary"
disabled={methods.formState.isSubmitting}
>
{intl.formatMessage({ id: "Sign up to Scandic Friends" })}
</Button>
</form>
</FormProvider>
</section>
)
}

View File

@@ -0,0 +1,35 @@
import { z } from "zod"
import { passwordValidator } from "@/utils/passwordValidator"
import { phoneValidator } from "@/utils/phoneValidator"
export const registerSchema = z.object({
firstName: z
.string()
.max(250)
.refine((value) => value.trim().length > 0, {
message: "First name is required",
}),
lastName: z
.string()
.max(250)
.refine((value) => value.trim().length > 0, {
message: "Last name is required",
}),
email: z.string().max(250).email(),
phoneNumber: phoneValidator(
"Phone is required",
"Please enter a valid phone number"
),
dateOfBirth: z.string().min(1),
address: z.object({
countryCode: z.string(),
zipCode: z.string().min(1),
}),
password: passwordValidator("Password is required"),
termsAccepted: z.boolean().refine((value) => value === true, {
message: "You must accept the terms and conditions",
}),
})
export type RegisterSchema = z.infer<typeof registerSchema>

View File

@@ -193,6 +193,7 @@
"Phone number": "Telefonnummer",
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
"Points": "Point",
"points": "Point",
"Points being calculated": "Point udregnes",
"Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021",
"Points may take up to 10 days to be displayed.": "Det kan tage op til 10 dage at få vist point.",
@@ -222,6 +223,7 @@
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Search": "Søge",
"Scandic's Privacy Policy.": "Scandic's integritetspolicy.",
"See all photos": "Se alle billeder",
"See hotel details": "Se hoteloplysninger",
"See room details": "Se værelsesdetaljer",
@@ -241,6 +243,7 @@
"Show map": "Vis kort",
"Show more": "Vis mere",
"Sign up bonus": "Velkomstbonus",
"Sign up to Scandic Friends": "Tilmeld dig Scandic Friends",
"Skip to main content": "Spring over og gå til hovedindhold",
"Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.",
@@ -252,6 +255,7 @@
"Summary": "Opsummering",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.",
"Terms and conditions": "Vilkår og betingelser",
"Thank you": "Tak",
"Theatre": "Teater",
"There are no transactions to display": "Der er ingen transaktioner at vise",
@@ -286,6 +290,7 @@
"Which room class suits you the best?": "Hvilken rumklasse passer bedst til dig",
"Year": "År",
"Yes, discard changes": "Ja, kasser ændringer",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg accepterer vilkårene for Scandic Friends og forstår, at Scandic vil behandle mine personlige oplysninger i henhold til",
"Yes, remove my card": "Ja, fjern mit kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
"You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.",

View File

@@ -193,6 +193,7 @@
"Phone number": "Telefonnummer",
"Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein",
"Points": "Punkte",
"points": "Punkte",
"Points being calculated": "Punkte werden berechnet",
"Points earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021",
"Points may take up to 10 days to be displayed.": "Es kann bis zu 10 Tage dauern, bis Punkte angezeigt werden.",
@@ -222,6 +223,7 @@
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Search": "Suchen",
"Scandic's Privacy Policy.": "Scandics Datenschutzrichtlinie.",
"See all photos": "Alle Fotos ansehen",
"See hotel details": "Hotelinformationen ansehen",
"See room details": "Zimmerdetails ansehen",
@@ -241,6 +243,7 @@
"Show map": "Karte anzeigen",
"Show more": "Mehr anzeigen",
"Sign up bonus": "Anmelde-Bonus",
"Sign up to Scandic Friends": "Treten Sie Scandic Friends bei",
"Skip to main content": "Direkt zum Inhalt",
"Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.",
@@ -252,6 +255,7 @@
"Summary": "Zusammenfassung",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.",
"Terms and conditions": "Geschäftsbedingungen",
"Thank you": "Danke",
"Theatre": "Theater",
"There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden",
@@ -286,6 +290,7 @@
"Which room class suits you the best?": "Welche Zimmerklasse passt am besten zu Ihnen?",
"Year": "Jahr",
"Yes, discard changes": "Ja, Änderungen verwerfen",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, ich akzeptiere die Geschäftsbedingungen für Scandic Friends und erkenne an, dass Scandic meine persönlichen Daten in Übereinstimmung mit",
"Yes, remove my card": "Ja, meine Karte entfernen",
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
"You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.",
@@ -327,3 +332,4 @@
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
"{width} cm × {length} cm": "{width} cm × {length} cm"
}

View File

@@ -221,6 +221,7 @@
"Save": "Save",
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Scandic's Privacy Policy.": "Scandic's Privacy Policy.",
"Search": "Search",
"See all photos": "See all photos",
"See hotel details": "See hotel details",
@@ -241,6 +242,7 @@
"Show map": "Show map",
"Show more": "Show more",
"Sign up bonus": "Sign up bonus",
"Sign up to Scandic Friends": "Sign up to Scandic Friends",
"Skip to main content": "Skip to main content",
"Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.",
@@ -252,6 +254,7 @@
"Summary": "Summary",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.",
"Terms and conditions": "Terms and conditions",
"Thank you": "Thank you",
"Theatre": "Theatre",
"There are no transactions to display": "There are no transactions to display",
@@ -286,6 +289,7 @@
"Which room class suits you the best?": "Which room class suits you the best?",
"Year": "Year",
"Yes, discard changes": "Yes, discard changes",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with",
"Yes, remove my card": "Yes, remove my card",
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
"You canceled adding a new credit card.": "You canceled adding a new credit card.",

View File

@@ -193,6 +193,7 @@
"Phone number": "Puhelinnumero",
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
"Points": "Pisteet",
"points": "pistettä",
"Points being calculated": "Pisteitä lasketaan",
"Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021",
"Points may take up to 10 days to be displayed.": "Pisteiden näyttäminen voi kestää jopa 10 päivää.",
@@ -223,6 +224,7 @@
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Search": "Haku",
"Scandic's Privacy Policy.": "Scandicin tietosuojavalmioksi.",
"See all photos": "Katso kaikki kuvat",
"See hotel details": "Katso hotellin tiedot",
"See room details": "Katso huoneen tiedot",
@@ -242,6 +244,7 @@
"Show map": "Näytä kartta",
"Show more": "Näytä lisää",
"Sign up bonus": "Liittymisbonus",
"Sign up to Scandic Friends": "Liity Scandic Friends -jäseneksi",
"Skip to main content": "Siirry pääsisältöön",
"Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.",
@@ -253,6 +256,7 @@
"Summary": "Yhteenveto",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.",
"Terms and conditions": "Käyttöehdot",
"Thank you": "Kiitos",
"Theatre": "Teatteri",
"There are no transactions to display": "Näytettäviä tapahtumia ei ole",
@@ -287,6 +291,7 @@
"Which room class suits you the best?": "Mikä huoneluokka sopii sinulle parhaiten?",
"Year": "Vuosi",
"Yes, discard changes": "Kyllä, hylkää muutokset",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Kyllä, hyväksyn Scandic Friends -käyttöehdot ja ymmärrän, että Scandic käsittelee minun henkilötietoni asianmukaisesti",
"Yes, remove my card": "Kyllä, poista korttini",
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
"You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.",
@@ -319,7 +324,6 @@
"nights": "yötä",
"number": "määrä",
"or": "tai",
"points": "pistettä",
"special character": "erikoishahmo",
"spendable points expiring by": "{points} pistettä vanhenee {date} mennessä",
"to": "to",

View File

@@ -221,6 +221,7 @@
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Search": "Søk",
"Scandic's Privacy Policy.": "Scandics integritetspolicy.",
"See all photos": "Se alle bilder",
"See hotel details": "Se hotellinformasjon",
"See room details": "Se detaljer om rommet",
@@ -240,6 +241,7 @@
"Show map": "Vis kart",
"Show more": "Vis mer",
"Sign up bonus": "Velkomstbonus",
"Sign up to Scandic Friends": "Bli med i Scandic Friends",
"Skip to main content": "Gå videre til hovedsiden",
"Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.",
@@ -251,6 +253,7 @@
"Summary": "Sammendrag",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.",
"Terms and conditions": "Vilkår og betingelser",
"Thank you": "Takk",
"Theatre": "Teater",
"There are no transactions to display": "Det er ingen transaksjoner å vise",
@@ -285,6 +288,7 @@
"Which room class suits you the best?": "Hvilken romklasse passer deg best?",
"Year": "År",
"Yes, discard changes": "Ja, forkast endringer",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg aksepterer vilkårene for Scandic Friends og forstår at Scandic vil behandle mine personlige opplysninger i henhold til",
"Yes, remove my card": "Ja, fjern kortet mitt",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
"You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.",

View File

@@ -222,6 +222,7 @@
"Scandic Friends Mastercard": "Scandic Friends Mastercard",
"Scandic Friends Point Shop": "Scandic Friends Point Shop",
"Search": "Sök",
"Scandic's Privacy Policy.": "Scandics integritetspolicy.",
"See all photos": "Se alla foton",
"See hotel details": "Se hotellinformation",
"See room details": "Se rumsdetaljer",
@@ -241,6 +242,7 @@
"Show map": "Visa karta",
"Show more": "Visa mer",
"Sign up bonus": "Välkomstbonus",
"Sign up to Scandic Friends": "Bli medlem i Scandic Friends",
"Skip to main content": "Fortsätt till huvudinnehåll",
"Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.",
"Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.",
@@ -252,6 +254,7 @@
"Summary": "Sammanfattning",
"TUI Points": "TUI Points",
"Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.",
"Terms and conditions": "Allmänna villkor",
"Thank you": "Tack",
"Theatre": "Teater",
"There are no transactions to display": "Det finns inga transaktioner att visa",
@@ -286,6 +289,7 @@
"Which room class suits you the best?": "Vilken rumsklass passar dig bäst?",
"Year": "År",
"Yes, discard changes": "Ja, ignorera ändringar",
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jag accepterar villkoren för Scandic Friends och förstår att Scandic kommer att bearbeta mina personliga uppgifter i enlighet med",
"Yes, remove my card": "Ja, ta bort mitt kort",
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
"You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.",

View File

@@ -140,6 +140,34 @@ export const serverActionProcedure = t.procedure.experimental_caller(
})
)
export const hotelServiceServerActionProcedure = serverActionProcedure.use(
async (opts) => {
const { access_token } = await fetchServiceToken("hotel")
if (!access_token) {
throw internalServerError("Failed to obtain service token")
}
return opts.next({
ctx: {
serviceToken: access_token,
},
})
}
)
export const profileServiceServerActionProcedure = serverActionProcedure.use(
async (opts) => {
const { access_token } = await fetchServiceToken("profile")
if (!access_token) {
throw internalServerError("Failed to obtain service token")
}
return opts.next({
ctx: {
serviceToken: access_token,
},
})
}
)
export const protectedServerActionProcedure = serverActionProcedure.use(
async (opts) => {
const session = await opts.ctx.auth()