mirror of
https://github.com/akveo/ngx-admin.git
synced 2026-03-03 20:30:15 +01:00
Updates
This commit is contained in:
parent
c2a24c7b69
commit
c264b0a79a
41 changed files with 2615 additions and 305 deletions
|
|
@ -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 class="col-xxxl-4 col-xxl-4 col-lg-4 col-md-12">
|
||||
<ngx-differentiators></ngx-differentiators>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -13,4 +13,4 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,46 +66,24 @@ 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() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 { }
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue