"use server" import { z } from "zod" import * as api from "@scandic-hotels/trpc/api" import { ApiLang } from "@scandic-hotels/trpc/constants/apiLang" import { countriesMap } from "@scandic-hotels/trpc/constants/countries" import { getProfile } from "@/lib/trpc/memoizedRequests" import { protectedServerActionProcedure } from "@/server/trpc" import { editProfileSchema } from "@/components/Forms/Edit/Profile/schema" import { getIntl } from "@/i18n" import { phoneValidator } from "@/utils/zod/phoneValidator" import { Status } from "@/types/components/myPages/myProfile/edit" const editProfilePayload = z .object({ address: z.object({ city: z.string().optional(), countryCode: z.nativeEnum(countriesMap), streetAddress: z.string().optional(), zipCode: z.string().min(1, { message: "Zip code is required" }), }), dateOfBirth: z.string(), email: z.string().email(), language: z.nativeEnum(ApiLang), newPassword: z.string().optional(), password: z.string().optional(), phoneNumber: phoneValidator("Phone is required"), }) .transform((data) => { if (!data.password || !data.newPassword) { delete data.password delete data.newPassword } if (data.phoneNumber) { data.phoneNumber = data.phoneNumber.replaceAll(" ", "").trim() } return data }) export const editProfile = protectedServerActionProcedure .input(editProfileSchema) .mutation(async function ({ ctx, input }) { const intl = await getIntl() const payload = editProfilePayload.safeParse(input) if (!payload.success) { console.error( "editProfile payload validation error", JSON.stringify({ query: input, error: payload.error, }) ) return { data: input, issues: payload.error.issues.map((issue) => ({ field: issue.path.join("."), message: issue.message, })), message: intl.formatMessage({ defaultMessage: "An error occured when trying to update profile.", }), status: Status.error, } } const profile = await getProfile() if (!profile || "error" in profile) { console.error( "editProfile profile fetch error", JSON.stringify({ query: input, error: profile?.error, }) ) return { data: input, issues: [], message: intl.formatMessage({ defaultMessage: "An error occured when trying to update profile.", }), status: Status.error, } } const body: Partial> = {} Object.keys(payload.data).forEach((key) => { const typedKey = key as unknown as keyof z.infer< typeof editProfilePayload > if (typedKey === "password" || typedKey === "newPassword") { body[typedKey] = payload.data[typedKey] return } if (typedKey === "address") { if ( (payload.data.address.city === profile.address.city || (!payload.data.address.city && !profile.address.city)) && (payload.data.address.countryCode === profile.address.countryCode || (!payload.data.address.countryCode && !profile.address.countryCode)) && (payload.data.address.streetAddress === profile.address.streetAddress || (!payload.data.address.streetAddress && !profile.address.streetAddress)) && (payload.data.address.zipCode === profile.address.zipCode || (!payload.data.address.zipCode && !profile.address.zipCode)) ) { // untouched - noop } else { /** API clears all other fields not sent in address payload */ body.address = payload.data.address } return } // Handle case where dateOfBirth is missing in profile and not updated in form if ( typedKey === "dateOfBirth" && !profile.dateOfBirth && payload.data.dateOfBirth === "1900-01-01" ) { return } if (payload.data[typedKey] !== profile[typedKey]) { // @ts-ignore body[typedKey] = payload.data[typedKey] } }) if (Object.keys(body).length === 0) { return { data: input, message: intl.formatMessage({ defaultMessage: "Successfully updated profile!", }), status: Status.success, } } else { console.log( `[edit profile: ${profile.membershipNumber}] body keys: ${JSON.stringify(Object.keys(body))}` ) } const apiResponse = await api.patch(api.endpoints.v1.Profile.profile, { body, cache: "no-store", headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, }, }) if (!apiResponse.ok) { const text = await apiResponse.text() console.error( "editProfile api patch error", JSON.stringify({ query: input, error: { status: apiResponse.status, statusText: apiResponse.statusText, error: text, }, }) ) return { data: input, issues: [], message: intl.formatMessage({ defaultMessage: "An error occured when trying to update profile.", }), status: Status.error, } } const json = await apiResponse.json() if (json.errors?.length) { json.errors.forEach((error: any) => { console.warn( "editProfile api patch errors (not aborting)", JSON.stringify({ query: input, error, }) ) }) } const validatedData = editProfileSchema.safeParse(json.data.attributes) if (!validatedData.success) { console.error( "editProfile validation error", JSON.stringify({ query: input, error: validatedData.error, }) ) return { data: input, issues: validatedData.error.issues.map((issue) => ({ field: issue.path.join("."), message: issue.message, })), message: intl.formatMessage({ defaultMessage: "An error occured when trying to update profile.", }), status: Status.error, } } return { data: validatedData.data, message: intl.formatMessage({ defaultMessage: "Successfully updated profile!", }), status: Status.success, } })