'use server'; import { z } from 'zod'; import { getDbClient } from '../dbClient'; import { UserProfile } from '../db-types'; import { withUser } from '@/app/lib/auth'; import { AuthenticatedUser } from '../types/next-auth'; import { unstable_noStore as noStore } from 'next/cache'; import { IntlTemplateFn } from '@/app/i18n'; import { getTranslations, getLocale } from "next-intl/server"; import { revalidatePath } from 'next/cache'; import { gotoHomeWithMessage } from './navigationActions'; import * as IBAN from 'iban'; export type State = { errors?: { firstName?: string[]; lastName?: string[]; address?: string[]; iban?: string[]; show2dCodeInMonthlyStatement?: string[]; }; message?: string | null; success?: boolean; }; /** * Schema for validating user profile form fields */ const FormSchema = (t: IntlTemplateFn) => z.object({ firstName: z.string().optional(), lastName: z.string().optional(), address: z.string().optional(), iban: z.string() .optional() .refine( (val) => { if (!val || val.trim() === '') return true; // Remove spaces and validate using iban.js library const cleaned = val.replace(/\s/g, '').toUpperCase(); return IBAN.isValid(cleaned); }, { message: t("iban-invalid") } ), show2dCodeInMonthlyStatement: z.boolean().optional().nullable(), }) .refine((data) => { if (data.show2dCodeInMonthlyStatement) { return !!data.firstName && data.firstName.trim().length > 0; } return true; }, { message: t("first-name-required"), path: ["firstName"], }) .refine((data) => { if (data.show2dCodeInMonthlyStatement) { return !!data.lastName && data.lastName.trim().length > 0; } return true; }, { message: t("last-name-required"), path: ["lastName"], }) .refine((data) => { if (data.show2dCodeInMonthlyStatement) { return !!data.address && data.address.trim().length > 0; } return true; }, { message: t("address-required"), path: ["address"], }) .refine((data) => { if (data.show2dCodeInMonthlyStatement) { if (!data.iban || data.iban.trim().length === 0) { return false; } // Validate IBAN format when required const cleaned = data.iban.replace(/\s/g, '').toUpperCase(); return IBAN.isValid(cleaned); } return true; }, { message: t("iban-required"), path: ["iban"], }); /** * Get user profile */ export const getUserProfile = withUser(async (user: AuthenticatedUser) => { noStore(); const dbClient = await getDbClient(); const { id: userId } = user; const profile = await dbClient.collection("users") .findOne({ userId }); return profile; }); /** * Update user profile */ export const updateUserProfile = withUser(async (user: AuthenticatedUser, prevState: State, formData: FormData) => { noStore(); const t = await getTranslations("settings-form.validation"); const validatedFields = FormSchema(t).safeParse({ firstName: formData.get('firstName') || undefined, lastName: formData.get('lastName') || undefined, address: formData.get('address') || undefined, iban: formData.get('iban') || undefined, show2dCodeInMonthlyStatement: formData.get('generateTenantCode') === 'on', }); // If form validation fails, return errors early. Otherwise, continue... if (!validatedFields.success) { return { errors: validatedFields.error.flatten().fieldErrors, message: t("validation-failed"), success: false, }; } const { firstName, lastName, address, iban, show2dCodeInMonthlyStatement } = validatedFields.data; // Normalize IBAN: remove spaces and convert to uppercase const normalizedIban = iban ? iban.replace(/\s/g, '').toUpperCase() : null; // Update the user profile in MongoDB const dbClient = await getDbClient(); const { id: userId } = user; const userProfile: UserProfile = { userId, firstName: firstName || null, lastName: lastName || null, address: address || null, iban: normalizedIban, show2dCodeInMonthlyStatement: show2dCodeInMonthlyStatement ?? false, }; await dbClient.collection("users") .updateOne( { userId }, { $set: userProfile }, { upsert: true } ); revalidatePath('/account'); // Get current locale and redirect to home with success message const locale = await getLocale(); await gotoHomeWithMessage(locale, 'profileSaved'); // This return is needed for TypeScript, but won't be reached due to redirect return { message: null, errors: {}, success: true, }; });