Add IBAN validation using iban.js library
Implemented optional IBAN validation for the account form profile: - Installed iban.js package for robust IBAN validation - Added Zod validation with IBAN.isValid() for format checking - Normalizes IBAN (removes spaces, uppercase) before storage - Validates country-specific formats and checksums - Added validation error messages in English and Croatian 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { IntlTemplateFn } from '@/app/i18n';
|
|||||||
import { getTranslations, getLocale } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { gotoHomeWithMessage } from './navigationActions';
|
import { gotoHomeWithMessage } from './navigationActions';
|
||||||
|
import * as IBAN from 'iban';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
errors?: {
|
errors?: {
|
||||||
@@ -29,7 +30,17 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
|
|||||||
firstName: z.string().optional(),
|
firstName: z.string().optional(),
|
||||||
lastName: z.string().optional(),
|
lastName: z.string().optional(),
|
||||||
address: z.string().optional(),
|
address: z.string().optional(),
|
||||||
iban: 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") }
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +84,9 @@ export const updateUserProfile = withUser(async (user: AuthenticatedUser, prevSt
|
|||||||
|
|
||||||
const { firstName, lastName, address, iban } = validatedFields.data;
|
const { firstName, lastName, address, iban } = 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
|
// Update the user profile in MongoDB
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
const { id: userId } = user;
|
const { id: userId } = user;
|
||||||
@@ -82,7 +96,7 @@ export const updateUserProfile = withUser(async (user: AuthenticatedUser, prevSt
|
|||||||
firstName: firstName || null,
|
firstName: firstName || null,
|
||||||
lastName: lastName || null,
|
lastName: lastName || null,
|
||||||
address: address || null,
|
address: address || null,
|
||||||
iban: iban || null,
|
iban: normalizedIban,
|
||||||
};
|
};
|
||||||
|
|
||||||
await dbClient.collection<UserProfile>("users")
|
await dbClient.collection<UserProfile>("users")
|
||||||
|
|||||||
@@ -149,6 +149,8 @@
|
|||||||
"iban-placeholder": "Enter your IBAN",
|
"iban-placeholder": "Enter your IBAN",
|
||||||
"save-button": "Save",
|
"save-button": "Save",
|
||||||
"cancel-button": "Cancel",
|
"cancel-button": "Cancel",
|
||||||
"validation": {}
|
"validation": {
|
||||||
|
"iban-invalid": "Invalid IBAN format. Please enter a valid IBAN"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,6 +148,8 @@
|
|||||||
"iban-placeholder": "Unesite svoj IBAN",
|
"iban-placeholder": "Unesite svoj IBAN",
|
||||||
"save-button": "Spremi",
|
"save-button": "Spremi",
|
||||||
"cancel-button": "Odbaci",
|
"cancel-button": "Odbaci",
|
||||||
"validation": {}
|
"validation": {
|
||||||
|
"iban-invalid": "Neispravan IBAN format. Molimo unesite ispravan IBAN."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"@mui/icons-material": "^7.3.5",
|
"@mui/icons-material": "^7.3.5",
|
||||||
"@mui/material": "^7.3.5",
|
"@mui/material": "^7.3.5",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"@types/iban": "^0.0.35",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
"@zxing/browser": "^0.1.4",
|
"@zxing/browser": "^0.1.4",
|
||||||
"@zxing/library": "^0.20.0",
|
"@zxing/library": "^0.20.0",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"daisyui": "^4.5.0",
|
"daisyui": "^4.5.0",
|
||||||
|
"iban": "^0.0.14",
|
||||||
"is-ua-webview": "^1.1.2",
|
"is-ua-webview": "^1.1.2",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
"next": "^14.0.2",
|
"next": "^14.0.2",
|
||||||
@@ -1745,6 +1747,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/iban": {
|
||||||
|
"version": "0.0.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/iban/-/iban-0.0.35.tgz",
|
||||||
|
"integrity": "sha512-BOsp/b0ypIBnZdp1R8aP3n4w7I0n6vcObXtD0OT91lVSdo+Bx4VL26tW3yx1Dr9I4D5H3A27IOZoMVAdVfG4FQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -5013,6 +5021,12 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iban": {
|
||||||
|
"version": "0.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/iban/-/iban-0.0.14.tgz",
|
||||||
|
"integrity": "sha512-+rocNKk+Ga9m8Lr9fTMWd+87JnsBrucm0ZsIx5ROOarZlaDLmd+FKdbtvb0XyoBw9GAFOYG2GuLqoNB16d+p3w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@mui/icons-material": "^7.3.5",
|
"@mui/icons-material": "^7.3.5",
|
||||||
"@mui/material": "^7.3.5",
|
"@mui/material": "^7.3.5",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"@types/iban": "^0.0.35",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
"@zxing/browser": "^0.1.4",
|
"@zxing/browser": "^0.1.4",
|
||||||
"@zxing/library": "^0.20.0",
|
"@zxing/library": "^0.20.0",
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"daisyui": "^4.5.0",
|
"daisyui": "^4.5.0",
|
||||||
|
"iban": "^0.0.14",
|
||||||
"is-ua-webview": "^1.1.2",
|
"is-ua-webview": "^1.1.2",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
"next": "^14.0.2",
|
"next": "^14.0.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user