Skip to content

Commit eca6062

Browse files
Copilot0xrinegade
andcommitted
Implement advanced UI feedback components with comprehensive testing
Co-authored-by: 0xrinegade <[email protected]>
1 parent 2af8e45 commit eca6062

10 files changed

+2735
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
import React, { useState, useEffect } from 'react';
2+
import PropTypes from 'prop-types';
3+
import '../../styles/TransactionAnalytics.css';
4+
5+
/**
6+
* TransactionAnalytics component
7+
* Displays transaction success rates, timing analytics, and trust indicators
8+
* Provides transparency and builds user confidence
9+
*/
10+
const TransactionAnalytics = ({
11+
userStats = {},
12+
globalStats = {},
13+
recentTransactions = [],
14+
showPersonalStats = true,
15+
showGlobalStats = true,
16+
showRecentActivity = true,
17+
timeframe = '7d',
18+
compact = false,
19+
onTimeframeChange = null
20+
}) => {
21+
const [selectedTimeframe, setSelectedTimeframe] = useState(timeframe);
22+
const [isExpanded, setIsExpanded] = useState(!compact);
23+
24+
// Calculate statistics
25+
const calculateStats = (transactions) => {
26+
if (!transactions || transactions.length === 0) {
27+
return {
28+
total: 0,
29+
successful: 0,
30+
failed: 0,
31+
pending: 0,
32+
successRate: 0,
33+
averageTime: 0,
34+
totalValue: 0
35+
};
36+
}
37+
38+
const total = transactions.length;
39+
const successful = transactions.filter(tx => tx.status === 'success').length;
40+
const failed = transactions.filter(tx => tx.status === 'error').length;
41+
const pending = transactions.filter(tx => tx.status === 'pending').length;
42+
const successRate = total > 0 ? Math.round((successful / total) * 100) : 0;
43+
44+
const completedTxs = transactions.filter(tx => tx.status === 'success' && tx.duration);
45+
const averageTime = completedTxs.length > 0
46+
? Math.round(completedTxs.reduce((sum, tx) => sum + tx.duration, 0) / completedTxs.length)
47+
: 0;
48+
49+
const totalValue = transactions
50+
.filter(tx => tx.status === 'success' && tx.value)
51+
.reduce((sum, tx) => sum + tx.value, 0);
52+
53+
return {
54+
total,
55+
successful,
56+
failed,
57+
pending,
58+
successRate,
59+
averageTime,
60+
totalValue
61+
};
62+
};
63+
64+
const stats = calculateStats(recentTransactions);
65+
66+
// Handle timeframe change
67+
const handleTimeframeChange = (newTimeframe) => {
68+
setSelectedTimeframe(newTimeframe);
69+
if (onTimeframeChange) {
70+
onTimeframeChange(newTimeframe);
71+
}
72+
};
73+
74+
// Format value display
75+
const formatValue = (value) => {
76+
if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
77+
if (value >= 1000) return `${(value / 1000).toFixed(1)}K`;
78+
return value.toFixed(2);
79+
};
80+
81+
// Format time display
82+
const formatTime = (seconds) => {
83+
if (seconds < 60) return `${seconds}s`;
84+
const minutes = Math.floor(seconds / 60);
85+
const remainingSeconds = seconds % 60;
86+
return `${minutes}m ${remainingSeconds}s`;
87+
};
88+
89+
// Get success rate color
90+
const getSuccessRateColor = (rate) => {
91+
if (rate >= 95) return '#10b981';
92+
if (rate >= 85) return '#f59e0b';
93+
return '#ef4444';
94+
};
95+
96+
// Get status indicator
97+
const getStatusIndicator = (rate) => {
98+
if (rate >= 95) return '🟢';
99+
if (rate >= 85) return '🟡';
100+
return '🔴';
101+
};
102+
103+
if (compact) {
104+
return (
105+
<div className="transaction-analytics-compact">
106+
<div className="stats-summary">
107+
<div className="stat-item">
108+
<span className="stat-value" style={{ color: getSuccessRateColor(stats.successRate) }}>
109+
{stats.successRate}%
110+
</span>
111+
<span className="stat-label">Success Rate</span>
112+
</div>
113+
114+
<div className="stat-item">
115+
<span className="stat-value">{stats.total}</span>
116+
<span className="stat-label">Total Transactions</span>
117+
</div>
118+
119+
{stats.averageTime > 0 && (
120+
<div className="stat-item">
121+
<span className="stat-value">{formatTime(stats.averageTime)}</span>
122+
<span className="stat-label">Avg Time</span>
123+
</div>
124+
)}
125+
</div>
126+
127+
</div>
128+
);
129+
}
130+
131+
return (
132+
<div className="transaction-analytics">
133+
<div className="analytics-header">
134+
<h3 className="analytics-title">Transaction Analytics</h3>
135+
136+
<div className="analytics-controls">
137+
<div className="timeframe-selector">
138+
{['24h', '7d', '30d', '90d'].map((tf) => (
139+
<button
140+
key={tf}
141+
className={`timeframe-button ${tf === selectedTimeframe ? 'active' : ''}`}
142+
onClick={() => handleTimeframeChange(tf)}
143+
>
144+
{tf}
145+
</button>
146+
))}
147+
</div>
148+
149+
<button
150+
className="expand-button"
151+
onClick={() => setIsExpanded(!isExpanded)}
152+
>
153+
{isExpanded ? '▲' : '▼'}
154+
</button>
155+
</div>
156+
</div>
157+
158+
{isExpanded && (
159+
<div className="analytics-content">
160+
{/* Overall Success Rate */}
161+
<div className="success-rate-card">
162+
<div className="success-rate-header">
163+
<h4>Overall Success Rate</h4>
164+
<span className="status-indicator">
165+
{getStatusIndicator(stats.successRate)}
166+
</span>
167+
</div>
168+
169+
<div className="success-rate-display">
170+
<div className="rate-circle">
171+
<svg viewBox="0 0 36 36" className="circular-chart">
172+
<path
173+
className="circle-bg"
174+
d="M18 2.0845
175+
a 15.9155 15.9155 0 0 1 0 31.831
176+
a 15.9155 15.9155 0 0 1 0 -31.831"
177+
/>
178+
<path
179+
className="circle"
180+
strokeDasharray={`${stats.successRate}, 100`}
181+
d="M18 2.0845
182+
a 15.9155 15.9155 0 0 1 0 31.831
183+
a 15.9155 15.9155 0 0 1 0 -31.831"
184+
style={{ stroke: getSuccessRateColor(stats.successRate) }}
185+
/>
186+
</svg>
187+
<div className="rate-text">
188+
<span className="rate-percentage">{stats.successRate}%</span>
189+
<span className="rate-label">Success</span>
190+
</div>
191+
</div>
192+
193+
<div className="rate-breakdown">
194+
<div className="breakdown-item success">
195+
<span className="breakdown-count">{stats.successful}</span>
196+
<span className="breakdown-label">Successful</span>
197+
</div>
198+
<div className="breakdown-item failed">
199+
<span className="breakdown-count">{stats.failed}</span>
200+
<span className="breakdown-label">Failed</span>
201+
</div>
202+
{stats.pending > 0 && (
203+
<div className="breakdown-item pending">
204+
<span className="breakdown-count">{stats.pending}</span>
205+
<span className="breakdown-label">Pending</span>
206+
</div>
207+
)}
208+
</div>
209+
</div>
210+
</div>
211+
212+
{/* Performance Metrics */}
213+
<div className="metrics-grid">
214+
<div className="metric-card">
215+
<div className="metric-icon"></div>
216+
<div className="metric-content">
217+
<span className="metric-value">
218+
{stats.averageTime > 0 ? formatTime(stats.averageTime) : 'N/A'}
219+
</span>
220+
<span className="metric-label">Average Completion Time</span>
221+
</div>
222+
</div>
223+
224+
<div className="metric-card">
225+
<div className="metric-icon">💰</div>
226+
<div className="metric-content">
227+
<span className="metric-value">
228+
{stats.totalValue > 0 ? `$${formatValue(stats.totalValue)}` : 'N/A'}
229+
</span>
230+
<span className="metric-label">Total Value Processed</span>
231+
</div>
232+
</div>
233+
234+
<div className="metric-card">
235+
<div className="metric-icon">🔄</div>
236+
<div className="metric-content">
237+
<span className="metric-value">{stats.total}</span>
238+
<span className="metric-label">Total Transactions</span>
239+
</div>
240+
</div>
241+
242+
{globalStats.networkHealth && (
243+
<div className="metric-card">
244+
<div className="metric-icon">🌐</div>
245+
<div className="metric-content">
246+
<span className="metric-value">{globalStats.networkHealth}%</span>
247+
<span className="metric-label">Network Health</span>
248+
</div>
249+
</div>
250+
)}
251+
</div>
252+
253+
{/* Trust Indicators */}
254+
<div className="trust-section">
255+
<h4>Trust & Security Indicators</h4>
256+
<div className="trust-indicators">
257+
<div className="trust-item">
258+
<span className="trust-icon">🔒</span>
259+
<span className="trust-text">End-to-end encryption</span>
260+
<span className="trust-status verified"></span>
261+
</div>
262+
263+
<div className="trust-item">
264+
<span className="trust-icon">🛡️</span>
265+
<span className="trust-text">Multi-signature verification</span>
266+
<span className="trust-status verified"></span>
267+
</div>
268+
269+
<div className="trust-item">
270+
<span className="trust-icon"></span>
271+
<span className="trust-text">Lightning-fast processing</span>
272+
<span className="trust-status verified"></span>
273+
</div>
274+
275+
<div className="trust-item">
276+
<span className="trust-icon">📊</span>
277+
<span className="trust-text">Real-time monitoring</span>
278+
<span className="trust-status verified"></span>
279+
</div>
280+
</div>
281+
</div>
282+
283+
{/* Recent Activity Preview */}
284+
{showRecentActivity && recentTransactions.length > 0 && (
285+
<div className="recent-activity">
286+
<h4>Recent Transaction Activity</h4>
287+
<div className="activity-list">
288+
{recentTransactions.slice(0, 5).map((tx, index) => (
289+
<div key={index} className={`activity-item ${tx.status}`}>
290+
<div className="activity-status">
291+
{tx.status === 'success' ? '✓' :
292+
tx.status === 'error' ? '✗' : '⏳'}
293+
</div>
294+
<div className="activity-details">
295+
<span className="activity-type">{tx.type || 'Transaction'}</span>
296+
<span className="activity-time">
297+
{new Date(tx.timestamp).toLocaleTimeString()}
298+
</span>
299+
</div>
300+
{tx.value && (
301+
<div className="activity-value">
302+
${formatValue(tx.value)}
303+
</div>
304+
)}
305+
</div>
306+
))}
307+
</div>
308+
</div>
309+
)}
310+
</div>
311+
)}
312+
313+
</div>
314+
);
315+
};
316+
317+
TransactionAnalytics.propTypes = {
318+
userStats: PropTypes.object,
319+
globalStats: PropTypes.object,
320+
recentTransactions: PropTypes.arrayOf(
321+
PropTypes.shape({
322+
id: PropTypes.string,
323+
type: PropTypes.string,
324+
status: PropTypes.oneOf(['success', 'error', 'pending']).isRequired,
325+
timestamp: PropTypes.string.isRequired,
326+
duration: PropTypes.number,
327+
value: PropTypes.number
328+
})
329+
),
330+
showPersonalStats: PropTypes.bool,
331+
showGlobalStats: PropTypes.bool,
332+
showRecentActivity: PropTypes.bool,
333+
timeframe: PropTypes.oneOf(['24h', '7d', '30d', '90d']),
334+
compact: PropTypes.bool,
335+
onTimeframeChange: PropTypes.func
336+
};
337+
338+
export default TransactionAnalytics;

0 commit comments

Comments
 (0)