fix: Apply monochrome theme and improve UX for license application

- Applicant dashboard: Replace colorful stat cards with monochrome grey/teal accent
- Department dashboard: Remove blockchain wallet section (not needed for dept users)
- License form: Change "Workflow" to user-friendly "License Type" terminology
- License form: Strip internal terms like "Approval Workflow" from display names
- License form: Update header to monochrome theme with teal accent icon
- Add getLicenseIcon() and getLicenseDescription() helpers for better UX

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mahi
2026-02-10 20:45:31 -04:00
parent 6ec8d3236d
commit e6b7ed6827
4 changed files with 213 additions and 245 deletions

View File

@@ -118,7 +118,7 @@ interface ApplicantStats {
<mat-icon>description</mat-icon> <mat-icon>description</mat-icon>
<h2>Recent Applications</h2> <h2>Recent Applications</h2>
</div> </div>
<button mat-button color="primary" routerLink="/requests"> <button mat-button class="view-all-btn" routerLink="/requests">
View All View All
<mat-icon>arrow_forward</mat-icon> <mat-icon>arrow_forward</mat-icon>
</button> </button>
@@ -213,23 +213,10 @@ interface ApplicantStats {
/* Welcome Section */ /* Welcome Section */
.welcome-section { .welcome-section {
background: linear-gradient(135deg, #1D0A69 0%, #2563EB 100%); background: #FAFAFA;
color: white; border-bottom: 1px solid #E0E0E0;
padding: 32px; padding: 32px;
margin: -24px -24px 24px -24px; margin: -24px -24px 24px -24px;
position: relative;
overflow: hidden;
}
.welcome-section::before {
content: '';
position: absolute;
top: -50%;
right: -10%;
width: 50%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
pointer-events: none;
} }
.welcome-content { .welcome-content {
@@ -239,27 +226,26 @@ interface ApplicantStats {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 24px; gap: 24px;
position: relative;
z-index: 1;
} }
.welcome-text .greeting { .welcome-text .greeting {
font-size: 0.9rem; font-size: 0.85rem;
opacity: 0.8; color: #757575;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 0.5px;
} }
.welcome-text h1 { .welcome-text h1 {
margin: 8px 0; margin: 8px 0;
font-size: 2rem; font-size: 1.75rem;
font-weight: 700; font-weight: 700;
color: #212121;
} }
.welcome-text .subtitle { .welcome-text .subtitle {
margin: 0; margin: 0;
opacity: 0.85; color: #757575;
font-size: 0.95rem; font-size: 0.9rem;
} }
.quick-actions { .quick-actions {
@@ -268,17 +254,18 @@ interface ApplicantStats {
} }
.action-btn.primary { .action-btn.primary {
background: white !important; background: #00897B !important;
color: #1D0A69 !important; color: white !important;
} }
.action-btn:not(.primary) { .action-btn:not(.primary) {
color: white; color: #424242;
border-color: rgba(255, 255, 255, 0.5); border-color: #E0E0E0;
} }
.action-btn:not(.primary):hover { .action-btn:not(.primary):hover {
background: rgba(255, 255, 255, 0.1); background: #F5F5F5;
border-color: #BDBDBD;
} }
.action-btn mat-icon { .action-btn mat-icon {
@@ -303,49 +290,43 @@ interface ApplicantStats {
padding: 24px; padding: 24px;
border-radius: 16px !important; border-radius: 16px !important;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.2s ease;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
color: white; background: white !important;
border: 1px solid #E0E0E0;
} }
.stat-card:hover { .stat-card:hover {
transform: translateY(-4px); transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
} border-color: #00897B;
.stat-card.pending {
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%) !important;
}
.stat-card.approved {
background: linear-gradient(135deg, #059669 0%, #10b981 100%) !important;
}
.stat-card.documents {
background: linear-gradient(135deg, #1D0A69 0%, #2563EB 100%) !important;
}
.stat-card.blockchain {
background: linear-gradient(135deg, #7c3aed 0%, #a78bfa 100%) !important;
} }
.stat-icon-wrapper { .stat-icon-wrapper {
width: 56px; width: 52px;
height: 56px; height: 52px;
border-radius: 14px; border-radius: 12px;
background: rgba(255, 255, 255, 0.2); background: #FAFAFA;
border: 1px solid #E0E0E0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
backdrop-filter: blur(10px);
flex-shrink: 0; flex-shrink: 0;
color: #424242;
}
.stat-card.pending .stat-icon-wrapper,
.stat-card.approved .stat-icon-wrapper {
background: #E0F2F1;
border-color: #B2DFDB;
color: #00897B;
} }
.stat-icon-wrapper mat-icon { .stat-icon-wrapper mat-icon {
font-size: 28px; font-size: 26px;
width: 28px; width: 26px;
height: 28px; height: 26px;
} }
.stat-content { .stat-content {
@@ -354,25 +335,20 @@ interface ApplicantStats {
} }
.stat-value { .stat-value {
font-size: 2rem; font-size: 1.75rem;
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.2;
color: #212121;
} }
.stat-label { .stat-label {
font-size: 0.85rem; font-size: 0.8rem;
opacity: 0.9; color: #757575;
margin-top: 4px; margin-top: 4px;
} }
.stat-decoration { .stat-decoration {
position: absolute; display: none;
top: -20px;
right: -20px;
width: 100px;
height: 100px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
} }
/* Content Grid */ /* Content Grid */
@@ -407,7 +383,7 @@ interface ApplicantStats {
} }
.card-header .header-left mat-icon { .card-header .header-left mat-icon {
color: #2563EB; color: #00897B;
} }
.card-header .header-left h2 { .card-header .header-left h2 {
@@ -417,6 +393,10 @@ interface ApplicantStats {
color: #150202; color: #150202;
} }
.card-header .view-all-btn {
color: #00897B;
}
.card-header button mat-icon { .card-header button mat-icon {
margin-left: 4px; margin-left: 4px;
font-size: 18px; font-size: 18px;
@@ -587,37 +567,33 @@ interface ApplicantStats {
} }
.action-icon { .action-icon {
width: 56px; width: 52px;
height: 56px; height: 52px;
border-radius: 14px; border-radius: 12px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #FAFAFA;
border: 1px solid #E0E0E0;
color: #424242;
transition: all 0.2s ease;
} }
.action-icon mat-icon { .action-icon mat-icon {
font-size: 26px; font-size: 24px;
width: 26px; width: 24px;
height: 26px; height: 24px;
}
.action-item:hover .action-icon {
background: #00897B;
border-color: #00897B;
color: white;
} }
.action-icon.license { .action-icon.license {
background: linear-gradient(135deg, #1D0A69, #2563EB); background: #00897B;
color: white; border-color: #00897B;
}
.action-icon.renewal {
background: linear-gradient(135deg, #059669, #10b981);
color: white;
}
.action-icon.track {
background: linear-gradient(135deg, #f59e0b, #fbbf24);
color: white;
}
.action-icon.help {
background: linear-gradient(135deg, #7c3aed, #a78bfa);
color: white; color: white;
} }

View File

@@ -159,127 +159,6 @@ interface Transaction {
} }
</section> </section>
</div> </div>
<!-- Right Column - Wallet -->
<div class="right-column">
<!-- Department Wallet Card -->
<section class="wallet-section">
<div class="wallet-card">
<div class="wallet-gradient-bg"></div>
<div class="wallet-content">
<div class="wallet-header">
<div class="wallet-title">
<span class="wallet-label">Department Wallet</span>
<span class="wallet-name">{{ departmentName() }}</span>
</div>
<div class="wallet-icon">
<mat-icon>account_balance_wallet</mat-icon>
</div>
</div>
<div class="wallet-balance">
<span class="balance-label">Available Balance</span>
<div class="balance-value">
<span class="balance-amount">{{ wallet().balance }}</span>
<span class="balance-currency">ETH</span>
</div>
<span class="balance-usd">≈ {{ wallet().balanceUsd }} USD</span>
</div>
<div class="wallet-address" (click)="copyAddress()">
<div class="address-content">
<mat-icon class="address-icon">link</mat-icon>
<span class="address-text">{{ formatAddress(wallet().address) }}</span>
</div>
<button mat-icon-button class="copy-btn" matTooltip="Copy address">
<mat-icon>{{ copied() ? 'check' : 'content_copy' }}</mat-icon>
</button>
</div>
<div class="wallet-stats">
<div class="wallet-stat">
<span class="stat-value">{{ wallet().transactionCount }}</span>
<span class="stat-label">Transactions</span>
</div>
<div class="wallet-stat">
<span class="stat-value">{{ wallet().lastActive }}</span>
<span class="stat-label">Last Active</span>
</div>
</div>
</div>
</div>
</section>
<!-- Recent Transactions -->
<section class="transactions-section">
<div class="section-header">
<div class="section-title">
<mat-icon>receipt_long</mat-icon>
<h2>Recent Transactions</h2>
</div>
<button mat-button>
View All
<mat-icon>arrow_forward</mat-icon>
</button>
</div>
<div class="transactions-list">
@for (tx of recentTransactions(); track tx.hash) {
<div class="transaction-item" [class]="'tx-' + tx.status">
<div class="tx-icon" [class]="tx.type.toLowerCase()">
<mat-icon>
{{ tx.type === 'APPROVAL' ? 'check_circle' : tx.type === 'REJECTION' ? 'cancel' : 'token' }}
</mat-icon>
</div>
<div class="tx-content">
<div class="tx-title">
{{ tx.type === 'APPROVAL' ? 'Approval Transaction' : tx.type === 'REJECTION' ? 'Rejection Transaction' : 'NFT Minting' }}
</div>
<div class="tx-hash" (click)="copyHash(tx.hash)">
<span class="hash-text">{{ formatHash(tx.hash) }}</span>
<mat-icon class="copy-icon">content_copy</mat-icon>
</div>
</div>
<div class="tx-meta">
<span class="tx-status" [class]="tx.status">
<span class="status-dot"></span>
{{ tx.status }}
</span>
<span class="tx-time">{{ tx.timestamp | date: 'shortTime' }}</span>
</div>
</div>
}
</div>
</section>
<!-- Blockchain Status -->
<section class="blockchain-section">
<div class="blockchain-card">
<div class="blockchain-header">
<mat-icon>hub</mat-icon>
<span>Hyperledger Besu Network</span>
</div>
<div class="blockchain-stats">
<div class="bc-stat">
<span class="bc-value">{{ blockHeight() }}</span>
<span class="bc-label">Block Height</span>
</div>
<div class="bc-stat">
<span class="bc-value">{{ totalTxCount() }}</span>
<span class="bc-label">Total Txs</span>
</div>
<div class="bc-stat">
<span class="bc-value">4</span>
<span class="bc-label">Nodes</span>
</div>
</div>
<div class="blockchain-status">
<span class="status-indicator online"></span>
<span class="status-text">Network Healthy</span>
</div>
</div>
</section>
</div>
</div> </div>
</div> </div>
`, `,
@@ -353,12 +232,8 @@ interface Transaction {
// ============================================================================= // =============================================================================
.dashboard-grid { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: 1fr 400px; grid-template-columns: 1fr;
gap: 24px; gap: 24px;
@media (max-width: 1200px) {
grid-template-columns: 1fr;
}
} }
// ============================================================================= // =============================================================================

View File

@@ -43,10 +43,10 @@
} }
</div> </div>
<!-- Workflow Selection --> <!-- License Type Selection -->
<div class="step-header" style="margin-top: 32px"> <div class="step-header" style="margin-top: 32px">
<h3>Select Workflow</h3> <h3>Select License Type</h3>
<p>Choose the approval workflow for your application</p> <p>What type of license are you applying for?</p>
</div> </div>
@if (loading()) { @if (loading()) {
@@ -55,19 +55,33 @@
</div> </div>
} @else if (workflows().length === 0) { } @else if (workflows().length === 0) {
<div style="text-align: center; padding: 32px; color: var(--dbim-grey-2)"> <div style="text-align: center; padding: 32px; color: var(--dbim-grey-2)">
<mat-icon style="font-size: 48px; width: 48px; height: 48px; opacity: 0.5">warning</mat-icon> <mat-icon style="font-size: 48px; width: 48px; height: 48px; opacity: 0.5">info</mat-icon>
<p>No active workflows available</p> <p>No license types available at the moment. Please try again later.</p>
</div> </div>
} @else { } @else {
<div class="workflow-selection"> <div class="license-type-selection">
@for (workflow of workflows(); track workflow.id) { @for (workflow of workflows(); track workflow.id) {
<div <div
class="workflow-option" class="license-type-option"
[class.selected]="basicForm.controls.workflowId.value === workflow.id" [class.selected]="basicForm.controls.workflowId.value === workflow.id"
(click)="basicForm.controls.workflowId.setValue(workflow.id)" (click)="basicForm.controls.workflowId.setValue(workflow.id)"
> >
<div class="workflow-name">{{ workflow.name }}</div> <div class="license-type-icon">
<div class="workflow-desc">{{ workflow.description || 'Standard approval workflow' }}</div> <mat-icon>{{ getLicenseIcon(workflow.name) }}</mat-icon>
</div>
<div class="license-type-content">
<div class="license-type-name">{{ getCleanLicenseName(workflow.name) }}</div>
<div class="license-type-desc">{{ workflow.description || getLicenseDescription(workflow.name) }}</div>
<div class="license-type-depts">
<mat-icon>groups</mat-icon>
<span>Requires {{ workflow.definition?.stages?.length || 'multiple' }} department approvals</span>
</div>
</div>
@if (basicForm.controls.workflowId.value === workflow.id) {
<div class="license-type-check">
<mat-icon>check_circle</mat-icon>
</div>
}
</div> </div>
} }
</div> </div>

View File

@@ -54,16 +54,16 @@ import { createSubmitDebounce } from '../../../shared/utils/form-utils';
} }
.form-header { .form-header {
background: linear-gradient(135deg, var(--dbim-blue-dark) 0%, var(--dbim-blue-mid) 100%); background: #FAFAFA;
padding: 32px; padding: 32px;
color: white;
text-align: center; text-align: center;
border-bottom: 1px solid #E0E0E0;
.header-icon { .header-icon {
width: 64px; width: 64px;
height: 64px; height: 64px;
margin: 0 auto 16px; margin: 0 auto 16px;
background: rgba(255, 255, 255, 0.2); background: #00897B;
border-radius: 16px; border-radius: 16px;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -73,6 +73,7 @@ import { createSubmitDebounce } from '../../../shared/utils/form-utils';
font-size: 32px; font-size: 32px;
width: 32px; width: 32px;
height: 32px; height: 32px;
color: white;
} }
} }
@@ -80,12 +81,13 @@ import { createSubmitDebounce } from '../../../shared/utils/form-utils';
margin: 0 0 8px; margin: 0 0 8px;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
color: #212121;
} }
p { p {
margin: 0; margin: 0;
opacity: 0.9;
font-size: 14px; font-size: 14px;
color: #757575;
} }
} }
@@ -151,41 +153,104 @@ import { createSubmitDebounce } from '../../../shared/utils/form-utils';
gap: 20px; gap: 20px;
} }
/* Workflow selection cards */ /* License Type selection cards */
.workflow-selection { .license-type-selection {
display: grid; display: flex;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); flex-direction: column;
gap: 16px; gap: 12px;
margin-top: 16px; margin-top: 16px;
} }
.workflow-option { .license-type-option {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 20px; padding: 20px;
border: 2px solid var(--dbim-linen); border: 2px solid #E0E0E0;
border-radius: 12px; border-radius: 12px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
background: white;
&:hover { &:hover {
border-color: var(--dbim-blue-light); border-color: #00897B;
background: var(--dbim-blue-subtle); background: #FAFAFA;
} }
&.selected { &.selected {
border-color: var(--dbim-blue-mid); border-color: #00897B;
background: var(--dbim-blue-subtle); background: #E0F2F1;
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1);
} }
.workflow-name { .license-type-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: #FAFAFA;
border: 1px solid #E0E0E0;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
color: #757575;
}
}
&.selected .license-type-icon {
background: #00897B;
border-color: #00897B;
mat-icon {
color: white;
}
}
.license-type-content {
flex: 1;
}
.license-type-name {
font-size: 16px;
font-weight: 600; font-weight: 600;
color: var(--dbim-brown); color: #212121;
margin-bottom: 4px; margin-bottom: 4px;
} }
.workflow-desc { .license-type-desc {
font-size: 13px; font-size: 13px;
color: var(--dbim-grey-2); color: #757575;
line-height: 1.4;
margin-bottom: 8px;
}
.license-type-depts {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #9E9E9E;
mat-icon {
font-size: 14px;
width: 14px;
height: 14px;
}
}
.license-type-check {
flex-shrink: 0;
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
color: #00897B;
}
} }
} }
@@ -434,6 +499,44 @@ export class RequestCreateComponent implements OnInit {
} }
} }
/** Clean workflow name for display - remove internal terms like "Approval Workflow" */
getCleanLicenseName(name: string): string {
return name
.replace(/\s*Approval\s*Workflow\s*/gi, '')
.replace(/\s*Workflow\s*/gi, '')
.trim();
}
getLicenseIcon(name: string): string {
const lower = name.toLowerCase();
if (lower.includes('resort') || lower.includes('hotel')) return 'hotel';
if (lower.includes('restaurant') || lower.includes('food')) return 'restaurant';
if (lower.includes('tour') || lower.includes('travel')) return 'tour';
if (lower.includes('health') || lower.includes('medical')) return 'local_hospital';
if (lower.includes('trade') || lower.includes('shop')) return 'storefront';
if (lower.includes('transport')) return 'local_shipping';
if (lower.includes('environment')) return 'eco';
if (lower.includes('forest')) return 'forest';
return 'description';
}
getLicenseDescription(name: string): string {
const lower = name.toLowerCase();
if (lower.includes('resort') || lower.includes('hotel')) {
return 'For hotels, resorts, guesthouses, and similar accommodation businesses in Goa.';
}
if (lower.includes('restaurant') || lower.includes('food')) {
return 'For restaurants, cafes, food stalls, and catering services.';
}
if (lower.includes('tour') || lower.includes('travel')) {
return 'For tour operators, travel agencies, and tourism-related services.';
}
if (lower.includes('trade') || lower.includes('shop')) {
return 'For retail shops, trading establishments, and commercial businesses.';
}
return 'Standard license application with multi-department approval.';
}
onSubmit(): void { onSubmit(): void {
// Debounce to prevent double-click submissions // Debounce to prevent double-click submissions
this.submitDebounce(() => this.performSubmit()); this.submitDebounce(() => this.performSubmit());