mirror of
https://github.com/akveo/ngx-admin.git
synced 2025-12-16 15:40:11 +01:00
Updates
This commit is contained in:
parent
c2a24c7b69
commit
c264b0a79a
41 changed files with 2615 additions and 305 deletions
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "ngx-admin",
|
||||
"name": "identity-pulse",
|
||||
"version": "11.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
userPictureOnly: boolean = false;
|
||||
user: any;
|
||||
|
||||
themes = [
|
||||
themes = [
|
||||
{
|
||||
value: 'default',
|
||||
name: 'Light',
|
||||
|
|
@ -38,9 +38,9 @@ themes = [
|
|||
value: 'marketsoft',
|
||||
name: 'Marketsoft',
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
currentTheme = 'default';
|
||||
currentTheme = 'marketsoft';
|
||||
|
||||
userMenu = [ { title: 'Profile' }, { title: 'Log out' } ];
|
||||
|
||||
|
|
|
|||
|
|
@ -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="row">
|
||||
<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>
|
||||
|
|
@ -1,12 +1,108 @@
|
|||
.country-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@import '../../@theme/styles/themes';
|
||||
|
||||
.country-info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@include nb-install-component() {
|
||||
.subtitle {
|
||||
color: nb-theme(text-hint-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 5px;
|
||||
.section-title {
|
||||
margin: 2rem 0 1rem 0;
|
||||
font-weight: 600;
|
||||
color: nb-theme(text-basic-color);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import {Component, OnDestroy} from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators' ;
|
||||
import { SolarData } from '../../@core/data/solar';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/app/pages/dashboard/metric-card/metric-card.component.ts
Normal file
30
src/app/pages/dashboard/metric-card/metric-card.component.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
];
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,108 @@
|
|||
.field-match-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@import '../../../@theme/styles/themes';
|
||||
|
||||
.match-result {
|
||||
margin-bottom: 20px;
|
||||
@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;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue