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:
2025-12-04 21:07:18 +03:30
parent c58210cdbd
commit 07fec02ea1
164 changed files with 20175 additions and 735 deletions
+60
View File
@@ -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;
+5
View File
@@ -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 };
};
}