feat: Complete Pennyzo frontend with live waitlist tracking

- Live waitlist integration with backend API
- Real-time polling every 60 seconds
- Comprehensive API logging and error handling
- WaitlistContext for global state management
- Responsive UI with live data updates
- Registration form with automatic waitlist refresh
- Modern React/TypeScript architecture
- Tailwind CSS styling with luxury theme
This commit is contained in:
Hardik Sankhla
2025-09-26 04:25:47 +05:30
commit b7247f21a3
58 changed files with 9359 additions and 0 deletions

378
reports/report-viewer.html Normal file
View File

@@ -0,0 +1,378 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pennyzo API Status Report Viewer</title>
<style>
:root {
--deep-navy: #05174d;
--brass: #d4af37;
--off-white: #f5f5f5;
--light-gray: #e0e0e0;
--success: #28a745;
--danger: #dc3545;
--warning: #ffc107;
--info: #17a2b8;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: var(--off-white);
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--deep-navy);
color: white;
padding: 20px;
text-align: center;
border-bottom: 4px solid var(--brass);
}
h1 {
margin: 0;
font-size: 2em;
}
.report-selector {
margin: 20px 0;
padding: 15px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.summary-card {
background-color: white;
border-radius: 5px;
padding: 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
text-align: center;
}
.summary-card h3 {
margin-top: 0;
color: var(--deep-navy);
}
.summary-card .value {
font-size: 2em;
font-weight: bold;
}
.success-rate {
color: var(--success);
}
.table-container {
overflow-x: auto;
margin-top: 20px;
}
table {
width: 100%;
border-collapse: collapse;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--light-gray);
}
th {
background-color: var(--deep-navy);
color: white;
position: sticky;
top: 0;
}
tr:hover {
background-color: var(--light-gray);
}
.status {
padding: 5px 10px;
border-radius: 3px;
font-weight: bold;
}
.status-success {
background-color: var(--success);
color: white;
}
.status-failed {
background-color: var(--danger);
color: white;
}
.method {
font-weight: bold;
}
.method-GET {
color: var(--info);
}
.method-POST {
color: var(--warning);
}
.method-PUT {
color: var(--deep-navy);
}
.method-DELETE {
color: var(--danger);
}
.details-row {
display: none;
background-color: #f9f9f9;
}
.details-cell {
padding: 15px;
}
.details-container {
white-space: pre-wrap;
font-family: monospace;
background-color: #f5f5f5;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
max-height: 300px;
overflow-y: auto;
}
.expandable {
cursor: pointer;
}
.expandable::after {
content: '▼';
margin-left: 5px;
font-size: 0.8em;
}
footer {
margin-top: 30px;
text-align: center;
padding: 10px;
color: #666;
}
#file-input {
display: none;
}
.file-upload-btn {
background-color: var(--deep-navy);
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
}
.file-name {
margin-left: 10px;
font-style: italic;
}
.no-report {
padding: 50px;
text-align: center;
background-color: white;
border-radius: 5px;
margin-top: 20px;
}
</style>
</head>
<body>
<header>
<h1>Pennyzo API Status Report Viewer</h1>
</header>
<div class="container">
<div class="report-selector">
<h2>Select Report</h2>
<input type="file" id="file-input" accept=".json">
<button class="file-upload-btn" onclick="document.getElementById('file-input').click()">
Choose Report File
</button>
<span class="file-name" id="file-name"></span>
</div>
<div id="report-content">
<div class="no-report">
<h2>No Report Loaded</h2>
<p>Please select a report JSON file to view its contents.</p>
</div>
</div>
<footer>
<p>Pennyzo API Status Report Viewer | &copy; 2025 Pennyzo</p>
</footer>
</div>
<script>
document.getElementById('file-input').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
document.getElementById('file-name').textContent = file.name;
const reader = new FileReader();
reader.onload = function(e) {
try {
const report = JSON.parse(e.target.result);
renderReport(report);
} catch (error) {
alert('Error parsing JSON file: ' + error.message);
}
};
reader.readAsText(file);
}
});
function renderReport(report) {
const reportContent = document.getElementById('report-content');
// Create HTML for report
let html = `
<h2>${report.title}</h2>
<p><strong>Generated:</strong> ${new Date(report.timestamp).toLocaleString()}</p>
<p><strong>Duration:</strong> ${report.duration}</p>
<p><strong>Base URL:</strong> ${report.baseUrl}</p>
<div class="summary">
<div class="summary-card">
<h3>Total Endpoints</h3>
<div class="value">${report.summary.totalEndpoints}</div>
</div>
<div class="summary-card">
<h3>Successful</h3>
<div class="value" style="color: var(--success)">${report.summary.successful}</div>
</div>
<div class="summary-card">
<h3>Failed</h3>
<div class="value" style="color: var(--danger)">${report.summary.failed}</div>
</div>
<div class="summary-card">
<h3>Success Rate</h3>
<div class="value success-rate">${report.summary.successRate}</div>
</div>
</div>
<h2>Endpoint Results</h2>
<div class="table-container">
<table>
<thead>
<tr>
<th>Endpoint</th>
<th>Method</th>
<th>Status</th>
<th>Status Code</th>
<th>Response Time</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
`;
// Add rows for each result
report.results.forEach((result, index) => {
html += `
<tr class="expandable" onclick="toggleDetails(${index})">
<td>${result.endpoint}</td>
<td><span class="method method-${result.method}">${result.method}</span></td>
<td><span class="status status-${result.status.toLowerCase()}">${result.status}</span></td>
<td>${result.statusCode}</td>
<td>${result.responseTime}ms</td>
<td>${new Date(result.timestamp).toLocaleTimeString()}</td>
</tr>
<tr id="details-${index}" class="details-row">
<td colspan="6" class="details-cell">
<div class="details-container">
${renderDetails(result)}
</div>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
reportContent.innerHTML = html;
}
function renderDetails(result) {
let details = '';
if (result.error) {
details += `<strong>Error Message:</strong> ${result.error.message}\n\n`;
if (result.error.details) {
details += `<strong>Error Details:</strong>\n${JSON.stringify(result.error.details, null, 2)}\n\n`;
}
}
if (result.data) {
details += `<strong>Response Data:</strong>\n${JSON.stringify(result.data, null, 2)}`;
}
return details || 'No additional details available';
}
function toggleDetails(index) {
const detailsRow = document.getElementById(`details-${index}`);
if (detailsRow.style.display === 'table-row') {
detailsRow.style.display = 'none';
} else {
detailsRow.style.display = 'table-row';
}
}
// Load example report on page load
fetch('example-report.json')
.then(response => {
if (!response.ok) {
throw new Error('Example report not found');
}
return response.json();
})
.then(report => {
document.getElementById('file-name').textContent = 'example-report.json (pre-loaded)';
renderReport(report);
})
.catch(error => {
console.log('No example report pre-loaded:', error);
// No need to show an error, we'll just show the default "No Report Loaded" message
});
</script>
</body>
</html>