225 lines
6.4 KiB
TypeScript
225 lines
6.4 KiB
TypeScript
"use server"
|
|
|
|
import { z } from "zod"
|
|
|
|
import { ApiLang } from "@/constants/languages"
|
|
import * as api from "@/lib/api"
|
|
import { getProfile } from "@/lib/trpc/memoizedRequests"
|
|
import { protectedServerActionProcedure } from "@/server/trpc"
|
|
|
|
import { editProfileSchema } from "@/components/Forms/Edit/Profile/schema"
|
|
import { countriesMap } from "@/components/TempDesignSystem/Form/Country/countries"
|
|
import { getIntl } from "@/i18n"
|
|
import { getMembership } from "@/utils/user"
|
|
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({
|
|
id: "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({
|
|
id: "An error occured when trying to update profile.",
|
|
}),
|
|
status: Status.error,
|
|
}
|
|
}
|
|
|
|
const body: Partial<z.infer<typeof editProfilePayload>> = {}
|
|
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({ id: "Successfully updated profile!" }),
|
|
status: Status.success,
|
|
}
|
|
} else {
|
|
const membership = getMembership(profile.memberships)
|
|
console.log(
|
|
`[edit profile: ${membership?.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({
|
|
id: "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({
|
|
id: "An error occured when trying to update profile.",
|
|
}),
|
|
status: Status.error,
|
|
}
|
|
}
|
|
|
|
return {
|
|
data: validatedData.data,
|
|
message: intl.formatMessage({ id: "Successfully updated profile!" }),
|
|
status: Status.success,
|
|
}
|
|
})
|