Add town and currency fields to user settings

Adds two new fields to user settings form:
- Town field: Text input for city/town (required when 2D code enabled)
- Currency field: Select dropdown with ISO 4217 currency codes (EUR default)

Updates:
- Database schema: Added town and currency fields to UserSettings
- Validation: Both fields required when 2D code is enabled
- Form UI: Added input fields with proper validation and error handling
- Translations: Added Croatian and English labels and error messages
- Currency options: 36 ISO 4217 codes with EUR at top as default

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-11-22 15:14:19 +01:00
parent 795d9c690b
commit 3cf880a661
5 changed files with 126 additions and 2 deletions

View File

@@ -17,7 +17,9 @@ export type State = {
firstName?: string[];
lastName?: string[];
street?: string[];
town?: string[];
iban?: string[];
currency?: string[];
show2dCodeInMonthlyStatement?: string[];
};
message?: string | null;
@@ -31,6 +33,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
firstName: z.string().optional(),
lastName: z.string().optional(),
street: z.string().optional(),
town: z.string().optional(),
iban: z.string()
.optional()
.refine(
@@ -42,6 +45,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
},
{ message: t("iban-invalid") }
),
currency: z.string().optional(),
show2dCodeInMonthlyStatement: z.boolean().optional().nullable(),
})
.refine((data) => {
@@ -71,6 +75,15 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
message: t("street-required"),
path: ["street"],
})
.refine((data) => {
if (data.show2dCodeInMonthlyStatement) {
return !!data.town && data.town.trim().length > 0;
}
return true;
}, {
message: t("town-required"),
path: ["town"],
})
.refine((data) => {
if (data.show2dCodeInMonthlyStatement) {
if (!data.iban || data.iban.trim().length === 0) {
@@ -84,6 +97,15 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
}, {
message: t("iban-required"),
path: ["iban"],
})
.refine((data) => {
if (data.show2dCodeInMonthlyStatement) {
return !!data.currency && data.currency.trim().length > 0;
}
return true;
}, {
message: t("currency-required"),
path: ["currency"],
});
/**
@@ -113,7 +135,9 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS
firstName: formData.get('firstName') || undefined,
lastName: formData.get('lastName') || undefined,
street: formData.get('street') || undefined,
town: formData.get('town') || undefined,
iban: formData.get('iban') || undefined,
currency: formData.get('currency') || undefined,
show2dCodeInMonthlyStatement: formData.get('generateTenantCode') === 'on',
});
@@ -126,7 +150,7 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS
};
}
const { firstName, lastName, street, iban, show2dCodeInMonthlyStatement } = validatedFields.data;
const { firstName, lastName, street, town, iban, currency, show2dCodeInMonthlyStatement } = validatedFields.data;
// Normalize IBAN: remove spaces and convert to uppercase
const normalizedIban = iban ? iban.replace(/\s/g, '').toUpperCase() : null;
@@ -140,7 +164,9 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS
firstName: firstName || null,
lastName: lastName || null,
street: street || null,
town: town || null,
iban: normalizedIban,
currency: currency || null,
show2dCodeInMonthlyStatement: show2dCodeInMonthlyStatement ?? false,
};

View File

@@ -24,8 +24,12 @@ export interface UserSettings {
lastName?: string | null;
/** street */
street?: string | null;
/** town */
town?: string | null;
/** IBAN */
iban?: string | null;
/** currency (ISO 4217) */
currency?: string | null;
/** whether to show 2D code in monthly statement */
show2dCodeInMonthlyStatement?: boolean | null;
};