This commit is contained in:
TulkasOrome 2025-05-23 12:59:06 +10:00
parent c2a24c7b69
commit c264b0a79a
41 changed files with 2615 additions and 305 deletions

View file

@ -1,5 +1,5 @@
{
"name": "ngx-admin",
"name": "identity-pulse",
"version": "11.0.0",
"license": "MIT",
"repository": {

View file

@ -6,7 +6,7 @@ import { NbThemeService } from '@nebular/theme';
styleUrls: ['./footer.component.scss'],
template: `
<span class="created-by">
Created with by <b><a href="https://marketsoft.com.au" target="_blank">Marketsoft</a></b> 2025
Powered by <b><a href="https://marketsoft.com.au" target="_blank">IdentityPulse</a></b> © 2025
</span>
<div class="socials">
<a href="#" target="_blank" class="ion ion-social-github"></a>

View file

@ -3,7 +3,7 @@
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
<nb-icon icon="menu-2-outline"></nb-icon>
</a>
<a class="logo" href="#" (click)="navigateHome()">ngx-<span>admin</span></a>
<a class="logo" href="#" (click)="navigateHome()">Identity<span>Pulse</span></a>
</div>
<nb-select [selected]="currentTheme" (selectedChange)="changeTheme($event)" status="primary">
<nb-option *ngFor="let theme of themes" [value]="theme.value"> {{ theme.name }}</nb-option>

View file

@ -40,7 +40,7 @@ themes = [
},
];
currentTheme = 'default';
currentTheme = 'marketsoft';
userMenu = [ { title: 'Profile' }, { title: 'Log out' } ];

View file

@ -2,56 +2,118 @@
<div class="col-12">
<nb-card>
<nb-card-header>
<h5>Available Countries</h5>
<h5>Supported Countries & Regions</h5>
</nb-card-header>
<nb-card-body>
<p>
Select from our available countries below to perform identity verification. Each country has different
data sources, update frequencies, and record counts.
</p>
<p class="subtitle">IdentityPulse provides comprehensive identity verification coverage across Asia-Pacific and expanding into the Middle East.</p>
</nb-card-body>
</nb-card>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-4" *ngFor="let country of countries">
<nb-card [status]="country.availability ? 'success' : 'basic'">
<div class="col-12">
<h6 class="section-title">Active Countries</h6>
</div>
<div class="col-lg-6 col-xl-4" *ngFor="let country of activeCountries">
<nb-card class="country-card" [class.active]="country.status === 'active'">
<nb-card-header>
<div class="d-flex align-items-center">
<span class="country-icon mr-2">{{ country.icon }}</span>
<span>{{ country.name }}</span>
<span class="ml-auto">
<nb-icon *ngIf="country.availability" icon="checkmark-circle-2-outline" status="success"></nb-icon>
<nb-icon *ngIf="!country.availability" icon="clock-outline" status="warning"></nb-icon>
</span>
<div class="country-header">
<div class="country-title">
<span class="flag">{{ country.flag }}</span>
<h6>{{ country.name }}</h6>
</div>
<nb-badge [text]="country.status" status="success"></nb-badge>
</div>
</nb-card-header>
<nb-card-body>
<div class="country-info">
<div class="info-item">
<strong>Records:</strong> {{ country.records }}
<div class="country-stats">
<div class="stat">
<span class="label">Coverage</span>
<span class="value">{{ country.coverage }}%</span>
</div>
<div class="info-item">
<strong>Updates:</strong> {{ country.updateFrequency }}
<div class="stat">
<span class="label">Population</span>
<span class="value">{{ country.population }}</span>
</div>
<div class="info-item">
<strong>Status:</strong> {{ country.availability ? 'Available' : 'Coming Soon' }}
<div class="stat">
<span class="label">Databases</span>
<span class="value">{{ country.databases }}</span>
</div>
<div class="stat">
<span class="label">Updates</span>
<span class="value">{{ country.updateFrequency }}</span>
</div>
</div>
<div class="features">
<h6>Available Features</h6>
<div class="feature-tags">
<span class="tag" *ngFor="let feature of country.features">{{ feature }}</span>
</div>
</div>
<div class="regulations">
<h6>Compliance</h6>
<div class="regulation-tags">
<span class="tag" *ngFor="let reg of country.regulations">{{ reg }}</span>
</div>
</div>
</nb-card-body>
<nb-card-footer *ngIf="country.availability">
<button nbButton fullWidth status="primary" [routerLink]="['/pages/identity/manual-lookup']" [queryParams]="{country: country.code}">
<nb-card-footer>
<button nbButton fullWidth status="primary" routerLink="/pages/identity/manual-lookup"
[queryParams]="{country: country.name.toLowerCase().replace(' ', '-')}">
<nb-icon icon="search-outline"></nb-icon>
Verify Identity
Verify in {{ country.name }}
</button>
</nb-card-footer>
<nb-card-footer *ngIf="!country.availability">
</nb-card>
</div>
</div>
<div class="row" *ngIf="comingSoonCountries.length > 0">
<div class="col-12">
<h6 class="section-title">Coming Soon</h6>
</div>
<div class="col-lg-6 col-xl-4" *ngFor="let country of comingSoonCountries">
<nb-card class="country-card coming-soon">
<nb-card-header>
<div class="country-header">
<div class="country-title">
<span class="flag">{{ country.flag }}</span>
<h6>{{ country.name }}</h6>
</div>
<nb-badge text="coming soon" status="warning"></nb-badge>
</div>
</nb-card-header>
<nb-card-body>
<div class="country-stats">
<div class="stat">
<span class="label">Population</span>
<span class="value">{{ country.population }}</span>
</div>
</div>
<div class="features">
<h6>Planned Features</h6>
<div class="feature-tags">
<span class="tag" *ngFor="let feature of country.features">{{ feature }}</span>
</div>
</div>
<div class="regulations">
<h6>Target Compliance</h6>
<div class="regulation-tags">
<span class="tag" *ngFor="let reg of country.regulations">{{ reg }}</span>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<button nbButton fullWidth status="basic" disabled>
<nb-icon icon="alert-triangle-outline"></nb-icon>
<nb-icon icon="clock-outline"></nb-icon>
Coming Soon
</button>
</nb-card-footer>
</nb-card>
</div>
</div>
</nb-card-body>
</nb-card>
</div>
</div>

View file

@ -1,12 +1,108 @@
.country-icon {
font-size: 1.5rem;
margin-right: 10px;
@import '../../@theme/styles/themes';
@include nb-install-component() {
.subtitle {
color: nb-theme(text-hint-color);
margin: 0;
}
.country-info {
margin-bottom: 15px;
.section-title {
margin: 2rem 0 1rem 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
.info-item {
margin-bottom: 5px;
.country-card {
height: 100%;
transition: all 0.3s ease;
&:hover:not(.coming-soon) {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
&.coming-soon {
opacity: 0.8;
}
.country-header {
display: flex;
justify-content: space-between;
align-items: center;
.country-title {
display: flex;
align-items: center;
gap: 0.75rem;
.flag {
font-size: 2rem;
}
h6 {
margin: 0;
font-weight: 600;
}
}
}
.country-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: nb-theme(background-basic-color-2);
border-radius: nb-theme(card-border-radius);
.stat {
display: flex;
flex-direction: column;
.label {
font-size: 0.75rem;
color: nb-theme(text-hint-color);
margin-bottom: 0.25rem;
}
.value {
font-size: 1.25rem;
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
}
.features,
.regulations {
margin-bottom: 1.5rem;
h6 {
font-size: 0.875rem;
font-weight: 600;
margin: 0 0 0.75rem 0;
color: nb-theme(text-basic-color);
}
.feature-tags,
.regulation-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
.tag {
display: inline-block;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
background: nb-theme(background-basic-color-3);
border-radius: 1rem;
color: nb-theme(text-hint-color);
}
}
}
nb-card-footer {
padding: 1rem;
}
}
}

View file

@ -1,65 +1,82 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
interface Country {
name: string;
flag: string;
status: 'active' | 'coming-soon';
coverage: number;
population: string;
databases: number;
updateFrequency: string;
regulations: string[];
features: string[];
}
@Component({
selector: 'ngx-countries',
templateUrl: './countries.component.html',
styleUrls: ['./countries.component.scss']
})
export class CountriesComponent implements OnInit {
export class CountriesComponent {
countries = [
countries: Country[] = [
{
name: 'Australia',
code: 'aus',
records: '15M+',
updateFrequency: 'Weekly',
availability: true,
icon: '🇦🇺'
flag: '🇦🇺',
status: 'active',
coverage: 95,
population: '25.7M',
databases: 5,
updateFrequency: 'Daily',
regulations: ['Privacy Act 1988', 'AML/CTF Act', 'CDR'],
features: ['Full KYC', 'Document Verification', 'Biometric Matching', 'Watchlist Screening']
},
{
name: 'Indonesia',
code: 'indo',
records: '120M+',
updateFrequency: 'Monthly',
availability: true,
icon: '🇮🇩'
},
{
name: 'Japan',
code: 'japan',
records: '85M+',
updateFrequency: 'Bi-weekly',
availability: true,
icon: '🇯🇵'
flag: '🇮🇩',
status: 'active',
coverage: 82,
population: '273.5M',
databases: 4,
updateFrequency: 'Weekly',
regulations: ['UU PDP', 'OJK Regulations'],
features: ['Identity Verification', 'Address Validation', 'Phone Verification', 'KTP Validation']
},
{
name: 'Malaysia',
code: 'malay',
records: '20M+',
flag: '🇲🇾',
status: 'active',
coverage: 78,
population: '32.4M',
databases: 3,
updateFrequency: 'Weekly',
availability: true,
icon: '🇲🇾'
regulations: ['PDPA 2010', 'AMLA'],
features: ['MyKad Verification', 'Address Check', 'Phone Validation', 'Business Registry']
},
{
name: 'Singapore',
code: 'sg',
records: '5M+',
updateFrequency: 'Weekly',
availability: true,
icon: '🇸🇬'
name: 'Japan',
flag: '🇯🇵',
status: 'active',
coverage: 88,
population: '125.8M',
databases: 4,
updateFrequency: 'Daily',
regulations: ['APPI', 'FIEA'],
features: ['My Number Verification', 'Address Validation', 'Corporate Registry', 'AML Check']
},
{
name: 'Thailand',
code: 'thai',
records: '45M+',
updateFrequency: 'Monthly',
availability: false,
icon: '🇹🇭'
},
name: 'Saudi Arabia',
flag: '🇸🇦',
status: 'coming-soon',
coverage: 0,
population: '34.8M',
databases: 0,
updateFrequency: 'TBD',
regulations: ['PDPL', 'SAMA Regulations'],
features: ['National ID Verification', 'Iqama Validation', 'Business Registry', 'AML Screening']
}
];
constructor() { }
ngOnInit(): void {
}
activeCountries = this.countries.filter(c => c.status === 'active');
comingSoonCountries = this.countries.filter(c => c.status === 'coming-soon');
}

View file

@ -1,21 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { NbCardModule, NbIconModule, NbListModule } from '@nebular/theme';
import {
NbCardModule,
NbButtonModule,
NbIconModule,
NbBadgeModule
} from '@nebular/theme';
import { CountriesComponent } from './countries.component';
@NgModule({
imports: [
CommonModule,
NbCardModule,
NbIconModule,
NbListModule,
RouterModule.forChild([
{
path: '',
component: CountriesComponent,
},
]),
NbCardModule,
NbButtonModule,
NbIconModule,
NbBadgeModule
],
declarations: [
CountriesComponent,

View file

@ -0,0 +1,42 @@
<nb-card>
<nb-card-header>
<h6>Trusted by Industry Leaders</h6>
</nb-card-header>
<nb-card-body>
<div class="clients-grid">
<div class="client-card" *ngFor="let client of clients">
<div class="client-logo">
<div class="logo-placeholder">
{{ client.name }}
</div>
</div>
<div class="client-info">
<h6 class="client-name">{{ client.name }}</h6>
<span class="client-industry">{{ client.industry }}</span>
<p class="client-description">{{ client.description }}</p>
<div class="verification-count">
<nb-icon icon="checkmark-circle-2-outline" pack="eva"></nb-icon>
<span>{{ client.verifications }} verifications</span>
</div>
</div>
</div>
</div>
<div class="showcase-footer">
<div class="trust-metrics">
<div class="metric">
<span class="metric-value">50+</span>
<span class="metric-label">Enterprise Clients</span>
</div>
<div class="metric">
<span class="metric-value">15M+</span>
<span class="metric-label">Monthly Verifications</span>
</div>
<div class="metric">
<span class="metric-value">99.9%</span>
<span class="metric-label">Uptime SLA</span>
</div>
</div>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,137 @@
@import '../../../@theme/styles/themes';
@import 'bootstrap/scss/mixins/breakpoints';
@import '@nebular/theme/styles/global/breakpoints';
@include nb-install-component() {
nb-card {
height: 100%;
}
nb-card-header {
h6 {
margin: 0;
font-weight: 600;
}
}
.clients-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 2rem;
@include media-breakpoint-down(sm) {
grid-template-columns: 1fr;
}
}
.client-card {
display: flex;
align-items: center;
padding: 1rem;
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
transition: all 0.3s ease;
cursor: pointer;
&:hover {
border-color: nb-theme(color-primary-default);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.client-logo {
flex-shrink: 0;
width: 60px;
height: 60px;
margin-right: 1rem;
.logo-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: nb-theme(background-basic-color-3);
border-radius: nb-theme(card-border-radius);
font-size: 0.625rem;
font-weight: 600;
text-align: center;
padding: 0.5rem;
color: nb-theme(text-hint-color);
}
}
.client-info {
flex: 1;
min-width: 0;
.client-name {
margin: 0 0 0.25rem 0;
font-size: 0.875rem;
font-weight: 600;
color: nb-theme(text-basic-color);
}
.client-industry {
display: inline-block;
font-size: 0.75rem;
color: nb-theme(text-hint-color);
margin-bottom: 0.5rem;
}
.client-description {
font-size: 0.75rem;
color: nb-theme(text-hint-color);
margin: 0 0 0.5rem 0;
display: none;
@include media-breakpoint-up(lg) {
display: block;
}
}
.verification-count {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: nb-theme(color-success-default);
nb-icon {
font-size: 1rem;
}
}
}
.showcase-footer {
border-top: 1px solid nb-theme(border-basic-color-3);
padding-top: 1.5rem;
margin-top: 1rem;
}
.trust-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
text-align: center;
.metric {
display: flex;
flex-direction: column;
.metric-value {
font-size: 1.5rem;
font-weight: 700;
color: nb-theme(color-primary-default);
margin-bottom: 0.25rem;
}
.metric-label {
font-size: 0.75rem;
color: nb-theme(text-hint-color);
}
}
}
}

View file

@ -0,0 +1,79 @@
import { Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
interface Client {
name: string;
logo: string;
industry: string;
description: string;
verifications: string;
}
@Component({
selector: 'ngx-client-showcase',
styleUrls: ['./client-showcase.component.scss'],
templateUrl: './client-showcase.component.html',
})
export class ClientShowcaseComponent implements OnDestroy {
private alive = true;
currentTheme: string;
clients: Client[] = [
{
name: 'LexisNexis',
logo: 'assets/images/clients/lexisnexis.png',
industry: 'Risk Solutions',
description: 'Global leader in legal and business information',
verifications: '2.3M+'
},
{
name: 'Data Zoo',
logo: 'assets/images/clients/datazoo.png',
industry: 'Identity Verification',
description: 'Identity verification and fraud prevention',
verifications: '1.8M+'
},
{
name: 'Commonwealth Bank',
logo: 'assets/images/clients/cba.png',
industry: 'Banking',
description: 'Australia\'s leading financial institution',
verifications: '3.1M+'
},
{
name: 'Westpac',
logo: 'assets/images/clients/westpac.png',
industry: 'Banking',
description: 'Major Australian bank and financial services',
verifications: '2.7M+'
},
{
name: 'Telstra',
logo: 'assets/images/clients/telstra.png',
industry: 'Telecommunications',
description: 'Australia\'s largest telecommunications company',
verifications: '1.5M+'
},
{
name: 'ANZ Bank',
logo: 'assets/images/clients/anz.png',
industry: 'Banking',
description: 'Leading bank across Australia and New Zealand',
verifications: '2.4M+'
}
];
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -0,0 +1,40 @@
<nb-card>
<nb-card-header>
<h6>Compliance & Data Residency</h6>
</nb-card-header>
<nb-card-body>
<div class="compliance-list">
<div class="compliance-item" *ngFor="let item of complianceItems">
<div class="item-header">
<span class="region">{{ item.region }}</span>
<nb-icon
[icon]="getStatusIcon(item.status)"
[status]="getStatusColor(item.status)"
pack="eva">
</nb-icon>
</div>
<div class="regulations">
<span class="regulation" *ngFor="let reg of item.regulations">
{{ reg }}
</span>
</div>
<div class="hosting-model">
<nb-icon icon="hard-drive-outline" pack="eva"></nb-icon>
<span>{{ item.hostingModel }}</span>
</div>
</div>
</div>
<div class="certifications">
<h6>Certifications</h6>
<div class="cert-badges">
<div class="badge" *ngFor="let cert of certifications">
<nb-icon [icon]="cert.icon" pack="eva" status="primary"></nb-icon>
<span>{{ cert.name }}</span>
</div>
</div>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,92 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card-header h6 {
margin: 0;
font-weight: 600;
}
.compliance-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.compliance-item {
padding: 1rem;
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
.region {
font-weight: 600;
color: nb-theme(text-basic-color);
}
nb-icon {
font-size: 1.25rem;
}
}
.regulations {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
.regulation {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
background: nb-theme(background-basic-color-3);
border-radius: nb-theme(border-radius);
color: nb-theme(text-hint-color);
}
}
.hosting-model {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: nb-theme(text-hint-color);
nb-icon {
font-size: 1rem;
}
}
}
.certifications {
h6 {
margin: 0 0 0.75rem 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
.cert-badges {
display: flex;
flex-direction: column;
gap: 0.5rem;
.badge {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: nb-theme(background-basic-color-2);
border-radius: nb-theme(card-border-radius);
font-size: 0.875rem;
nb-icon {
font-size: 1.25rem;
}
}
}
}
}

View file

@ -0,0 +1,75 @@
import { Component } from '@angular/core';
interface ComplianceItem {
region: string;
regulations: string[];
status: 'compliant' | 'in-progress' | 'planned';
hostingModel: string;
}
@Component({
selector: 'ngx-compliance-summary',
styleUrls: ['./compliance-summary.component.scss'],
templateUrl: './compliance-summary.component.html',
})
export class ComplianceSummaryComponent {
complianceItems: ComplianceItem[] = [
{
region: 'Australia',
regulations: ['Privacy Act 1988', 'AML/CTF Act', 'CDR'],
status: 'compliant',
hostingModel: 'Local data residency'
},
{
region: 'Indonesia',
regulations: ['UU PDP', 'OJK Regulations'],
status: 'compliant',
hostingModel: 'Local data center'
},
{
region: 'Malaysia',
regulations: ['PDPA 2010', 'AMLA'],
status: 'compliant',
hostingModel: 'Regional hosting'
},
{
region: 'Japan',
regulations: ['APPI', 'FIEA'],
status: 'compliant',
hostingModel: 'Local data residency'
}
];
certifications = [
{ name: 'ISO 27001', icon: 'shield-outline' },
{ name: 'SOC 2 Type II', icon: 'lock-outline' },
{ name: 'GDPR Ready', icon: 'checkmark-square-outline' }
];
getStatusColor(status: string): string {
switch (status) {
case 'compliant':
return 'success';
case 'in-progress':
return 'warning';
case 'planned':
return 'info';
default:
return 'basic';
}
}
getStatusIcon(status: string): string {
switch (status) {
case 'compliant':
return 'checkmark-circle-2-outline';
case 'in-progress':
return 'sync-outline';
case 'planned':
return 'calendar-outline';
default:
return 'minus-circle-outline';
}
}
}

View file

@ -0,0 +1,44 @@
<nb-card size="large">
<nb-card-header>
<span>Country Coverage & Availability</span>
<nb-select [(selected)]="selectedRegion" class="region-select">
<nb-option value="all">All Regions</nb-option>
<nb-option value="apac">APAC</nb-option>
<nb-option value="mena">MENA</nb-option>
<nb-option value="europe">Europe</nb-option>
</nb-select>
</nb-card-header>
<nb-card-body>
<div class="country-list">
<div class="country-item" *ngFor="let country of countries">
<div class="country-header">
<div class="country-info">
<h6 class="country-name">{{ country.name }}</h6>
<nb-icon
[icon]="getStatusIcon(country.status)"
[status]="getStatusColor(country.status)"
pack="eva">
</nb-icon>
</div>
<div class="country-stats">
<span class="records">{{ country.records }} records</span>
<span class="update-freq">{{ country.updateFrequency }} updates</span>
</div>
</div>
<div class="coverage-section">
<div class="coverage-header">
<span class="coverage-label">Coverage</span>
<span class="coverage-value">{{ country.coverage }}%</span>
</div>
<nb-progress-bar
[value]="country.coverage"
[status]="getCoverageStatus(country.coverage)"
size="tiny">
</nb-progress-bar>
</div>
</div>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,117 @@
@import '../../../@theme/styles/themes';
@import 'bootstrap/scss/mixins/breakpoints';
@import '@nebular/theme/styles/global/breakpoints';
@include nb-install-component() {
nb-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: nb-theme(card-header-with-select-padding-top);
padding-bottom: nb-theme(card-header-with-select-padding-bottom);
}
.region-select {
margin-left: auto;
min-width: 8rem;
}
.country-list {
max-height: 400px;
overflow-y: auto;
padding-right: 0.5rem;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: nb-theme(background-basic-color-2);
}
&::-webkit-scrollbar-thumb {
background: nb-theme(background-basic-color-4);
border-radius: 3px;
}
}
.country-item {
padding: 1rem;
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
margin-bottom: 1rem;
transition: all 0.3s ease;
&:hover {
border-color: nb-theme(color-primary-default);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&:last-child {
margin-bottom: 0;
}
}
.country-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.75rem;
}
.country-info {
display: flex;
align-items: center;
gap: 0.5rem;
.country-name {
margin: 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
nb-icon {
font-size: 1.25rem;
}
}
.country-stats {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.25rem;
span {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
}
}
.coverage-section {
.coverage-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
.coverage-label {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
}
.coverage-value {
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
nb-progress-bar {
height: 0.5rem;
}
}
@include media-breakpoint-down(sm) {
.country-stats {
display: none;
}
}
}

View file

@ -0,0 +1,101 @@
import { Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
interface CountryData {
name: string;
coverage: number;
records: string;
updateFrequency: string;
status: 'active' | 'coming-soon' | 'limited';
}
@Component({
selector: 'ngx-country-coverage',
styleUrls: ['./country-coverage.component.scss'],
templateUrl: './country-coverage.component.html',
})
export class CountryCoverageComponent implements OnDestroy {
private alive = true;
countries: CountryData[] = [
{
name: 'Australia',
coverage: 95,
records: '25.6M',
updateFrequency: 'Daily',
status: 'active'
},
{
name: 'Indonesia',
coverage: 82,
records: '180.2M',
updateFrequency: 'Weekly',
status: 'active'
},
{
name: 'Malaysia',
coverage: 78,
records: '28.4M',
updateFrequency: 'Weekly',
status: 'active'
},
{
name: 'Japan',
coverage: 88,
records: '126.8M',
updateFrequency: 'Daily',
status: 'active'
},
];
selectedRegion = 'all';
regions = ['all', 'apac', 'mena', 'europe'];
currentTheme: string;
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.currentTheme = theme.name;
});
}
ngOnDestroy() {
this.alive = false;
}
getStatusIcon(status: string): string {
switch (status) {
case 'active':
return 'checkmark-circle-2-outline';
case 'coming-soon':
return 'clock-outline';
case 'limited':
return 'alert-triangle-outline';
default:
return 'minus-circle-outline';
}
}
getStatusColor(status: string): string {
switch (status) {
case 'active':
return 'success';
case 'coming-soon':
return 'warning';
case 'limited':
return 'danger';
default:
return 'basic';
}
}
getCoverageStatus(coverage: number): string {
if (coverage >= 80) return 'success';
if (coverage >= 60) return 'warning';
return 'danger';
}
}

View file

@ -1,42 +1,45 @@
<div class="row">
<div class="col-xxxl-3 col-md-6" *ngFor="let statusCard of statusCards">
<ngx-status-card [title]="statusCard.title" [type]="statusCard.type">
<i [ngClass]="statusCard.iconClass"></i>
</ngx-status-card>
<ngx-metric-card
[title]="statusCard.title"
[type]="statusCard.type"
[value]="statusCard.value"
[unitOfMeasurement]="statusCard.unitOfMeasurement"
[iconClass]="statusCard.iconClass">
</ngx-metric-card>
</div>
</div>
<div class="row">
<div class="col-xxxl-3 col-xxl-4 col-lg-5 col-md-6">
<ngx-temperature></ngx-temperature>
<div class="col-xxxl-6 col-xxl-7 col-lg-7 col-md-12">
<ngx-country-coverage></ngx-country-coverage>
</div>
<div class="col-xxxl-9 col-xxl-8 col-lg-7 col-md-6">
<ngx-electricity></ngx-electricity>
<div class="col-xxxl-6 col-xxl-5 col-lg-5 col-md-12">
<ngx-client-showcase></ngx-client-showcase>
</div>
</div>
<div class="row mt-4">
<div class="col-xxxl-8 col-xl-12">
<ngx-data-centers-map></ngx-data-centers-map>
</div>
<div class="col-xxxl-4 col-xxl-4 col-lg-5 col-md-6">
<ngx-recent-verifications></ngx-recent-verifications>
</div>
</div>
<div class="row">
<div class="col-xxxl-9 col-xl-12">
<ngx-rooms></ngx-rooms>
<div class="col-xxxl-4 col-xxl-4 col-lg-4 col-md-6">
<ngx-pricing-regions></ngx-pricing-regions>
</div>
<div class="col-xxxl-3 col-xxl-4 col-lg-7 col-md-6">
<ngx-contacts></ngx-contacts>
<div class="col-xxxl-4 col-xxl-4 col-lg-4 col-md-6">
<ngx-compliance-summary></ngx-compliance-summary>
</div>
<div class="col-xxxl-3 col-xxl-4 col-lg-5 col-md-6">
<ngx-solar [chartValue]="solarValue"></ngx-solar>
<ngx-kitten></ngx-kitten>
</div>
<div class="col-xxxl-3 col-xxl-4 col-md-5">
<ngx-traffic></ngx-traffic>
<ngx-weather></ngx-weather>
</div>
<div class="col-xxxl-6 col-xxl-12 col-md-7">
<ngx-security-cameras></ngx-security-cameras>
<div class="col-xxxl-4 col-xxl-4 col-lg-4 col-md-12">
<ngx-differentiators></ngx-differentiators>
</div>
</div>

View file

@ -1,10 +1,11 @@
import { Component, OnDestroy } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { takeWhile } from 'rxjs/operators';
import { SolarData } from '../../@core/data/solar';
interface CardSettings {
title: string;
value: string;
unitOfMeasurement: string;
iconClass: string;
type: string;
}
@ -18,35 +19,46 @@ export class DashboardComponent implements OnDestroy {
private alive = true;
solarValue: number;
lightCard: CardSettings = {
title: 'Light',
iconClass: 'nb-lightbulb',
// Key metrics for IdentityPulse
verificationCard: CardSettings = {
title: 'Total Verifications',
value: '1,247',
unitOfMeasurement: 'today',
iconClass: 'nb-checkmark-circle',
type: 'primary',
};
rollerShadesCard: CardSettings = {
title: 'Roller Shades',
iconClass: 'nb-roller-shades',
matchRateCard: CardSettings = {
title: 'Average Match Rate',
value: '87.3',
unitOfMeasurement: '%',
iconClass: 'nb-bar-chart',
type: 'success',
};
wirelessAudioCard: CardSettings = {
title: 'Wireless Audio',
iconClass: 'nb-audio',
countriesCard: CardSettings = {
title: 'Active Countries',
value: '4',
unitOfMeasurement: 'regions',
iconClass: 'nb-location',
type: 'info',
};
coffeeMakerCard: CardSettings = {
title: 'Coffee Maker',
iconClass: 'nb-coffee-maker',
apiResponseCard: CardSettings = {
title: 'API Response Time',
value: '245',
unitOfMeasurement: 'ms',
iconClass: 'nb-gear',
type: 'warning',
};
statusCards: string;
statusCards: CardSettings[];
commonStatusCardsSet: CardSettings[] = [
this.lightCard,
this.rollerShadesCard,
this.wirelessAudioCard,
this.coffeeMakerCard,
this.verificationCard,
this.matchRateCard,
this.countriesCard,
this.apiResponseCard,
];
statusCardsByThemes: {
@ -54,43 +66,21 @@ export class DashboardComponent implements OnDestroy {
cosmic: CardSettings[];
corporate: CardSettings[];
dark: CardSettings[];
marketsoft: CardSettings[];
} = {
default: this.commonStatusCardsSet,
cosmic: this.commonStatusCardsSet,
corporate: [
{
...this.lightCard,
type: 'warning',
},
{
...this.rollerShadesCard,
type: 'primary',
},
{
...this.wirelessAudioCard,
type: 'danger',
},
{
...this.coffeeMakerCard,
type: 'info',
},
],
corporate: this.commonStatusCardsSet,
dark: this.commonStatusCardsSet,
marketsoft: this.commonStatusCardsSet,
};
constructor(private themeService: NbThemeService,
private solarService: SolarData) {
constructor(private themeService: NbThemeService) {
this.themeService.getJsTheme()
.pipe(takeWhile(() => this.alive))
.subscribe(theme => {
this.statusCards = this.statusCardsByThemes[theme.name];
});
this.solarService.getSolarData()
.pipe(takeWhile(() => this.alive))
.subscribe((data) => {
this.solarValue = data;
});
}
ngOnDestroy() {

View file

@ -9,6 +9,7 @@ import {
NbSelectModule,
NbListModule,
NbIconModule,
NbProgressBarModule,
} from '@nebular/theme';
import { NgxEchartsModule } from 'ngx-echarts';
@ -31,6 +32,19 @@ import { TrafficComponent } from './traffic/traffic.component';
import { TrafficChartComponent } from './traffic/traffic-chart.component';
import { FormsModule } from '@angular/forms';
// IdentityPulse components
import { MetricCardComponent } from './metric-card/metric-card.component';
import { CountryCoverageComponent } from './country-coverage/country-coverage.component';
import { ClientShowcaseComponent } from './client-showcase/client-showcase.component';
import { DataCentersMapComponent } from './data-centers-map/data-centers-map.component';
import { RecentVerificationsComponent } from './recent-verifications/recent-verifications.component';
import { PricingRegionsComponent } from './pricing-regions/pricing-regions.component';
import { ComplianceSummaryComponent } from './compliance-summary/compliance-summary.component';
import { DifferentiatorsComponent } from './differentiators/differentiators.component';
// Note: ECharts is already available globally through ngx-echarts
// The world map is registered by ngx-admin's theme module
@NgModule({
imports: [
FormsModule,
@ -45,6 +59,7 @@ import { FormsModule } from '@angular/forms';
NbListModule,
NbIconModule,
NbButtonModule,
NbProgressBarModule,
NgxEchartsModule,
],
declarations: [
@ -64,6 +79,15 @@ import { FormsModule } from '@angular/forms';
SolarComponent,
TrafficComponent,
TrafficChartComponent,
// IdentityPulse components
MetricCardComponent,
CountryCoverageComponent,
ClientShowcaseComponent,
DataCentersMapComponent,
RecentVerificationsComponent,
PricingRegionsComponent,
ComplianceSummaryComponent,
DifferentiatorsComponent,
],
})
export class DashboardModule { }

View file

@ -0,0 +1,38 @@
<nb-card>
<nb-card-header>
<div class="header-container">
<h6>Global Identity Verification Coverage</h6>
<div class="legend">
<span class="legend-item">
<span class="dot online"></span>
Online
</span>
<span class="legend-item">
<span class="dot maintenance"></span>
Maintenance
</span>
<span class="legend-item">
<span class="dot offline"></span>
Offline
</span>
</div>
</div>
</nb-card-header>
<nb-card-body>
<div id="map" class="map-container"></div>
<div class="map-stats">
<div class="stat">
<nb-icon icon="globe-2-outline" pack="eva"></nb-icon>
<span>{{ getCountryCount() }} Countries</span>
</div>
<div class="stat">
<nb-icon icon="hard-drive-outline" pack="eva"></nb-icon>
<span>{{ getTotalDatabases() }} Databases</span>
</div>
<div class="stat">
<nb-icon icon="activity-outline" pack="eva"></nb-icon>
<span>{{ getOnlineCount() }} Online</span>
</div>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,88 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card {
height: 450px;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
h6 {
margin: 0;
font-weight: 600;
}
}
.legend {
display: flex;
gap: 1rem;
.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: nb-theme(text-hint-color);
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
&.online {
background-color: #00d68f;
}
&.maintenance {
background-color: #ffaa00;
}
&.offline {
background-color: #ff3d71;
}
}
}
}
nb-card-body {
position: relative;
padding: 0;
}
.map-container {
width: 100%;
height: 350px;
z-index: 1;
}
.map-stats {
position: absolute;
bottom: 1rem;
left: 1rem;
background: nb-theme(background-basic-color-1);
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
padding: 0.75rem 1rem;
display: flex;
gap: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000; // Ensure stats appear above the map
.stat {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: nb-theme(text-basic-color);
nb-icon {
font-size: 1.25rem;
color: nb-theme(color-primary-default);
}
}
}
}

View file

@ -0,0 +1,215 @@
import { Component, OnDestroy, AfterViewInit } from '@angular/core';
import { NbThemeService } from '@nebular/theme';
import { Router } from '@angular/router';
import { takeWhile } from 'rxjs/operators';
declare const L: any;
interface DataCenter {
name: string;
country: string;
city: string;
coordinates: [number, number];
status: 'online' | 'maintenance' | 'offline';
databases: number;
responseTime: number;
}
@Component({
selector: 'ngx-data-centers-map',
styleUrls: ['./data-centers-map.component.scss'],
templateUrl: './data-centers-map.component.html',
})
export class DataCentersMapComponent implements AfterViewInit, OnDestroy {
private alive = true;
private map: any;
dataCenters: DataCenter[] = [
{
name: 'Sydney DC1',
country: 'Australia',
city: 'Sydney',
coordinates: [-33.8688, 151.2093],
status: 'online',
databases: 3,
responseTime: 45
},
{
name: 'Melbourne DC2',
country: 'Australia',
city: 'Melbourne',
coordinates: [-37.8136, 144.9631],
status: 'online',
databases: 2,
responseTime: 52
},
{
name: 'Jakarta DC1',
country: 'Indonesia',
city: 'Jakarta',
coordinates: [-6.2088, 106.8456],
status: 'online',
databases: 4,
responseTime: 78
},
{
name: 'Kuala Lumpur DC1',
country: 'Malaysia',
city: 'Kuala Lumpur',
coordinates: [3.1390, 101.6869],
status: 'online',
databases: 2,
responseTime: 65
},
{
name: 'Tokyo DC1',
country: 'Japan',
city: 'Tokyo',
coordinates: [35.6762, 139.6503],
status: 'online',
databases: 3,
responseTime: 89
}
];
constructor(
private theme: NbThemeService,
private router: Router
) {}
ngAfterViewInit() {
// Wait for the view to be fully initialized
setTimeout(() => {
this.initMap();
}, 100);
}
initMap() {
// Initialize the map centered on Asia-Pacific region
this.map = L.map('map', {
center: [0, 120],
zoom: 4,
minZoom: 2,
maxZoom: 18,
maxBounds: [[-90, -180], [90, 180]],
maxBoundsViscosity: 1.0,
zoomControl: true,
attributionControl: false
});
// Add tile layer with a professional style
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
maxZoom: 19,
subdomains: 'abcd',
noWrap: true // Prevent the world from repeating
}).addTo(this.map);
// Add markers for each data center
this.dataCenters.forEach(dc => {
const icon = this.createCustomIcon(dc.status);
const marker = L.marker(dc.coordinates, { icon })
.addTo(this.map)
.bindPopup(this.createPopupContent(dc));
// Add click event to navigate to identity lookup
marker.on('click', () => {
setTimeout(() => {
this.router.navigate(['/pages/identity/manual-lookup'], {
queryParams: { country: dc.country.toLowerCase() }
});
}, 1000);
});
});
// Fit map to show all markers
const group = new L.featureGroup(this.dataCenters.map(dc => L.marker(dc.coordinates)));
this.map.fitBounds(group.getBounds().pad(0.1));
}
createCustomIcon(status: string) {
const colors = {
online: '#00d68f',
maintenance: '#ffaa00',
offline: '#ff3d71'
};
const html = `
<div style="
background-color: ${colors[status]};
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
"></div>
`;
return L.divIcon({
html: html,
className: 'custom-marker',
iconSize: [24, 24],
iconAnchor: [12, 12]
});
}
createPopupContent(dc: DataCenter): string {
const statusColors = {
online: '#00d68f',
maintenance: '#ffaa00',
offline: '#ff3d71'
};
return `
<div style="padding: 10px; min-width: 200px;">
<h6 style="margin: 0 0 10px 0; font-weight: 600;">${dc.name}</h6>
<div style="font-size: 14px; line-height: 1.6;">
<div><strong>City:</strong> ${dc.city}, ${dc.country}</div>
<div><strong>Status:</strong> <span style="color: ${statusColors[dc.status]}; font-weight: 600;">${dc.status}</span></div>
<div><strong>Databases:</strong> ${dc.databases}</div>
<div><strong>Response Time:</strong> ${dc.responseTime}ms</div>
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e4e9f2; text-align: center;">
<span style="color: #0095ff; font-size: 12px; cursor: pointer;">
<strong>Click to query this region </strong>
</span>
</div>
</div>
`;
}
getTotalDatabases(): number {
return this.dataCenters.reduce((sum, dc) => sum + dc.databases, 0);
}
getOnlineCount(): number {
return this.dataCenters.filter(dc => dc.status === 'online').length;
}
getCountryCount(): number {
// Get unique countries
const uniqueCountries = new Set(this.dataCenters.map(dc => dc.country));
return uniqueCountries.size;
}
getStatusColor(status: string): string {
switch (status) {
case 'online':
return '#00d68f';
case 'maintenance':
return '#ffaa00';
case 'offline':
return '#ff3d71';
default:
return '#8f9bb3';
}
}
ngOnDestroy() {
this.alive = false;
if (this.map) {
this.map.remove();
}
}
}

View file

@ -0,0 +1,27 @@
<nb-card>
<nb-card-header>
<h6>Why IdentityPulse is Different</h6>
</nb-card-header>
<nb-card-body>
<div class="differentiators-grid">
<div class="differentiator" *ngFor="let item of differentiators">
<div class="icon-wrapper">
<nb-icon [icon]="item.icon" pack="eva"></nb-icon>
</div>
<div class="content">
<h6 class="title">{{ item.title }}</h6>
<p class="description">{{ item.description }}</p>
<span class="metric" *ngIf="item.metric">{{ item.metric }}</span>
</div>
</div>
</div>
<div class="cta-section">
<p class="cta-text">Experience the difference with a live demo</p>
<button nbButton status="primary" fullWidth>
Schedule Demo
</button>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,90 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card-header h6 {
margin: 0;
font-weight: 600;
}
.differentiators-grid {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.differentiator {
display: flex;
gap: 1rem;
padding: 1rem;
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
transition: all 0.3s ease;
&:hover {
border-color: nb-theme(color-primary-default);
transform: translateX(4px);
.icon-wrapper {
background: nb-theme(color-primary-default);
color: white;
}
}
.icon-wrapper {
flex-shrink: 0;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: nb-theme(background-basic-color-3);
border-radius: nb-theme(card-border-radius);
transition: all 0.3s ease;
nb-icon {
font-size: 1.5rem;
}
}
.content {
flex: 1;
.title {
margin: 0 0 0.25rem 0;
font-size: 0.875rem;
font-weight: 600;
color: nb-theme(text-basic-color);
}
.description {
margin: 0 0 0.5rem 0;
font-size: 0.75rem;
color: nb-theme(text-hint-color);
line-height: 1.4;
}
.metric {
display: inline-block;
font-size: 0.75rem;
font-weight: 600;
color: nb-theme(color-primary-default);
padding: 0.125rem 0.5rem;
background: nb-theme(color-primary-transparent-100);
border-radius: nb-theme(border-radius);
}
}
}
.cta-section {
text-align: center;
padding-top: 1rem;
border-top: 1px solid nb-theme(border-basic-color-3);
.cta-text {
margin: 0 0 1rem 0;
font-size: 0.875rem;
color: nb-theme(text-hint-color);
}
}
}

View file

@ -0,0 +1,55 @@
import { Component } from '@angular/core';
interface Differentiator {
title: string;
description: string;
icon: string;
metric?: string;
}
@Component({
selector: 'ngx-differentiators',
styleUrls: ['./differentiators.component.scss'],
templateUrl: './differentiators.component.html',
})
export class DifferentiatorsComponent {
differentiators: Differentiator[] = [
{
title: 'Real-time Updates',
description: 'Direct integration with government and trusted sources for instant data updates',
icon: 'flash-outline',
metric: '< 5min latency'
},
{
title: 'Multi-Source Validation',
description: 'Cross-reference data from multiple authoritative sources for accuracy',
icon: 'layers-outline',
metric: '15+ sources'
},
{
title: 'AI-Powered Matching',
description: 'Advanced fuzzy matching algorithms handle name variations and typos',
icon: 'bulb-outline',
metric: '99.2% accuracy'
},
{
title: 'Regional Expertise',
description: 'Deep understanding of local naming conventions and data formats',
icon: 'globe-2-outline',
metric: '4 regions'
},
{
title: 'Elastic Infrastructure',
description: 'Built on Elasticsearch for lightning-fast queries at any scale',
icon: 'activity-outline',
metric: '< 250ms response'
},
{
title: 'Privacy by Design',
description: 'Zero data retention policy and end-to-end encryption',
icon: 'shield-outline',
metric: 'ISO 27001'
}
];
}

View file

@ -0,0 +1,95 @@
@import '../../../@theme/styles/themes';
@import 'bootstrap/scss/mixins/breakpoints';
@import '@nebular/theme/styles/global/breakpoints';
@include nb-install-component() {
nb-card {
flex-direction: row;
align-items: center;
height: 6rem;
overflow: visible;
.icon-container {
height: 100%;
padding: 0.625rem;
}
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 5.75rem;
height: 4.75rem;
font-size: 3.75rem;
border-radius: nb-theme(card-border-radius);
transition: width 0.4s ease;
transform: translate3d(0, 0, 0);
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
color: nb-theme(text-control-color);
@each $status in nb-get-statuses() {
&.status-#{$status} {
$left-color: nb-theme(button-hero-#{$status}-left-background-color);
$right-color: nb-theme(button-hero-#{$status}-right-background-color);
background-image: linear-gradient(to right, $left-color, $right-color);
&:hover {
$left-hover-color: nb-theme(button-hero-#{$status}-hover-left-background-color);
$right-hover-color: nb-theme(button-hero-#{$status}-hover-right-background-color);
background-image: linear-gradient(to right, $left-hover-color, $right-hover-color);
}
}
}
}
.details {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
@include nb-ltr(padding, 0 0.5rem 0 0.75rem);
@include nb-rtl(padding, 0 0.75rem 0 0.5rem);
border-left: 1px solid transparent;
}
.title {
margin: 0;
font-weight: 600;
color: nb-theme(text-hint-color);
font-size: 0.875rem;
}
.status-value {
display: flex;
align-items: baseline;
.value {
margin: 0;
font-weight: 700;
color: nb-theme(text-basic-color);
font-size: 2rem;
}
.unit {
margin-left: 0.5rem;
color: nb-theme(text-hint-color);
font-size: 0.875rem;
}
}
}
@include media-breakpoint-down(sm) {
nb-card {
.icon {
width: 4rem;
height: 4rem;
font-size: 2.5rem;
}
.value {
font-size: 1.5rem;
}
}
}
}

View file

@ -0,0 +1,30 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'ngx-metric-card',
styleUrls: ['./metric-card.component.scss'],
template: `
<nb-card>
<div class="icon-container">
<div class="icon status-{{ type }}">
<i [ngClass]="iconClass"></i>
</div>
</div>
<div class="details">
<div class="title">{{ title }}</div>
<div class="status-value">
<span class="value">{{ value }}</span>
<span class="unit">{{ unitOfMeasurement }}</span>
</div>
</div>
</nb-card>
`,
})
export class MetricCardComponent {
@Input() title: string;
@Input() type: string;
@Input() value: string;
@Input() unitOfMeasurement: string;
@Input() iconClass: string;
}

View file

@ -0,0 +1,47 @@
<nb-card>
<nb-card-header>
<h6>Regional Pricing</h6>
</nb-card-header>
<nb-card-body>
<div class="pricing-tiers">
<div class="tier" *ngFor="let tier of pricingTiers">
<div class="tier-header">
<span class="region-flag">{{ tier.flag }}</span>
<h6 class="region-name">{{ tier.region }}</h6>
</div>
<div class="pricing-details">
<div class="base-price">
<span class="currency">{{ tier.currency }}</span>
<span class="price">${{ tier.basePrice }}</span>
<span class="period">/month</span>
</div>
<div class="per-verification">
<span class="label">Per verification:</span>
<span class="value">${{ tier.perVerification }}</span>
</div>
<div class="volume-discount">
<nb-icon icon="pricetags-outline" pack="eva"></nb-icon>
<span>{{ tier.volumeDiscount }}</span>
</div>
</div>
</div>
</div>
<div class="features">
<h6>All plans include:</h6>
<ul>
<li *ngFor="let feature of features">
<nb-icon icon="checkmark-outline" pack="eva" status="success"></nb-icon>
{{ feature }}
</li>
</ul>
</div>
<button nbButton fullWidth status="primary">
Contact Sales
</button>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,115 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card-header h6 {
margin: 0;
font-weight: 600;
}
.pricing-tiers {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.tier {
padding: 1rem;
border: 1px solid nb-theme(border-basic-color-3);
border-radius: nb-theme(card-border-radius);
transition: all 0.3s ease;
&:hover {
border-color: nb-theme(color-primary-default);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.tier-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
.region-flag {
font-size: 1.5rem;
}
.region-name {
margin: 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
.pricing-details {
.base-price {
display: flex;
align-items: baseline;
margin-bottom: 0.5rem;
.currency {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
margin-right: 0.25rem;
}
.price {
font-size: 1.5rem;
font-weight: 700;
color: nb-theme(color-primary-default);
}
.period {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
margin-left: 0.25rem;
}
}
.per-verification,
.volume-discount {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: nb-theme(text-hint-color);
margin-bottom: 0.25rem;
nb-icon {
font-size: 1rem;
color: nb-theme(color-success-default);
}
}
}
}
.features {
margin-bottom: 1.5rem;
h6 {
margin: 0 0 0.75rem 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
ul {
list-style: none;
padding: 0;
margin: 0;
li {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: nb-theme(text-hint-color);
nb-icon {
font-size: 1.25rem;
}
}
}
}
}

View file

@ -0,0 +1,52 @@
import { Component } from '@angular/core';
interface PricingTier {
region: string;
flag: string;
currency: string;
basePrice: number;
perVerification: number;
volumeDiscount: string;
}
@Component({
selector: 'ngx-pricing-regions',
styleUrls: ['./pricing-regions.component.scss'],
templateUrl: './pricing-regions.component.html',
})
export class PricingRegionsComponent {
pricingTiers: PricingTier[] = [
{
region: 'APAC',
flag: '🌏',
currency: 'USD',
basePrice: 299,
perVerification: 0.15,
volumeDiscount: '10% off 100k+'
},
{
region: 'MENA',
flag: '🌍',
currency: 'USD',
basePrice: 349,
perVerification: 0.18,
volumeDiscount: '15% off 150k+'
},
{
region: 'Europe',
flag: '🇪🇺',
currency: 'EUR',
basePrice: 279,
perVerification: 0.14,
volumeDiscount: '12% off 100k+'
}
];
features = [
'Real-time API access',
'Batch processing',
'99.9% uptime SLA',
'24/7 support'
];
}

View file

@ -0,0 +1,41 @@
<nb-card>
<nb-card-header>
<h6>Recent Verifications</h6>
<button nbButton ghost size="tiny" status="primary">View All</button>
</nb-card-header>
<nb-card-body>
<nb-list>
<nb-list-item *ngFor="let verification of verifications">
<div class="verification-item">
<div class="verification-header">
<div class="country-info">
<span class="country-flag">{{ getCountryFlag(verification.country) }}</span>
<span class="country-name">{{ verification.country }}</span>
</div>
<span class="timestamp">{{ getTimeAgo(verification.timestamp) }}</span>
</div>
<div class="verification-details">
<div class="type-and-id">
<span class="type">{{ verification.type }}</span>
<span class="id">{{ verification.id }}</span>
</div>
<div class="score-and-status">
<nb-progress-bar
[value]="verification.matchScore"
[status]="getStatusColor(verification.status)"
size="tiny"
[displayValue]="true">
</nb-progress-bar>
<nb-icon
[icon]="getStatusIcon(verification.status)"
[status]="getStatusColor(verification.status)"
pack="eva">
</nb-icon>
</div>
</div>
</div>
</nb-list-item>
</nb-list>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,101 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card {
height: 450px;
}
nb-card-header {
display: flex;
justify-content: space-between;
align-items: center;
h6 {
margin: 0;
font-weight: 600;
}
}
nb-card-body {
padding: 0;
overflow-y: auto;
}
nb-list {
margin: 0;
}
nb-list-item {
padding: 0;
border: none;
&:hover {
background-color: nb-theme(background-basic-color-2);
}
}
.verification-item {
padding: 1rem;
border-bottom: 1px solid nb-theme(border-basic-color-3);
}
.verification-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
.country-info {
display: flex;
align-items: center;
gap: 0.5rem;
.country-flag {
font-size: 1.25rem;
}
.country-name {
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
.timestamp {
font-size: 0.75rem;
color: nb-theme(text-hint-color);
}
}
.verification-details {
.type-and-id {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
.type {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
}
.id {
font-size: 0.75rem;
color: nb-theme(text-hint-color);
font-family: monospace;
}
}
.score-and-status {
display: flex;
align-items: center;
gap: 0.5rem;
nb-progress-bar {
flex: 1;
}
nb-icon {
font-size: 1.25rem;
}
}
}
}

View file

@ -0,0 +1,126 @@
import { Component, OnDestroy } from '@angular/core';
import { takeWhile } from 'rxjs/operators';
import { NbThemeService } from '@nebular/theme';
interface Verification {
id: string;
country: string;
timestamp: Date;
matchScore: number;
status: 'verified' | 'partial' | 'failed';
type: string;
}
@Component({
selector: 'ngx-recent-verifications',
styleUrls: ['./recent-verifications.component.scss'],
templateUrl: './recent-verifications.component.html',
})
export class RecentVerificationsComponent implements OnDestroy {
private alive = true;
verifications: Verification[] = [
{
id: 'VER-2024-001',
country: 'Australia',
timestamp: new Date(Date.now() - 120000),
matchScore: 95,
status: 'verified',
type: 'Identity Check'
},
{
id: 'VER-2024-002',
country: 'Indonesia',
timestamp: new Date(Date.now() - 300000),
matchScore: 78,
status: 'partial',
type: 'Address Verification'
},
{
id: 'VER-2024-003',
country: 'Japan',
timestamp: new Date(Date.now() - 600000),
matchScore: 92,
status: 'verified',
type: 'Full KYC'
},
{
id: 'VER-2024-004',
country: 'Malaysia',
timestamp: new Date(Date.now() - 900000),
matchScore: 45,
status: 'failed',
type: 'Identity Check'
},
{
id: 'VER-2024-005',
country: 'Australia',
timestamp: new Date(Date.now() - 1200000),
matchScore: 88,
status: 'verified',
type: 'Document Verify'
},
{
id: 'VER-2024-006',
country: 'Indonesia',
timestamp: new Date(Date.now() - 1500000),
matchScore: 91,
status: 'verified',
type: 'Full KYC'
}
];
constructor(private themeService: NbThemeService) {}
getStatusIcon(status: string): string {
switch (status) {
case 'verified':
return 'checkmark-circle-2-outline';
case 'partial':
return 'alert-triangle-outline';
case 'failed':
return 'close-circle-outline';
default:
return 'minus-circle-outline';
}
}
getStatusColor(status: string): string {
switch (status) {
case 'verified':
return 'success';
case 'partial':
return 'warning';
case 'failed':
return 'danger';
default:
return 'basic';
}
}
getCountryFlag(country: string): string {
const flags = {
'Australia': '🇦🇺',
'Indonesia': '🇮🇩',
'Malaysia': '🇲🇾',
'Japan': '🇯🇵'
};
return flags[country] || '🌏';
}
getTimeAgo(date: Date): string {
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
if (seconds < 60) return 'just now';
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -4,7 +4,7 @@ import { Component, Input } from '@angular/core';
selector: 'ngx-status-card',
styleUrls: ['./status-card.component.scss'],
template: `
<nb-card (click)="on = !on" [ngClass]="{'off': !on}">
<nb-card>
<div class="icon-container">
<div class="icon status-{{ type }}">
<ng-content></ng-content>
@ -13,14 +13,17 @@ import { Component, Input } from '@angular/core';
<div class="details">
<div class="title h5">{{ title }}</div>
<div class="status paragraph-2">{{ on ? 'ON' : 'OFF' }}</div>
<div class="status-value">
<span class="value h2">{{ value }}</span>
<span class="unit">{{ unitOfMeasurement }}</span>
</div>
</div>
</nb-card>
`,
})
export class StatusCardComponent {
@Input() title: string;
@Input() type: string;
@Input() on = true;
@Input() value: string;
@Input() unitOfMeasurement: string;
}

View file

@ -9,7 +9,8 @@ import {
NbSelectModule,
NbSpinnerModule,
NbProgressBarModule,
NbIconModule
NbIconModule,
NbListModule
} from '@nebular/theme';
import { ManualLookupComponent } from './manual-lookup/manual-lookup.component';
import { ResultsHistoryComponent } from './results-history/results-history.component';
@ -34,7 +35,8 @@ import { ResultsHistoryComponent } from './results-history/results-history.compo
NbSelectModule,
NbSpinnerModule,
NbProgressBarModule,
NbIconModule
NbIconModule,
NbListModule
],
declarations: [
ManualLookupComponent,

View file

@ -1,76 +1,150 @@
<div class="row">
<div class="col-12 col-md-6">
<div class="col-12 col-lg-8">
<nb-card>
<nb-card-header>
<h5>Identity Verification - Manual Lookup</h5>
</nb-card-header>
<nb-card-body>
<form [formGroup]="identityForm" (ngSubmit)="onSubmit()">
<div class="form-group row">
<label class="col-sm-4 col-form-label">First Name</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="firstName" placeholder="First Name">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="firstName" class="label">First Name *</label>
<input nbInput
fullWidth
id="firstName"
formControlName="firstName"
placeholder="Enter first name"
status="basic"
[status]="identityForm.get('firstName').invalid && identityForm.get('firstName').touched ? 'danger' : 'basic'">
<span class="error-message" *ngIf="identityForm.get('firstName').invalid && identityForm.get('firstName').touched">
First name is required
</span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Last Name</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="lastName" placeholder="Last Name">
<div class="col-md-6">
<div class="form-group">
<label for="lastName" class="label">Last Name *</label>
<input nbInput
fullWidth
id="lastName"
formControlName="lastName"
placeholder="Enter last name"
[status]="identityForm.get('lastName').invalid && identityForm.get('lastName').touched ? 'danger' : 'basic'">
<span class="error-message" *ngIf="identityForm.get('lastName').invalid && identityForm.get('lastName').touched">
Last name is required
</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Birth</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="dateOfBirth" placeholder="YYYY-MM-DD">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="dateOfBirth" class="label">Date of Birth *</label>
<input nbInput
fullWidth
id="dateOfBirth"
type="date"
formControlName="dateOfBirth"
placeholder="YYYY-MM-DD"
[status]="identityForm.get('dateOfBirth').invalid && identityForm.get('dateOfBirth').touched ? 'danger' : 'basic'">
<span class="error-message" *ngIf="identityForm.get('dateOfBirth').invalid && identityForm.get('dateOfBirth').touched">
Date of birth is required
</span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Country</label>
<div class="col-sm-8">
<nb-select fullWidth formControlName="country" placeholder="Select Country">
<nb-option value="aus">Australia</nb-option>
<nb-option value="indo">Indonesia</nb-option>
<div class="col-md-6">
<div class="form-group">
<label for="country" class="label">Country *</label>
<nb-select fullWidth
id="country"
formControlName="country"
placeholder="Select Country"
[status]="identityForm.get('country').invalid && identityForm.get('country').touched ? 'danger' : 'basic'">
<nb-option value="australia">Australia</nb-option>
<nb-option value="indonesia">Indonesia</nb-option>
<nb-option value="japan">Japan</nb-option>
<nb-option value="malay">Malaysia</nb-option>
<nb-option value="malaysia">Malaysia</nb-option>
</nb-select>
<span class="error-message" *ngIf="identityForm.get('country').invalid && identityForm.get('country').touched">
Please select a country
</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">ID Number</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="identificationNumber" placeholder="Identification Number">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="identificationNumber" class="label">ID Number</label>
<input nbInput
fullWidth
id="identificationNumber"
formControlName="identificationNumber"
placeholder="Enter identification number"
status="basic">
<span class="hint-text">Optional: National ID, Passport, or Driver's License</span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Email</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="email" placeholder="Email">
<div class="col-md-6">
<div class="form-group">
<label for="email" class="label">Email</label>
<input nbInput
fullWidth
id="email"
type="email"
formControlName="email"
placeholder="Enter email address"
[status]="identityForm.get('email').invalid && identityForm.get('email').touched ? 'danger' : 'basic'">
<span class="error-message" *ngIf="identityForm.get('email').invalid && identityForm.get('email').touched">
Please enter a valid email
</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Phone</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="phone" placeholder="Phone Number">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="phone" class="label">Phone Number</label>
<input nbInput
fullWidth
id="phone"
formControlName="phone"
placeholder="Enter phone number"
status="basic">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Address</label>
<div class="col-sm-8">
<input nbInput fullWidth formControlName="address" placeholder="Address">
<div class="col-md-6">
<div class="form-group">
<label for="address" class="label">Address</label>
<input nbInput
fullWidth
id="address"
formControlName="address"
placeholder="Enter address"
status="basic">
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12 text-center">
<button nbButton status="primary" [disabled]="identityForm.invalid || isSubmitting">
<nb-icon icon="search-outline"></nb-icon> Verify Identity
<div class="row mt-4">
<div class="col-12">
<button nbButton
fullWidth
status="primary"
size="large"
[disabled]="identityForm.invalid || isSubmitting"
[nbSpinner]="isSubmitting"
nbSpinnerStatus="primary"
nbSpinnerSize="small">
<nb-icon icon="search-outline" *ngIf="!isSubmitting"></nb-icon>
{{ isSubmitting ? 'Verifying...' : 'Verify Identity' }}
</button>
</div>
</div>
@ -79,52 +153,105 @@
</nb-card>
</div>
<div class="col-12 col-md-6" *ngIf="result">
<nb-card>
<div class="col-12 col-lg-4">
<nb-card *ngIf="!result" class="info-card">
<nb-card-header>
<h5>Verification Results</h5>
<h6>Verification Tips</h6>
</nb-card-header>
<nb-card-body>
<nb-list>
<nb-list-item>
<nb-icon icon="checkmark-circle-outline" status="success"></nb-icon>
<span>Use full legal names as they appear on official documents</span>
</nb-list-item>
<nb-list-item>
<nb-icon icon="checkmark-circle-outline" status="success"></nb-icon>
<span>Ensure date format is YYYY-MM-DD</span>
</nb-list-item>
<nb-list-item>
<nb-icon icon="checkmark-circle-outline" status="success"></nb-icon>
<span>Include middle names if available</span>
</nb-list-item>
<nb-list-item>
<nb-icon icon="checkmark-circle-outline" status="success"></nb-icon>
<span>Double-check spelling and formatting</span>
</nb-list-item>
</nb-list>
</nb-card-body>
</nb-card>
<nb-card *ngIf="result" class="result-card">
<nb-card-header>
<h6>Verification Results</h6>
</nb-card-header>
<nb-card-body>
<div class="match-result">
<h6>Overall Match Confidence</h6>
<nb-progress-bar [value]="result.overallMatch" status="primary" [displayValue]="true"></nb-progress-bar>
<nb-progress-bar [value]="result.overallMatch"
[status]="getMatchStatus(result.overallMatch)"
[displayValue]="true"
size="large">
</nb-progress-bar>
</div>
<div class="field-matches mt-4">
<h6>Field-level Match Breakdown</h6>
<div class="field-match-item">
<div class="d-flex justify-content-between">
<div class="match-header">
<span>Name</span>
<span>{{ result.fieldMatches.name }}%</span>
<span class="match-value">{{ result.fieldMatches.name }}%</span>
</div>
<nb-progress-bar [value]="result.fieldMatches.name" size="tiny" status="success"></nb-progress-bar>
<nb-progress-bar [value]="result.fieldMatches.name"
size="tiny"
[status]="getMatchStatus(result.fieldMatches.name)">
</nb-progress-bar>
</div>
<div class="field-match-item mt-2">
<div class="d-flex justify-content-between">
<div class="field-match-item">
<div class="match-header">
<span>Date of Birth</span>
<span>{{ result.fieldMatches.dateOfBirth }}%</span>
<span class="match-value">{{ result.fieldMatches.dateOfBirth }}%</span>
</div>
<nb-progress-bar [value]="result.fieldMatches.dateOfBirth" size="tiny" status="success"></nb-progress-bar>
<nb-progress-bar [value]="result.fieldMatches.dateOfBirth"
size="tiny"
[status]="getMatchStatus(result.fieldMatches.dateOfBirth)">
</nb-progress-bar>
</div>
<div class="field-match-item mt-2">
<div class="d-flex justify-content-between">
<div class="field-match-item">
<div class="match-header">
<span>Address</span>
<span>{{ result.fieldMatches.address }}%</span>
<span class="match-value">{{ result.fieldMatches.address }}%</span>
</div>
<nb-progress-bar [value]="result.fieldMatches.address" size="tiny" status="warning"></nb-progress-bar>
<nb-progress-bar [value]="result.fieldMatches.address"
size="tiny"
[status]="getMatchStatus(result.fieldMatches.address)">
</nb-progress-bar>
</div>
<div class="field-match-item mt-2">
<div class="d-flex justify-content-between">
<div class="field-match-item">
<div class="match-header">
<span>Identification</span>
<span>{{ result.fieldMatches.identification }}%</span>
<span class="match-value">{{ result.fieldMatches.identification }}%</span>
</div>
<nb-progress-bar [value]="result.fieldMatches.identification" size="tiny" status="info"></nb-progress-bar>
<nb-progress-bar [value]="result.fieldMatches.identification"
size="tiny"
[status]="getMatchStatus(result.fieldMatches.identification)">
</nb-progress-bar>
</div>
</div>
<div class="action-buttons">
<button nbButton fullWidth status="info" size="medium">
<nb-icon icon="download-outline"></nb-icon>
Download Report
</button>
<button nbButton fullWidth status="basic" size="medium" (click)="resetForm()">
<nb-icon icon="refresh-outline"></nb-icon>
New Verification
</button>
</div>
</nb-card-body>
</nb-card>
</div>

View file

@ -1,7 +1,108 @@
.field-match-item {
margin-bottom: 15px;
@import '../../../@theme/styles/themes';
@include nb-install-component() {
.form-group {
margin-bottom: 1.5rem;
.label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: nb-theme(text-basic-color);
font-size: 0.875rem;
}
.match-result {
margin-bottom: 20px;
.error-message {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: nb-theme(color-danger-default);
}
.hint-text {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
color: nb-theme(text-hint-color);
}
}
.info-card {
nb-list {
margin: 0;
}
nb-list-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 0;
border: none;
nb-icon {
flex-shrink: 0;
font-size: 1.25rem;
margin-top: 0.125rem;
}
span {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
line-height: 1.5;
}
}
}
.result-card {
.match-result {
margin-bottom: 2rem;
padding: 1rem;
background: nb-theme(background-basic-color-2);
border-radius: nb-theme(card-border-radius);
h6 {
margin: 0 0 1rem 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
.field-matches {
h6 {
margin: 0 0 1rem 0;
font-weight: 600;
color: nb-theme(text-basic-color);
}
.field-match-item {
margin-bottom: 1rem;
.match-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
span {
font-size: 0.875rem;
color: nb-theme(text-hint-color);
}
.match-value {
font-weight: 600;
color: nb-theme(text-basic-color);
}
}
}
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid nb-theme(border-basic-color-3);
}
}
}

View file

@ -1,17 +1,21 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'ngx-manual-lookup',
templateUrl: './manual-lookup.component.html',
styleUrls: ['./manual-lookup.component.scss']
})
export class ManualLookupComponent {
export class ManualLookupComponent implements OnInit {
identityForm: FormGroup;
isSubmitting = false;
result = null;
constructor(private fb: FormBuilder) {
constructor(
private fb: FormBuilder,
private route: ActivatedRoute
) {
this.identityForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
@ -24,10 +28,21 @@ export class ManualLookupComponent {
});
}
ngOnInit(): void {
// Check if country is passed as query parameter
this.route.queryParams.subscribe(params => {
if (params['country']) {
this.identityForm.patchValue({
country: params['country']
});
}
});
}
onSubmit() {
if (this.identityForm.valid) {
this.isSubmitting = true;
// Here you would call your identity verification service
// Simulate API call
setTimeout(() => {
this.isSubmitting = false;
this.result = {
@ -40,6 +55,22 @@ export class ManualLookupComponent {
}
};
}, 2000);
} else {
// Mark all fields as touched to show validation errors
Object.keys(this.identityForm.controls).forEach(key => {
this.identityForm.get(key).markAsTouched();
});
}
}
resetForm() {
this.identityForm.reset();
this.result = null;
}
getMatchStatus(score: number): string {
if (score >= 80) return 'success';
if (score >= 60) return 'warning';
return 'danger';
}
}

View file

@ -2,14 +2,15 @@
<html>
<head>
<meta charset="utf-8">
<title>ngx-admin Demo Application</title>
<title>IdentityPulse - Identity Verification Platform</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCpVhQiwAllg1RAFaxMWSpQruuGARy0Y1k&libraries=places"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
</head>
<body>
<ngx-app>Loading...</ngx-app>