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:
5
reports/.gitignore
vendored
Normal file
5
reports/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Ignore all JSON files in this directory (API status reports)
|
||||
*.json
|
||||
|
||||
# But don't ignore any specifically created example files
|
||||
!example-*.json
|
||||
31
reports/README.md
Normal file
31
reports/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Reports Directory
|
||||
|
||||
This directory contains API status check reports generated by the API status checker tool.
|
||||
|
||||
## Files
|
||||
|
||||
- **JSON Reports**: Generated when running the extended API status checker
|
||||
- **example-report.json**: Sample report showing the expected format
|
||||
- **report-viewer.html**: HTML tool for visualizing the JSON reports
|
||||
|
||||
## Using the Report Viewer
|
||||
|
||||
To view a report:
|
||||
|
||||
1. Open the `report-viewer.html` file in a web browser
|
||||
2. Click "Choose Report File" to select a JSON report
|
||||
3. Review the report details including endpoint statuses
|
||||
|
||||
## Report Format
|
||||
|
||||
Each report includes:
|
||||
|
||||
- Summary statistics (success rate, endpoint counts)
|
||||
- Detailed endpoint results
|
||||
- Response times
|
||||
- Error details
|
||||
- API response data (when in verbose mode)
|
||||
|
||||
## Retention Policy
|
||||
|
||||
By default, all JSON reports in this directory are ignored by git. If you want to keep certain reports, consider copying them elsewhere or modifying the `.gitignore` file in this directory.
|
||||
74
reports/example-report.json
Normal file
74
reports/example-report.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"title": "Pennyzo API Status Check Report",
|
||||
"timestamp": "2025-09-25T10:15:30.123Z",
|
||||
"duration": "1.15 seconds",
|
||||
"summary": {
|
||||
"totalEndpoints": 3,
|
||||
"successful": 2,
|
||||
"failed": 1,
|
||||
"successRate": "66.7%"
|
||||
},
|
||||
"baseUrl": "https://api.pennyzo.com/api/v1/general",
|
||||
"results": [
|
||||
{
|
||||
"endpoint": "/system/health",
|
||||
"method": "GET",
|
||||
"status": "SUCCESS",
|
||||
"statusCode": 200,
|
||||
"responseTime": 342,
|
||||
"timestamp": "2025-09-25T10:15:30.123Z",
|
||||
"error": null,
|
||||
"data": {
|
||||
"status": "operational",
|
||||
"service": "Pennyzo General API",
|
||||
"version": "1.0.0",
|
||||
"environment": "production",
|
||||
"message": "Service is healthy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": "/registration/company/identity-rules",
|
||||
"method": "GET",
|
||||
"status": "SUCCESS",
|
||||
"statusCode": 200,
|
||||
"responseTime": 285,
|
||||
"timestamp": "2025-09-25T10:15:30.456Z",
|
||||
"error": null,
|
||||
"data": {
|
||||
"rules": [
|
||||
{
|
||||
"company_type": "PRIVATE_LIMITED",
|
||||
"required_fields": ["cin_number"]
|
||||
},
|
||||
{
|
||||
"company_type": "LLP",
|
||||
"required_fields": ["llp_number"]
|
||||
},
|
||||
{
|
||||
"company_type": "PROPRIETORSHIP",
|
||||
"required_fields": ["pan_number"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": "/auth/login",
|
||||
"method": "POST",
|
||||
"status": "FAILED",
|
||||
"statusCode": 401,
|
||||
"responseTime": 520,
|
||||
"timestamp": "2025-09-25T10:15:30.789Z",
|
||||
"error": {
|
||||
"message": "Request failed with status code 401",
|
||||
"details": {
|
||||
"error": {
|
||||
"type": "unauthorized",
|
||||
"code": 401,
|
||||
"message": "Invalid credentials"
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": null
|
||||
}
|
||||
]
|
||||
}
|
||||
378
reports/report-viewer.html
Normal file
378
reports/report-viewer.html
Normal 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 | © 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>
|
||||
Reference in New Issue
Block a user