feat: add logo image and RTL support styles
- Added logo image to assets. - Implemented RTL support styles in rtlSupport.scss for various components including breadcrumb, datatable, and tree node toggle button. chore: configure environment files for production, staging, and development - Created environment.prod.ts for production settings. - Created environment.staging.ts for staging settings. - Created environment.ts for local development settings. feat: define custom theme preset - Added presets.ts to define a custom theme preset using PrimeUIX with specific component styles and semantic colors. chore: set up proxy configuration for local development
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||
|
||||
function normalizeIban(input: string): string {
|
||||
return input.replace(/[^A-Za-z0-9]/g, '').toUpperCase();
|
||||
}
|
||||
|
||||
function ibanChecksum(iban: string): boolean {
|
||||
// Move first four chars to the end
|
||||
const rearranged = iban.slice(4) + iban.slice(0, 4);
|
||||
// Replace letters with numbers: A=10, B=11, ... Z=35
|
||||
let converted = '';
|
||||
for (let i = 0; i < rearranged.length; i++) {
|
||||
const ch = rearranged.charAt(i);
|
||||
if (/[A-Z]/.test(ch)) {
|
||||
converted += (ch.charCodeAt(0) - 55).toString();
|
||||
} else {
|
||||
converted += ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute mod 97 using chunking to avoid big integers
|
||||
let remainder = 0;
|
||||
const chunkSize = 9; // safe chunk length for JS numbers
|
||||
for (let offset = 0; offset < converted.length; offset += chunkSize) {
|
||||
const part = remainder.toString() + converted.substr(offset, chunkSize);
|
||||
remainder = parseInt(part, 10) % 97;
|
||||
}
|
||||
|
||||
return remainder === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iran-only IBAN (Sheba) validator.
|
||||
* Accepts values that normalize to a valid IR IBAN (country code IR, length 26,
|
||||
* and valid MOD-97 checksum).
|
||||
*/
|
||||
export function iranIbanValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const value = control.value;
|
||||
if (value == null || value === '') return null;
|
||||
if (typeof value !== 'string') return { iban: true };
|
||||
|
||||
const cleaned = normalizeIban(value);
|
||||
// IR IBAN total length is 26
|
||||
if (cleaned.length !== 26) return { iban: true };
|
||||
if (!cleaned.startsWith('IR')) return { iban: true };
|
||||
|
||||
// Basic pattern check: IR + 2 digits + rest alnum
|
||||
if (!/^IR[0-9]{2}[A-Z0-9]+$/.test(cleaned)) return { iban: true };
|
||||
|
||||
try {
|
||||
const ok = ibanChecksum(cleaned);
|
||||
return ok ? null : { iban: true };
|
||||
} catch (e) {
|
||||
return { iban: true };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default iranIbanValidator;
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './iban.validator';
|
||||
export * from './mobile.validator';
|
||||
export * from './must-match.validator';
|
||||
export * from './password.validator';
|
||||
export * from './postal-code.validator';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||
|
||||
/**
|
||||
* Validator for mobile phone numbers.
|
||||
*
|
||||
* Behavior/assumptions:
|
||||
* - Allows common Iranian mobile formats:
|
||||
* - 09xxxxxxxxx (leading 0, total 11 digits)
|
||||
* - +989xxxxxxxxx (international +98 followed by 9 and 9 digits)
|
||||
* - 9xxxxxxxxx (no leading 0, 10 digits)
|
||||
* - Treats empty / null / undefined values as valid (use Validators.required when needed).
|
||||
* - Returns `{ invalidMobile: true }` when the value does not match the pattern.
|
||||
*/
|
||||
export function mobileValidator(): ValidatorFn {
|
||||
const re = /^(?:(?:\+98)|0)?9\d{9}$/;
|
||||
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const v = control.value;
|
||||
if (v === null || v === undefined || v === '') {
|
||||
return null; // leave required validation to Validators.required
|
||||
}
|
||||
|
||||
if (typeof v !== 'string') {
|
||||
return { invalidMobile: true };
|
||||
}
|
||||
|
||||
const trimmed = v.trim();
|
||||
return re.test(trimmed) ? null : { invalidMobile: true };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||
|
||||
// Reusable validator that sets a 'mismatch' error on the matching control when values differ.
|
||||
export function MustMatch(controlName: string, matchingControlName: string): ValidatorFn {
|
||||
return (formGroup: AbstractControl): ValidationErrors | null => {
|
||||
const control = formGroup.get(controlName);
|
||||
const matchingControl = formGroup.get(matchingControlName);
|
||||
|
||||
if (!control || !matchingControl) return null;
|
||||
|
||||
// If another validator has already found an error on the matching control, don't overwrite it.
|
||||
const existingErrors = matchingControl.errors ? { ...matchingControl.errors } : null;
|
||||
if (existingErrors && !existingErrors['mismatch']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (control.value !== matchingControl.value) {
|
||||
matchingControl.setErrors({ ...(existingErrors ?? {}), mismatch: true });
|
||||
} else {
|
||||
if (existingErrors) {
|
||||
delete existingErrors['mismatch'];
|
||||
const remaining = Object.keys(existingErrors).length ? existingErrors : null;
|
||||
matchingControl.setErrors(remaining as any);
|
||||
} else {
|
||||
matchingControl.setErrors(null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ValidatorFn } from '@angular/forms';
|
||||
|
||||
// Password must be minimum 8 characters, include at least one uppercase,
|
||||
// one lowercase, one number and one special character
|
||||
export const PASSWORD_PATTERN = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
|
||||
|
||||
// Validator factory named `password` as requested. Returns `null` for empty
|
||||
// values so `Validators.required` can be used alongside it when needed.
|
||||
export function password(): ValidatorFn {
|
||||
return (control) => {
|
||||
const value = control?.value;
|
||||
if (value === null || value === undefined || String(value).length === 0) return null;
|
||||
return PASSWORD_PATTERN.test(String(value)) ? null : { password: 'معتبر نیست' };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ValidatorFn } from '@angular/forms';
|
||||
|
||||
export function postalCodeValidator(): ValidatorFn {
|
||||
return (control) => {
|
||||
if (control.value === null || control.value === undefined || control.value === '') {
|
||||
return null;
|
||||
}
|
||||
return control.value.length === 10 && /^[0-9]{10}$/.test(control.value)
|
||||
? null
|
||||
: { postalCode: true };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user