feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with: Backend: - NestJS API with JWT authentication - PostgreSQL database with Knex ORM - Redis caching and session management - MinIO document storage - Hyperledger Besu blockchain integration - Multi-department workflow system - Comprehensive API tests (266/282 passing) Frontend: - Angular 21 with standalone components - Angular Material + TailwindCSS UI - Visual workflow builder - Document upload with progress tracking - Blockchain explorer integration - Role-based dashboards (Admin, Department, Citizen) - E2E tests with Playwright (37 tests) Infrastructure: - Docker Compose orchestration - Blockscout blockchain explorer - Development and production configurations
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blockchain-info',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, MatIconModule, MatButtonModule, MatTooltipModule],
|
||||
template: `
|
||||
@if (tokenId || txHash) {
|
||||
<div class="blockchain-info" [class.compact]="compact">
|
||||
<div class="header">
|
||||
<mat-icon class="chain-icon">token</mat-icon>
|
||||
<span class="title">Blockchain Record</span>
|
||||
<span class="verified-badge">
|
||||
<mat-icon>verified</mat-icon>
|
||||
Verified
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (tokenId) {
|
||||
<div class="info-row">
|
||||
<span class="label">License NFT Token ID</span>
|
||||
<span class="value token-id">#{{ tokenId }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (txHash) {
|
||||
<div class="info-row">
|
||||
<span class="label">Transaction Hash</span>
|
||||
<div class="tx-hash-container">
|
||||
<code class="tx-hash">{{ truncateHash(txHash) }}</code>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Copy full hash"
|
||||
(click)="copyToClipboard(txHash)"
|
||||
class="copy-btn"
|
||||
>
|
||||
<mat-icon>content_copy</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (showExplorer && txHash) {
|
||||
<div class="explorer-link">
|
||||
<a mat-button color="primary" [href]="getExplorerUrl()" target="_blank">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
View on Block Explorer
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
.blockchain-info {
|
||||
background: linear-gradient(135deg, #e8f5e9 0%, #e3f2fd 100%);
|
||||
border: 1px solid #c8e6c9;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.blockchain-info.compact {
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chain-icon {
|
||||
color: #2e7d32;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
color: #1b5e20;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.verified-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.token-id {
|
||||
font-size: 1.125rem;
|
||||
color: #1565c0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.tx-hash-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tx-hash {
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: #455a64;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.explorer-link {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.compact .header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.compact .info-row {
|
||||
padding: 4px 0;
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class BlockchainInfoComponent {
|
||||
@Input() tokenId?: string | number;
|
||||
@Input() txHash?: string;
|
||||
@Input() compact = false;
|
||||
@Input() showExplorer = false;
|
||||
@Input() explorerBaseUrl = 'http://localhost:25000';
|
||||
|
||||
truncateHash(hash: string): string {
|
||||
if (hash.length <= 20) return hash;
|
||||
return `${hash.substring(0, 10)}...${hash.substring(hash.length - 8)}`;
|
||||
}
|
||||
|
||||
copyToClipboard(text: string): void {
|
||||
navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
getExplorerUrl(): string {
|
||||
return `${this.explorerBaseUrl}/tx/${this.txHash}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user