From 84b00fa1c5094b57ec37d2209f7f105a1171ef82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWilfred007=E2=80=9D?= <“adzerwilfred007@gmail.com”> Date: Tue, 21 Jul 2026 18:15:26 +0100 Subject: [PATCH 01/14] feat: add credential metadata display component with basic structure - Created new React component for displaying credential metadata - Added expandable accordion interface for credential details - Implemented accessibility features with ARIA attributes - Added responsive design with Tailwind CSS styling --- .../credential-metadata-display.tsx | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 frontend/src/components/credential-metadata-display.tsx diff --git a/frontend/src/components/credential-metadata-display.tsx b/frontend/src/components/credential-metadata-display.tsx new file mode 100644 index 00000000..70642dbf --- /dev/null +++ b/frontend/src/components/credential-metadata-display.tsx @@ -0,0 +1,218 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { Info, Calendar, Clock, Building2, Tag, ChevronDown, ChevronUp } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useAccessibility } from '@/contexts/AccessibilityContext'; + +interface CredentialMetadata { + id: string; + name: string; + type: string; + issuer: string; + issuerName: string; + issuedAt: string; + updatedAt: string; + expiresAt?: string; + status: 'active' | 'expired' | 'revoked'; + description?: string; +} + +interface CredentialMetadataDisplayProps { + credentials: CredentialMetadata[]; +} + +export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDisplayProps) { + const [expandedId, setExpandedId] = useState(null); + const { announceToScreenReader } = useAccessibility(); + + const handleToggleExpand = useCallback( + (id: string) => { + const newExpandedId = expandedId === id ? null : id; + setExpandedId(newExpandedId); + announceToScreenReader( + newExpandedId ? 'Expanded credential details' : 'Collapsed credential details' + ); + }, + [expandedId, announceToScreenReader] + ); + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + const formatDateTime = (dateString: string) => { + return new Date(dateString).toLocaleString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'active': + return 'bg-green-500/20 text-green-400 border-green-500/30'; + case 'expired': + return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'; + case 'revoked': + return 'bg-red-500/20 text-red-400 border-red-500/30'; + default: + return 'bg-gray-500/20 text-gray-400 border-gray-500/30'; + } + }; + + return ( +
+

+ Credential Metadata +

+ +
+ + {credentials.length === 0 ? ( + + + ) : ( + credentials.map((credential, index) => ( + + {/* Header */} +
handleToggleExpand(credential.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleToggleExpand(credential.id); + } + }} + tabIndex={0} + role="button" + aria-expanded={expandedId === credential.id} + aria-controls={`metadata-details-${credential.id}`} + > +
+
+ {expandedId === credential.id ? ( +
+ + {/* Details */} + + {expandedId === credential.id && ( + +
+ {/* Issuer Info */} +
+
+ + {/* Credential Type */} +
+
+ + {/* Dates */} +
+
+ +
+
+ + {/* Expiry if present */} + {credential.expiresAt && ( +
+
+ )} + + {/* Description if present */} + {credential.description && ( +
+

Description

+

{credential.description}

+
+ )} +
+
+ )} +
+
+ )) + )} +
+
+
+ ); +} From 35d659becc70e17831cbd80da4f1fe0a2bf45846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWilfred007=E2=80=9D?= <“adzerwilfred007@gmail.com”> Date: Tue, 21 Jul 2026 18:16:09 +0100 Subject: [PATCH 02/14] feat: enhance metadata display with summary and relative time - Added credential status summary section (active/expired/revoked counts) - Implemented relative time formatting for dates - Added status indicators with color-coded badges - Improved visual hierarchy with summary section --- .../credential-metadata-display.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/frontend/src/components/credential-metadata-display.tsx b/frontend/src/components/credential-metadata-display.tsx index 70642dbf..dc57baaf 100644 --- a/frontend/src/components/credential-metadata-display.tsx +++ b/frontend/src/components/credential-metadata-display.tsx @@ -37,6 +37,10 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis [expandedId, announceToScreenReader] ); + const activeCount = credentials.filter((c) => c.status === 'active').length; + const expiredCount = credentials.filter((c) => c.status === 'expired').length; + const revokedCount = credentials.filter((c) => c.status === 'revoked').length; + const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', @@ -55,6 +59,20 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis }); }; + const getRelativeTime = (dateString: string) => { + const now = new Date(); + const date = new Date(dateString); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Yesterday'; + if (diffDays < 7) return `${diffDays} days ago`; + if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`; + if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`; + return `${Math.floor(diffDays / 365)} years ago`; + }; + const getStatusColor = (status: string) => { switch (status) { case 'active': @@ -74,6 +92,36 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis Credential Metadata + {/* Summary Section */} + {credentials.length > 0 && ( +
+
+
+ Total: + {credentials.length} +
+ {activeCount > 0 && ( +
+ + {activeCount} Active +
+ )} + {expiredCount > 0 && ( +
+ + {expiredCount} Expired +
+ )} + {revokedCount > 0 && ( +
+ + {revokedCount} Revoked +
+ )} +
+
+ )} +
{credentials.length === 0 ? ( From 54b61d817e1b9090c30fd9aa67de1f2ecbc95dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWilfred007=E2=80=9D?= <“adzerwilfred007@gmail.com”> Date: Tue, 21 Jul 2026 18:17:12 +0100 Subject: [PATCH 03/14] feat: add search and filter functionality for credentials - Implemented search by credential name, issuer, and type - Added status filter dropdown (active/expired/revoked) - Created real-time filtering with useMemo for performance - Added search input with icon and filter controls --- .../credential-metadata-display.tsx | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/credential-metadata-display.tsx b/frontend/src/components/credential-metadata-display.tsx index dc57baaf..ff818cb4 100644 --- a/frontend/src/components/credential-metadata-display.tsx +++ b/frontend/src/components/credential-metadata-display.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { Info, Calendar, Clock, Building2, Tag, ChevronDown, ChevronUp } from 'lucide-react'; +import { useState, useCallback, useMemo } from 'react'; +import { Info, Calendar, Clock, Building2, Tag, ChevronDown, ChevronUp, Search, Filter } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useAccessibility } from '@/contexts/AccessibilityContext'; @@ -24,8 +24,21 @@ interface CredentialMetadataDisplayProps { export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDisplayProps) { const [expandedId, setExpandedId] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'expired' | 'revoked'>('all'); const { announceToScreenReader } = useAccessibility(); + const filteredCredentials = useMemo(() => { + return credentials.filter((credential) => { + const matchesSearch = + credential.name.toLowerCase().includes(searchQuery.toLowerCase()) || + credential.issuerName.toLowerCase().includes(searchQuery.toLowerCase()) || + credential.type.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesStatus = statusFilter === 'all' || credential.status === statusFilter; + return matchesSearch && matchesStatus; + }); + }, [credentials, searchQuery, statusFilter]); + const handleToggleExpand = useCallback( (id: string) => { const newExpandedId = expandedId === id ? null : id; @@ -122,9 +135,42 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis
)} + {/* Search and Filter */} + {credentials.length > 0 && ( +
+
+
+
+
+
+
+
+ )} +
- {credentials.length === 0 ? ( + {filteredCredentials.length === 0 ? ( No credentials to display

) : ( - credentials.map((credential, index) => ( + filteredCredentials.map((credential, index) => ( Date: Tue, 21 Jul 2026 18:17:40 +0100 Subject: [PATCH 04/14] feat: improve credential header with inline metadata preview - Added inline preview of key metadata (type, issuer, date) - Improved header layout with better visual hierarchy - Added relative time display in header - Enhanced readability with icon-labeled metadata fields --- .../credential-metadata-display.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/credential-metadata-display.tsx b/frontend/src/components/credential-metadata-display.tsx index ff818cb4..a830f2a2 100644 --- a/frontend/src/components/credential-metadata-display.tsx +++ b/frontend/src/components/credential-metadata-display.tsx @@ -212,15 +212,27 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis >
- {expandedId === credential.id ? ( -
{/* Details */} @@ -724,6 +771,28 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis )} + + {/* Toast Notification */} + + {toast.show && ( + + +
+

{toast.title}

+ {toast.description &&

{toast.description}

} +
+
+ )} +
); } From 604c4ccf25cfe907b68dac3f27b2bdd7363d979d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWilfred007=E2=80=9D?= <“adzerwilfred007@gmail.com”> Date: Tue, 21 Jul 2026 18:37:50 +0100 Subject: [PATCH 14/14] feat: enhance search with field-specific filtering - Added search across description, version, and issuer ID fields - Implemented clear search button - Added results count display showing filtered vs total - Improved search placeholder text with field examples --- .../credential-metadata-display.tsx | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/credential-metadata-display.tsx b/frontend/src/components/credential-metadata-display.tsx index 9eca12bd..421cf3c9 100644 --- a/frontend/src/components/credential-metadata-display.tsx +++ b/frontend/src/components/credential-metadata-display.tsx @@ -52,10 +52,14 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis const filteredCredentials = useMemo(() => { return credentials.filter((credential) => { + const query = searchQuery.toLowerCase(); const matchesSearch = - credential.name.toLowerCase().includes(searchQuery.toLowerCase()) || - credential.issuerName.toLowerCase().includes(searchQuery.toLowerCase()) || - credential.type.toLowerCase().includes(searchQuery.toLowerCase()); + credential.name.toLowerCase().includes(query) || + credential.issuerName.toLowerCase().includes(query) || + credential.type.toLowerCase().includes(query) || + credential.issuer.toLowerCase().includes(query) || + (credential.description && credential.description.toLowerCase().includes(query)) || + (credential.version && credential.version.toLowerCase().includes(query)); const matchesStatus = statusFilter === 'all' || credential.status === statusFilter; return matchesSearch && matchesStatus; }); @@ -370,12 +374,21 @@ export function CredentialMetadataDisplay({ credentials }: CredentialMetadataDis