Refactor phone input handling across onboarding and settings forms

- Replaced PhoneInput component with ControlledPhoneField for better integration with react-hook-form.
- Updated validation for phone numbers using isValidPhone function to ensure proper formatting.
- Removed country code handling from forms, simplifying phone number management.
- Introduced new phone field component with consistent styling and behavior.
- Added phone number validation on the backend using class-validator.
- Removed unused phone utility functions and cleaned up related code.
This commit is contained in:
Marshal
2026-06-21 10:34:40 +00:00
parent 171e02cf7f
commit dc708a2473
25 changed files with 645 additions and 568 deletions

View File

@@ -0,0 +1,53 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { isValidPhoneNumber, parsePhoneNumberFromString } from 'libphonenumber-js';
/**
* Country-aware phone validation. The value is expected as a full international
* number (E.164, e.g. "+251911223344"), so the country is derived from the
* value itself — no separate country field needed.
*/
@ValidatorConstraint({ name: 'IsValidPhone', async: false })
export class IsValidPhoneConstraint implements ValidatorConstraintInterface {
validate(value: unknown): boolean {
// Empty is allowed here; pair with @IsOptional / @IsNotEmpty as needed.
if (value === undefined || value === null || value === '') return true;
if (typeof value !== 'string') return false;
return isValidPhoneNumber(value);
}
defaultMessage(args: ValidationArguments): string {
return `${args.property} must be a valid international phone number (E.164, e.g. +251911223344)`;
}
}
/** Class-validator decorator wrapping the country-aware phone constraint. */
export function IsValidPhone(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [],
validator: IsValidPhoneConstraint,
});
};
}
/**
* Normalize a phone string to canonical E.164. Returns the canonical form when
* parseable, otherwise the trimmed original (tolerant — never throws), or the
* value unchanged when empty/nullish.
*/
export function normalizeE164(
value: string | null | undefined,
): string | null | undefined {
if (value === undefined || value === null || value === '') return value;
const parsed = parsePhoneNumberFromString(value);
return parsed?.isValid() ? parsed.number : value.trim();
}