diff --git a/actions/registerUser.ts b/actions/registerUser.ts new file mode 100644 index 000000000..3bca2da81 --- /dev/null +++ b/actions/registerUser.ts @@ -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 + } + }) diff --git a/components/Forms/Register/form.module.css b/components/Forms/Register/form.module.css new file mode 100644 index 000000000..59358dc28 --- /dev/null +++ b/components/Forms/Register/form.module.css @@ -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; + } +} diff --git a/components/Forms/Register/index.tsx b/components/Forms/Register/index.tsx new file mode 100644 index 000000000..9494e59a7 --- /dev/null +++ b/components/Forms/Register/index.tsx @@ -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({ + 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 ( +
+ +
+
+
+ + +
+ +
+ + +
+ + +
+ +
+
+ + {intl.formatMessage({ id: "Password" })} + +
+ +
+
+
+ + {intl.formatMessage({ id: "Terms and conditions" })} + +
+ + + {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", + })}{" "} + + {intl.formatMessage({ id: "Scandic's Privacy Policy." })} + + + +
+ + +
+
+ ) +} diff --git a/components/Forms/Register/schema.ts b/components/Forms/Register/schema.ts new file mode 100644 index 000000000..982641d2c --- /dev/null +++ b/components/Forms/Register/schema.ts @@ -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 diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 57d493bf9..3f30b3f79 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -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.", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index ffb0f018b..6dbe84117 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -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" } + diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index fd989d49d..16e62c357 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -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.", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index 716a58765..0d6e9e617 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -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", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index d422258a6..a44ecc1e3 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -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.", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 51543e59c..5f72ef6f7 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -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.", diff --git a/server/trpc.ts b/server/trpc.ts index dafd26690..1000d053c 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -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()