seller_central_backend/templates/dashboard.html
vickytechkey 4618bb0c6b
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 23s
feat: add dedicated Create Account form with field inputs and sub-tabs
2026-09-13 10:18:47 +05:30

1956 lines
66 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seller Central — Developer Studio & Log Viewer</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-main: #0b0f19;
--bg-card: #111827;
--bg-card-hover: #162032;
--bg-input: #1e293b;
--border-color: #243247;
--border-highlight: #3b82f6;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--text-faint: #64748b;
--primary: #3b82f6;
--primary-hover: #2563eb;
--primary-glow: rgba(59, 130, 246, 0.25);
--success: #10b981;
--success-glow: rgba(16, 185, 129, 0.2);
--warning: #f59e0b;
--warning-glow: rgba(245, 158, 11, 0.2);
--danger: #ef4444;
--danger-glow: rgba(239, 68, 68, 0.2);
--info: #06b6d4;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--font-mono: 'JetBrains Mono', Consolas, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-main);
color: var(--text-main);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* Top Navbar */
header {
background: rgba(17, 24, 39, 0.85);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 50;
padding: 0.75rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
}
.brand-logo {
width: 34px;
height: 34px;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: white;
font-size: 1.1rem;
box-shadow: 0 0 15px var(--primary-glow);
}
.brand-text h1 {
font-size: 1.1rem;
font-weight: 700;
color: var(--text-main);
letter-spacing: -0.02em;
}
.brand-text p {
font-size: 0.75rem;
color: var(--text-muted);
}
.nav-tabs {
display: flex;
background: var(--bg-input);
padding: 3px;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
gap: 2px;
}
.nav-tab-btn {
background: transparent;
border: none;
color: var(--text-muted);
padding: 0.45rem 1rem;
font-size: 0.85rem;
font-weight: 500;
border-radius: var(--radius-sm);
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.15s ease;
}
.nav-tab-btn:hover {
color: var(--text-main);
}
.nav-tab-btn.active {
background: var(--primary);
color: white;
box-shadow: 0 2px 8px var(--primary-glow);
}
.nav-meta {
display: flex;
align-items: center;
gap: 0.75rem;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 0.4rem;
background: rgba(16, 185, 129, 0.12);
border: 1px solid rgba(16, 185, 129, 0.3);
color: #34d399;
font-size: 0.75rem;
font-weight: 600;
padding: 0.3rem 0.65rem;
border-radius: 9999px;
}
.status-dot {
width: 7px;
height: 7px;
background-color: #10b981;
border-radius: 50%;
box-shadow: 0 0 8px #10b981;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.85); }
}
.auth-badge {
font-size: 0.75rem;
padding: 0.3rem 0.65rem;
border-radius: var(--radius-sm);
background: var(--bg-card);
border: 1px solid var(--border-color);
color: var(--text-muted);
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.auth-badge strong {
color: var(--text-main);
}
/* Main Container */
main {
flex: 1;
padding: 1.5rem;
max-width: 1560px;
width: 100%;
margin: 0 auto;
}
.tab-pane {
display: none;
}
.tab-pane.active {
display: block;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
/* Grid Layouts */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
}
@media (max-width: 1024px) {
.grid-2, .grid-3 {
grid-template-columns: 1fr;
}
}
/* Card Component */
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.75rem;
}
.card-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-main);
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title-tag {
font-size: 0.7rem;
background: rgba(59, 130, 246, 0.15);
color: #60a5fa;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
}
/* Form Inputs */
.form-group {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.03em;
}
input, select, textarea {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-main);
padding: 0.55rem 0.75rem;
font-size: 0.85rem;
font-family: inherit;
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
width: 100%;
}
input:focus, select:focus, textarea:focus {
border-color: var(--border-highlight);
box-shadow: 0 0 0 2px var(--primary-glow);
}
textarea {
resize: vertical;
font-family: var(--font-mono);
font-size: 0.8rem;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
padding: 0.55rem 1rem;
font-size: 0.85rem;
font-weight: 500;
border-radius: var(--radius-sm);
border: none;
cursor: pointer;
transition: all 0.15s ease;
text-decoration: none;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-hover);
box-shadow: 0 2px 10px var(--primary-glow);
}
.btn-secondary {
background: var(--bg-input);
color: var(--text-main);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-card-hover);
border-color: #3b82f6;
}
.btn-success {
background: #059669;
color: white;
}
.btn-success:hover {
background: #10b981;
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn-danger:hover {
background: #ef4444;
}
.btn-sm {
padding: 0.35rem 0.65rem;
font-size: 0.75rem;
}
/* Log Viewer Dashboard Styles */
.log-dashboard-layout {
display: flex;
flex-direction: column;
gap: 1rem;
}
.stats-bar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 0.85rem 1rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.stat-card .stat-info p {
font-size: 0.72rem;
color: var(--text-muted);
text-transform: uppercase;
font-weight: 600;
}
.stat-card .stat-info h3 {
font-size: 1.4rem;
font-weight: 700;
color: var(--text-main);
margin-top: 2px;
}
.stat-card.error-card { border-left: 4px solid var(--danger); }
.stat-card.error-card .stat-info h3 { color: #f87171; }
.stat-card.warn-card { border-left: 4px solid var(--warning); }
.stat-card.warn-card .stat-info h3 { color: #fbbf24; }
.stat-card.info-card { border-left: 4px solid var(--info); }
.stat-card.info-card .stat-info h3 { color: #38bdf8; }
.stat-card.total-card { border-left: 4px solid var(--primary); }
/* Filter Toolbar */
.filter-toolbar {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1rem;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.filter-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.6rem;
}
.pill-btn-group {
display: flex;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
overflow: hidden;
}
.pill-btn {
background: transparent;
border: none;
color: var(--text-muted);
padding: 0.35rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
}
.pill-btn:hover {
color: var(--text-main);
background: rgba(255, 255, 255, 0.05);
}
.pill-btn.active {
background: var(--primary);
color: white;
}
.pill-btn.pill-error.active { background: var(--danger); }
.pill-btn.pill-warn.active { background: var(--warning); color: black; }
.pill-btn.pill-info.active { background: var(--info); color: black; }
.pill-btn.pill-debug.active { background: #8b5cf6; }
.search-box-wrap {
position: relative;
min-width: 260px;
}
.search-box-wrap input {
padding-left: 2rem;
}
.search-icon {
position: absolute;
left: 0.7rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
font-size: 0.85rem;
pointer-events: none;
}
/* Terminal Console */
.terminal-container {
background: #070a12;
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
overflow: hidden;
display: flex;
flex-direction: column;
height: 620px;
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6);
}
.terminal-header {
background: #0d121f;
border-bottom: 1px solid var(--border-color);
padding: 0.6rem 1rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.terminal-title {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 0.6rem;
}
.terminal-dots {
display: flex;
gap: 5px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.dot-red { background: #ff5f56; }
.dot-yellow { background: #ffbd2e; }
.dot-green { background: #27c93f; }
.terminal-body {
flex: 1;
padding: 0.85rem 1rem;
overflow-y: auto;
font-family: var(--font-mono);
font-size: 0.82rem;
line-height: 1.55;
color: #cbd5e1;
}
.terminal-body::-webkit-scrollbar {
width: 8px;
}
.terminal-body::-webkit-scrollbar-track {
background: #070a12;
}
.terminal-body::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 4px;
}
.log-line {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 2px 4px;
border-radius: 4px;
transition: background 0.1s ease;
white-space: pre-wrap;
word-break: break-all;
}
.log-line:hover {
background: rgba(255, 255, 255, 0.04);
}
.log-num {
color: #475569;
user-select: none;
min-width: 38px;
text-align: right;
font-size: 0.75rem;
}
.log-ts {
color: #64748b;
font-size: 0.75rem;
white-space: nowrap;
}
.log-badge {
font-size: 0.68rem;
font-weight: 700;
padding: 1px 6px;
border-radius: 3px;
letter-spacing: 0.04em;
white-space: nowrap;
}
.badge-error { background: rgba(239, 68, 68, 0.2); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.4); }
.badge-warn, .badge-warning { background: rgba(245, 158, 11, 0.2); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.4); }
.badge-info { background: rgba(59, 130, 246, 0.2); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.4); }
.badge-debug { background: rgba(139, 92, 246, 0.2); color: #c084fc; border: 1px solid rgba(139, 92, 246, 0.4); }
.log-msg {
flex: 1;
color: #e2e8f0;
}
.log-line.is-error .log-msg { color: #fca5a5; }
.log-line.is-warning .log-msg { color: #fde68a; }
.log-highlight {
background: #eab308;
color: #000;
padding: 0 2px;
border-radius: 2px;
}
.terminal-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-faint);
gap: 0.5rem;
}
/* Response Inspector */
.response-inspector {
background: #070a12;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
margin-top: 0.75rem;
}
.response-header {
background: #0d121f;
border-bottom: 1px solid var(--border-color);
padding: 0.45rem 0.75rem;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.75rem;
}
.status-code {
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
}
.status-2xx { background: rgba(16, 185, 129, 0.2); color: #34d399; }
.status-4xx { background: rgba(245, 158, 11, 0.2); color: #fbbf24; }
.status-5xx { background: rgba(239, 68, 68, 0.2); color: #f87171; }
.response-body {
padding: 0.75rem;
max-height: 280px;
overflow-y: auto;
font-family: var(--font-mono);
font-size: 0.78rem;
white-space: pre-wrap;
word-break: break-all;
color: #cbd5e1;
}
/* Table */
.data-table-wrap {
overflow-x: auto;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.8rem;
}
th {
background: var(--bg-input);
text-align: left;
padding: 0.6rem 0.75rem;
color: var(--text-muted);
font-size: 0.72rem;
text-transform: uppercase;
font-weight: 600;
border-bottom: 1px solid var(--border-color);
}
td {
padding: 0.6rem 0.75rem;
border-bottom: 1px solid var(--border-color);
color: var(--text-main);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: rgba(255, 255, 255, 0.02);
}
/* Modals & Alerts */
.alert-toast {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
background: var(--bg-card);
border: 1px solid var(--border-color);
padding: 0.85rem 1.25rem;
border-radius: var(--radius-md);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.85rem;
z-index: 1000;
animation: slideUp 0.25s ease;
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
</style>
</head>
<body>
<!-- Header -->
<header>
<div class="brand">
<div class="brand-logo">⚡</div>
<div class="brand-text">
<h1>Seller Central Backend</h1>
<p>Developer Testing Studio &amp; Real-time Log Dashboard</p>
</div>
</div>
<!-- Navigation Tabs -->
<div class="nav-tabs">
<button class="nav-tab-btn active" onclick="switchTab('logs-tab')">
<span>📜</span> Log Dashboard
</button>
<button class="nav-tab-btn" onclick="switchTab('tester-tab')">
<span>🧪</span> API Test Suite
</button>
<button class="nav-tab-btn" onclick="switchTab('sandbox-tab')">
<span>⚡</span> API Sandbox
</button>
</div>
<!-- Header Meta & Auth Status -->
<div class="nav-meta">
<div class="status-pill">
<span class="status-dot"></span>
<span>Online ({{ environment }})</span>
</div>
<div class="auth-badge" id="headerAuthBadge" title="JWT Access Token Status">
Auth: <strong id="headerAuthUser">Dummy / Anonymous</strong>
</div>
</div>
</header>
<!-- Main Content -->
<main>
<!-- ================================================================= -->
<!-- TAB 1: LOG DASHBOARD (WITH RICH FILTERS) -->
<!-- ================================================================= -->
<div id="logs-tab" class="tab-pane active">
<div class="log-dashboard-layout">
<!-- Stats Overview Bar -->
<div class="stats-bar" id="logStatsBar">
<div class="stat-card total-card">
<div class="stat-info">
<p>Total Lines Displayed</p>
<h3 id="statTotalLines">0</h3>
</div>
<span style="font-size: 1.5rem;">📄</span>
</div>
<div class="stat-card error-card">
<div class="stat-info">
<p>Errors (Critical)</p>
<h3 id="statErrorCount">0</h3>
</div>
<span style="font-size: 1.5rem;">🔴</span>
</div>
<div class="stat-card warn-card">
<div class="stat-info">
<p>Warnings</p>
<h3 id="statWarnCount">0</h3>
</div>
<span style="font-size: 1.5rem;">🟡</span>
</div>
<div class="stat-card info-card">
<div class="stat-info">
<p>Info / Debug</p>
<h3 id="statInfoCount">0</h3>
</div>
<span style="font-size: 1.5rem;">🔵</span>
</div>
</div>
<!-- Filter & Control Toolbar -->
<div class="filter-toolbar">
<!-- Left: File Selector & Level Filter -->
<div class="filter-group">
<label style="margin-right: 0.25rem;">File:</label>
<div class="pill-btn-group" id="logFileSelector">
<button class="pill-btn active" onclick="setLogFile('app.log')">app.log</button>
<button class="pill-btn" onclick="setLogFile('error.log')">error.log</button>
<button class="pill-btn" onclick="setLogFile('requests.log')">requests.log</button>
<button class="pill-btn" onclick="setLogFile('all')">all</button>
</div>
<label style="margin-left: 0.5rem; margin-right: 0.25rem;">Level:</label>
<div class="pill-btn-group" id="logLevelSelector">
<button class="pill-btn active" onclick="setLogLevel('ALL')">ALL</button>
<button class="pill-btn pill-error" onclick="setLogLevel('ERROR')">ERROR</button>
<button class="pill-btn pill-warn" onclick="setLogLevel('WARNING')">WARN</button>
<button class="pill-btn pill-info" onclick="setLogLevel('INFO')">INFO</button>
<button class="pill-btn pill-debug" onclick="setLogLevel('DEBUG')">DEBUG</button>
</div>
</div>
<!-- Middle: Text Search Filter -->
<div class="filter-group">
<div class="search-box-wrap">
<span class="search-icon">🔍</span>
<input type="text" id="logSearchInput" placeholder="Filter logs (e.g. login, 404, OTP)..." onkeyup="handleSearchKeyUp(event)">
</div>
</div>
<!-- Right: Limit, Auto-refresh & Action Buttons -->
<div class="filter-group">
<label>Limit:</label>
<select id="logLimitSelect" onchange="fetchLogs()" style="width: auto; padding: 0.35rem 0.6rem;">
<option value="50">50</option>
<option value="150" selected>150</option>
<option value="300">300</option>
<option value="600">600</option>
<option value="1200">1200</option>
</select>
<label>Auto-Refresh:</label>
<select id="logAutoRefreshSelect" onchange="changeAutoRefresh()" style="width: auto; padding: 0.35rem 0.6rem;">
<option value="0">Off</option>
<option value="2000">2s</option>
<option value="5000" selected>5s</option>
<option value="10000">10s</option>
</select>
<button class="btn btn-primary btn-sm" onclick="fetchLogs()" title="Refresh now">
🔄 Refresh
</button>
<button class="btn btn-secondary btn-sm" onclick="copyLogsToClipboard()" title="Copy to clipboard">
📋 Copy
</button>
<button class="btn btn-secondary btn-sm" onclick="downloadLogs()" title="Download raw log">
📥 Download
</button>
<button class="btn btn-danger btn-sm" onclick="clearCurrentLog()" title="Clear/truncate log file">
🗑️ Clear
</button>
</div>
</div>
<!-- Terminal Log Viewer Console -->
<div class="terminal-container">
<div class="terminal-header">
<div class="terminal-title">
<div class="terminal-dots">
<span class="dot dot-red"></span>
<span class="dot dot-yellow"></span>
<span class="dot dot-green"></span>
</div>
<span id="terminalFileTitle">logs/app.log</span>
<span id="terminalActiveFilterNotice" style="color: #60a5fa; font-size: 0.72rem;"></span>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center; font-size: 0.72rem; color: var(--text-faint);">
<span id="terminalLastUpdated">Updated: Never</span>
<button class="btn btn-secondary btn-sm" style="padding: 2px 6px;" onclick="scrollToBottom()">Scroll to Bottom ⬇</button>
</div>
</div>
<div class="terminal-body" id="terminalBody">
<div class="terminal-empty">
<span>⏳ Loading logs...</span>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================= -->
<!-- TAB 2: BACKEND API TEST SUITE -->
<!-- ================================================================= -->
<div id="tester-tab" class="tab-pane">
<div class="grid-2">
<!-- 1. Authentication & Session Module -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>🔐</span> 1. Authentication &amp; User Accounts
</div>
<span class="card-title-tag">POST /api/auth/*</span>
</div>
<!-- Auth Sub-tabs -->
<div class="pill-btn-group" style="width: 100%; display: flex;">
<button id="tabBtnRegister" class="pill-btn active" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('register')">
📝 Create Account
</button>
<button id="tabBtnLogin" class="pill-btn" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('login')">
🔑 Sign In
</button>
<button id="tabBtnOtp" class="pill-btn" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('otp')">
📱 Verify OTP
</button>
</div>
<!-- Sub-tab 1: Create Account Form -->
<div id="authSubTabRegister">
<p style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.75rem;">
Create a brand new supplier account. Generates JWT tokens and sends mock verification OTPs.
</p>
<div class="form-row">
<div class="form-group">
<label>Username</label>
<input type="text" id="regUsername" value="artisan_seller_01">
</div>
<div class="form-group">
<label>Email Address</label>
<input type="email" id="regEmail" value="artisan01@example.com">
</div>
</div>
<div class="form-row" style="margin-top: 0.5rem;">
<div class="form-group">
<label>Phone Number (10 Digits)</label>
<input type="text" id="regPhone" value="9876543210">
</div>
<div class="form-group">
<label>Password (Min 8 chars)</label>
<input type="password" id="regPassword" value="SecurePassword123!">
</div>
</div>
<div style="display: flex; gap: 0.5rem; margin-top: 0.85rem; flex-wrap: wrap;">
<button class="btn btn-success btn-sm" onclick="runCreateAccount()">
✨ Create Account
</button>
<button class="btn btn-secondary btn-sm" onclick="randomizeRegisterFields()">
🎲 Randomize Fields
</button>
</div>
</div>
<!-- Sub-tab 2: Sign In Form -->
<div id="authSubTabLogin" style="display: none;">
<p style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.75rem;">
Authenticate an existing supplier with username or phone number.
</p>
<div class="form-row">
<div class="form-group">
<label>Username or Phone</label>
<input type="text" id="authUsername" value="demo_seller">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="authPassword" value="super_secure_pass_123">
</div>
</div>
<div style="display: flex; gap: 0.5rem; margin-top: 0.85rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runLogin()">
🔑 Sign In
</button>
<button class="btn btn-secondary btn-sm" onclick="fillDemoSeller()">
⚡ Fill Demo Seller
</button>
<button class="btn btn-danger btn-sm" onclick="runLogout()">
🚪 Logout
</button>
</div>
</div>
<!-- Sub-tab 3: Verify OTP Form -->
<div id="authSubTabOtp" style="display: none;">
<p style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.75rem;">
Validate simulated 2FA verification codes (default mock code: <code>123456</code>).
</p>
<div class="form-row">
<div class="form-group">
<label>OTP Verification Type</label>
<select id="otpType">
<option value="phone">Phone OTP</option>
<option value="email">Email OTP</option>
</select>
</div>
<div class="form-group">
<label>OTP Code</label>
<input type="text" id="otpCode" value="123456">
</div>
</div>
<div style="display: flex; gap: 0.5rem; margin-top: 0.85rem;">
<button class="btn btn-primary btn-sm" onclick="runVerifyOtp()">
✅ Verify OTP Code
</button>
</div>
</div>
<!-- Active Session Info Card -->
<div style="border-top: 1px solid var(--border-color); padding-top: 0.65rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.75rem; color: var(--text-muted);">
<span>Active: <strong id="sessionUserLabel" style="color: var(--text-main);">Anonymous / Dummy</strong></span>
<div style="display: flex; gap: 0.4rem;">
<button class="btn btn-secondary btn-sm" style="padding: 2px 7px; font-size: 0.7rem;" onclick="copyToken()">Copy JWT</button>
<button class="btn btn-secondary btn-sm" style="padding: 2px 7px; font-size: 0.7rem;" onclick="runLogout()">Logout</button>
</div>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="authResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="authStatusCode">200 OK</span>
</div>
<div class="response-body" id="authResponseBody"></div>
</div>
</div>
<!-- 2. Supplier Profile & KYC Module -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>👤</span> 2. Supplier Profile &amp; KYC
</div>
<span class="card-title-tag">/api/profile/*</span>
</div>
<p style="font-size: 0.8rem; color: var(--text-muted);">
Query verified seller profiles, update business coordinates, and submit KYC credentials (GSTIN / PAN).
</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runGetProfile()">
🔍 Get Current Profile
</button>
<button class="btn btn-secondary btn-sm" onclick="runUpdateProfile()">
✏️ Update Profile Details
</button>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<div class="form-row">
<div class="form-group">
<label>15-Digit GSTIN</label>
<input type="text" id="profileGstin" value="29AAAAA1111A1Z1">
</div>
<div class="form-group" style="justify-content: flex-end;">
<button class="btn btn-secondary btn-sm" style="height: 36px;" onclick="runSubmitGstin()">
🏛️ Submit GSTIN
</button>
</div>
</div>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<div class="form-row">
<div class="form-group">
<label>PAN Card Verification</label>
<input type="text" id="profilePan" value="ABCDE1234F">
</div>
<div class="form-group" style="justify-content: flex-end;">
<button class="btn btn-secondary btn-sm" style="height: 36px;" onclick="runVerifyPan()">
🆔 Verify PAN
</button>
</div>
</div>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="profileResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="profileStatusCode">200 OK</span>
</div>
<div class="response-body" id="profileResponseBody"></div>
</div>
</div>
<!-- 3. Product Catalog Management -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>📦</span> 3. Product Catalog
</div>
<span class="card-title-tag">/api/products/*</span>
</div>
<p style="font-size: 0.8rem; color: var(--text-muted);">
Manage products, query inventory by filters, create items, and execute bulk uploads.
</p>
<div class="form-row">
<div class="form-group">
<label>Search Keyword</label>
<input type="text" id="prodSearch" placeholder="Search by title or SKU...">
</div>
<div class="form-group">
<label>Low Stock Filter</label>
<select id="prodLowStock">
<option value="">All Stock Levels</option>
<option value="true">Low Stock (&le; 5)</option>
</select>
</div>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runListProducts()">
📋 List Products
</button>
<button class="btn btn-secondary btn-sm" onclick="runCreateSampleProduct()">
➕ Create Sample Product
</button>
<button class="btn btn-secondary btn-sm" onclick="runBulkUploadDemo()">
📦 Bulk Upload Demo JSON
</button>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="productResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="productStatusCode">200 OK</span>
</div>
<div class="response-body" id="productResponseBody"></div>
</div>
</div>
<!-- 4. Order Management & Fulfillment -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>🚚</span> 4. Order Fulfillment
</div>
<span class="card-title-tag">/api/orders/*</span>
</div>
<p style="font-size: 0.8rem; color: var(--text-muted);">
Track incoming supplier orders and test state transitions (Accept ➡️ Ready to Ship with DHL tracking).
</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runListOrders()">
📋 List Orders
</button>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<div class="form-row">
<div class="form-group">
<label>Order ID (DB ID)</label>
<input type="text" id="targetOrderId" placeholder="e.g. 1">
</div>
<div class="form-group" style="justify-content: flex-end; display: flex; gap: 0.5rem; flex-direction: row; align-items: flex-end;">
<button class="btn btn-success btn-sm" onclick="runAcceptOrder()">
✅ Accept Order
</button>
<button class="btn btn-danger btn-sm" onclick="runRejectOrder()">
❌ Reject Order
</button>
</div>
</div>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="orderResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="orderStatusCode">200 OK</span>
</div>
<div class="response-body" id="orderResponseBody"></div>
</div>
</div>
<!-- 5. Customer Returns -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>🔄</span> 5. Customer Returns
</div>
<span class="card-title-tag">/api/returns/*</span>
</div>
<p style="font-size: 0.8rem; color: var(--text-muted);">
Inspect customer return requests and test resolution (Approve ➡️ In Transit).
</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runListReturns()">
📋 List Return Requests
</button>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<div class="form-row">
<div class="form-group">
<label>Return Request ID (DB ID)</label>
<input type="text" id="targetReturnId" placeholder="e.g. 1">
</div>
<div class="form-group" style="justify-content: flex-end; display: flex; gap: 0.5rem; flex-direction: row; align-items: flex-end;">
<button class="btn btn-success btn-sm" onclick="runReturnAction('Approved')">
👍 Approve Return
</button>
<button class="btn btn-danger btn-sm" onclick="runReturnAction('Rejected')">
👎 Reject Return
</button>
</div>
</div>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="returnResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="returnStatusCode">200 OK</span>
</div>
<div class="response-body" id="returnResponseBody"></div>
</div>
</div>
<!-- 6. Wallet & Payouts -->
<div class="card">
<div class="card-header">
<div class="card-title">
<span>💰</span> 6. Wallet &amp; Payouts
</div>
<span class="card-title-tag">/api/wallet/*</span>
</div>
<p style="font-size: 0.8rem; color: var(--text-muted);">
View ledger balances and test withdrawal operations with generated audit transaction receipts.
</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-primary btn-sm" onclick="runGetWallet()">
💳 Check Wallet Balance
</button>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<div class="form-row">
<div class="form-group">
<label>Withdraw Amount ($)</label>
<input type="number" id="withdrawAmount" value="50.00" step="10">
</div>
<div class="form-group" style="justify-content: flex-end;">
<button class="btn btn-secondary btn-sm" style="height: 36px;" onclick="runWithdraw()">
💸 Request Payout
</button>
</div>
</div>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="walletResponseWrap" style="display: none;">
<div class="response-header">
<span>Response Inspector</span>
<span class="status-code" id="walletStatusCode">200 OK</span>
</div>
<div class="response-body" id="walletResponseBody"></div>
</div>
</div>
</div>
</div>
<!-- ================================================================= -->
<!-- TAB 3: INTERACTIVE API SANDBOX -->
<!-- ================================================================= -->
<div id="sandbox-tab" class="tab-pane">
<div class="card" style="max-width: 1000px; margin: 0 auto;">
<div class="card-header">
<div class="card-title">
<span>⚡</span> Interactive REST API Runner
</div>
<span class="card-title-tag">Custom HTTP Client</span>
</div>
<p style="font-size: 0.85rem; color: var(--text-muted);">
Send arbitrary HTTP requests to any backend endpoint with custom methods, headers, and request body.
</p>
<!-- Method & URL -->
<div style="display: flex; gap: 0.75rem;">
<select id="sandboxMethod" style="width: 120px; font-weight: 700;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="PATCH">PATCH</option>
</select>
<input type="text" id="sandboxUrl" value="/api/profile/" placeholder="/api/endpoint/">
<button class="btn btn-primary" onclick="sendSandboxRequest()" style="min-width: 130px;">
🚀 Send Request
</button>
</div>
<!-- Quick Presets -->
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<span style="font-size: 0.75rem; color: var(--text-muted); font-weight: 600;">Presets:</span>
<button class="btn btn-secondary btn-sm" onclick="loadSandboxPreset('GET', '/api/profile/')">GET /api/profile/</button>
<button class="btn btn-secondary btn-sm" onclick="loadSandboxPreset('GET', '/api/products/')">GET /api/products/</button>
<button class="btn btn-secondary btn-sm" onclick="loadSandboxPreset('GET', '/api/orders/')">GET /api/orders/</button>
<button class="btn btn-secondary btn-sm" onclick="loadSandboxPreset('GET', '/api/wallet/')">GET /api/wallet/</button>
<button class="btn btn-secondary btn-sm" onclick="loadSandboxPreset('GET', '/api/logs/?limit=50')">GET /api/logs/</button>
</div>
<!-- Request Body -->
<div class="form-group">
<label>JSON Request Body (For POST / PUT / PATCH):</label>
<textarea id="sandboxBody" rows="7" placeholder='{"key": "value"}'></textarea>
</div>
<!-- Response Inspector -->
<div class="response-inspector" id="sandboxResponseWrap" style="display: block;">
<div class="response-header">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<span>Response:</span>
<span class="status-code" id="sandboxStatusCode">— Ready —</span>
<span id="sandboxLatency" style="color: var(--text-muted); font-size: 0.7rem;"></span>
</div>
<button class="btn btn-secondary btn-sm" style="padding: 2px 6px;" onclick="copySandboxResponse()">Copy JSON</button>
</div>
<div class="response-body" id="sandboxResponseBody" style="min-height: 180px;">// Click 'Send Request' to inspect backend output.</div>
</div>
</div>
</div>
</main>
<!-- Notification Toast -->
<div id="toast" class="alert-toast" style="display: none;">
<span id="toastIcon">ℹ️</span>
<span id="toastMsg">Notification message</span>
</div>
<script>
// Global State
let activeToken = localStorage.getItem('seller_central_access_token') || '';
let currentLogFile = 'app.log';
let currentLogLevel = 'ALL';
let autoRefreshTimer = null;
// Initialize
window.addEventListener('DOMContentLoaded', () => {
updateAuthUI();
fetchLogs();
setupAutoRefresh();
});
// Toast Helper
function showToast(msg, icon = 'ℹ️') {
const toast = document.getElementById('toast');
document.getElementById('toastMsg').innerText = msg;
document.getElementById('toastIcon').innerText = icon;
toast.style.display = 'flex';
setTimeout(() => {
toast.style.display = 'none';
}, 3500);
}
// Tab Switching
function switchTab(tabId) {
document.querySelectorAll('.tab-pane').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.nav-tab-btn').forEach(el => el.classList.remove('active'));
const targetPane = document.getElementById(tabId);
if (targetPane) targetPane.classList.add('active');
const activeBtn = Array.from(document.querySelectorAll('.nav-tab-btn')).find(b =>
(tabId === 'logs-tab' && b.innerText.includes('Log')) ||
(tabId === 'tester-tab' && b.innerText.includes('Test')) ||
(tabId === 'sandbox-tab' && b.innerText.includes('Sandbox'))
);
if (activeBtn) activeBtn.classList.add('active');
if (tabId === 'logs-tab') {
fetchLogs();
}
}
// Update Header Auth UI
function updateAuthUI() {
const authBadge = document.getElementById('headerAuthUser');
const sessionLabel = document.getElementById('sessionUserLabel');
const savedUser = localStorage.getItem('seller_central_active_user') || '';
if (activeToken) {
authBadge.innerText = savedUser ? `User: ${savedUser}` : 'Token Active (Bearer)';
authBadge.style.color = '#34d399';
if (sessionLabel) {
sessionLabel.innerText = savedUser ? `${savedUser} (Authenticated)` : 'Logged In (Bearer Token)';
sessionLabel.style.color = '#34d399';
}
} else {
authBadge.innerText = 'Dummy / Anonymous';
authBadge.style.color = '#94a3b8';
if (sessionLabel) {
sessionLabel.innerText = 'Anonymous / Dummy';
sessionLabel.style.color = '#94a3b8';
}
}
}
// Base API Request Helper
async function apiRequest(endpoint, method = 'GET', body = null) {
const startTime = performance.now();
const headers = {
'Content-Type': 'application/json',
};
if (activeToken) {
headers['Authorization'] = `Bearer ${activeToken}`;
}
const options = {
method,
headers,
};
if (body && method !== 'GET') {
options.body = typeof body === 'string' ? body : JSON.stringify(body);
}
try {
const res = await fetch(endpoint, options);
const duration = Math.round(performance.now() - startTime);
let data = null;
const text = await res.text();
try {
data = JSON.parse(text);
} catch {
data = text;
}
return {
status: res.status,
statusText: res.statusText,
ok: res.ok,
duration,
data
};
} catch (err) {
return {
status: 0,
statusText: 'Network Error',
ok: false,
duration: 0,
data: { error: err.message }
};
}
}
// Display formatted response in inspector box
function showInspector(wrapId, statusId, bodyId, res) {
const wrap = document.getElementById(wrapId);
const statusEl = document.getElementById(statusId);
const bodyEl = document.getElementById(bodyId);
wrap.style.display = 'block';
statusEl.innerText = `${res.status} ${res.statusText} (${res.duration}ms)`;
statusEl.className = 'status-code ' + (res.ok ? 'status-2xx' : (res.status >= 500 ? 'status-5xx' : 'status-4xx'));
bodyEl.innerText = typeof res.data === 'object' ? JSON.stringify(res.data, null, 2) : String(res.data);
}
// =========================================================================
// LOG VIEWER DASHBOARD LOGIC
// =========================================================================
function setLogFile(filename) {
currentLogFile = filename;
document.querySelectorAll('#logFileSelector .pill-btn').forEach(btn => {
btn.classList.toggle('active', btn.innerText.trim() === filename);
});
document.getElementById('terminalFileTitle').innerText = filename === 'all' ? 'Combined (all logs)' : `logs/${filename}`;
fetchLogs();
}
function setLogLevel(level) {
currentLogLevel = level;
document.querySelectorAll('#logLevelSelector .pill-btn').forEach(btn => {
btn.classList.toggle('active', btn.innerText.trim() === level);
});
fetchLogs();
}
function handleSearchKeyUp(e) {
if (e.key === 'Enter') {
fetchLogs();
}
}
function changeAutoRefresh() {
setupAutoRefresh();
fetchLogs();
}
function setupAutoRefresh() {
if (autoRefreshTimer) {
clearInterval(autoRefreshTimer);
autoRefreshTimer = null;
}
const val = parseInt(document.getElementById('logAutoRefreshSelect').value, 10);
if (val > 0) {
autoRefreshTimer = setInterval(() => {
fetchLogs(true);
}, val);
}
}
async function fetchLogs(isSilent = false) {
const limit = document.getElementById('logLimitSelect').value;
const search = document.getElementById('logSearchInput').value.trim();
const params = new URLSearchParams({
file: currentLogFile,
level: currentLogLevel,
limit: limit,
tail: 'true',
});
if (search) {
params.set('search', search);
}
if (!isSilent) {
document.getElementById('terminalActiveFilterNotice').innerText = 'Loading...';
}
const res = await apiRequest(`/api/logs/?${params.toString()}`);
if (!res.ok) {
document.getElementById('terminalBody').innerHTML = `<div class="terminal-empty" style="color: #f87171;">⚠️ Failed to load logs: ${res.data?.error || res.statusText}</div>`;
return;
}
const data = res.data;
renderLogs(data.logs || []);
// Update counters
document.getElementById('statTotalLines').innerText = data.returned_count || 0;
document.getElementById('statErrorCount').innerText = data.level_counts?.ERROR || 0;
document.getElementById('statWarnCount').innerText = data.level_counts?.WARNING || 0;
document.getElementById('statInfoCount').innerText = (data.level_counts?.INFO || 0) + (data.level_counts?.DEBUG || 0);
document.getElementById('terminalLastUpdated').innerText = `Updated: ${new Date().toLocaleTimeString()}`;
document.getElementById('terminalActiveFilterNotice').innerText = search ? `[Filter: "${search}"]` : '';
}
function renderLogs(logs) {
const body = document.getElementById('terminalBody');
const search = document.getElementById('logSearchInput').value.trim().toLowerCase();
if (!logs || logs.length === 0) {
body.innerHTML = `<div class="terminal-empty"><span>📭 No log entries match the current filters.</span></div>`;
return;
}
const rows = logs.map(entry => {
const lvl = (entry.level || 'INFO').toUpperCase();
let badgeClass = 'badge-info';
let lineClass = '';
if (lvl === 'ERROR' || lvl === 'CRITICAL') {
badgeClass = 'badge-error';
lineClass = 'is-error';
} else if (lvl === 'WARNING' || lvl === 'WARN') {
badgeClass = 'badge-warn';
lineClass = 'is-warning';
} else if (lvl === 'DEBUG') {
badgeClass = 'badge-debug';
}
let rawMsg = escapeHtml(entry.message || entry.raw || '');
if (search) {
const regex = new RegExp(`(${escapeRegExp(search)})`, 'gi');
rawMsg = rawMsg.replace(regex, '<span class="log-highlight">$1</span>');
}
const sourceTag = entry.source && currentLogFile === 'all' ? `<span style="color:#818cf8; font-size:0.7rem;">[${entry.source}]</span> ` : '';
return `
<div class="log-line ${lineClass}">
<span class="log-num">${entry.id || ''}</span>
<span class="log-ts">${entry.timestamp || ''}</span>
<span class="log-badge ${badgeClass}">${lvl}</span>
<span class="log-msg">${sourceTag}${rawMsg}</span>
</div>
`;
}).join('');
body.innerHTML = rows;
}
function scrollToBottom() {
const body = document.getElementById('terminalBody');
body.scrollTop = body.scrollHeight;
}
function copyLogsToClipboard() {
const body = document.getElementById('terminalBody');
const text = body.innerText;
navigator.clipboard.writeText(text);
showToast('Logs copied to clipboard!', '📋');
}
function downloadLogs() {
const search = document.getElementById('logSearchInput').value.trim();
const params = new URLSearchParams({
file: currentLogFile,
level: currentLogLevel,
limit: document.getElementById('logLimitSelect').value,
raw: 'true',
});
if (search) params.set('search', search);
window.open(`/api/logs/?${params.toString()}`, '_blank');
}
async function clearCurrentLog() {
if (!confirm(`Are you sure you want to truncate and clear ${currentLogFile}?`)) return;
const res = await apiRequest('/api/logs/clear/', 'POST', { file: currentLogFile });
if (res.ok) {
showToast(`Cleared ${currentLogFile} successfully!`, '🗑️');
fetchLogs();
} else {
alert('Failed to clear log: ' + (res.data?.error || res.statusText));
}
}
function escapeHtml(str) {
return str.replace(/[&<>'"]/g,
tag => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}[tag] || tag)
);
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// =========================================================================
// AUTHENTICATION & ACCOUNT CREATION LOGIC
// =========================================================================
function switchAuthSubTab(tab) {
document.getElementById('authSubTabRegister').style.display = tab === 'register' ? 'block' : 'none';
document.getElementById('authSubTabLogin').style.display = tab === 'login' ? 'block' : 'none';
document.getElementById('authSubTabOtp').style.display = tab === 'otp' ? 'block' : 'none';
document.getElementById('tabBtnRegister').classList.toggle('active', tab === 'register');
document.getElementById('tabBtnLogin').classList.toggle('active', tab === 'login');
document.getElementById('tabBtnOtp').classList.toggle('active', tab === 'otp');
}
function randomizeRegisterFields() {
const rand = Math.floor(1000 + Math.random() * 9000);
document.getElementById('regUsername').value = `seller_${rand}`;
document.getElementById('regEmail').value = `seller_${rand}@example.com`;
document.getElementById('regPhone').value = `9${Math.floor(100000000 + Math.random() * 900000000)}`;
document.getElementById('regPassword').value = 'SecurePassword123!';
showToast('Generated fresh random credentials!', '🎲');
}
function fillDemoSeller() {
document.getElementById('authUsername').value = 'demo_seller';
document.getElementById('authPassword').value = 'super_secure_pass_123';
showToast('Pre-filled demo seller credentials', '⚡');
}
function copyToken() {
if (!activeToken) {
showToast('No active JWT token found. Please sign in.', '⚠️');
return;
}
navigator.clipboard.writeText(activeToken);
showToast('JWT Access Token copied to clipboard!', '📋');
}
async function runCreateAccount() {
const username = document.getElementById('regUsername').value.trim();
const email = document.getElementById('regEmail').value.trim();
const phone = document.getElementById('regPhone').value.trim();
const password = document.getElementById('regPassword').value;
if (!username || !email || !phone || !password) {
alert('All fields (username, email, phone, password) are required to create an account.');
return;
}
const res = await apiRequest('/api/auth/register/', 'POST', {
username,
email,
phone,
password,
confirm_password: password
});
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
if (res.ok && res.data?.access_token) {
activeToken = res.data.access_token;
localStorage.setItem('seller_central_access_token', activeToken);
localStorage.setItem('seller_central_active_user', username);
document.getElementById('authUsername').value = username;
updateAuthUI();
showToast(`Account created for ${username}! Verification OTPs sent.`, '🎉');
// Automatically switch to OTP tab and pre-fill default test OTP
document.getElementById('otpCode').value = '123456';
switchAuthSubTab('otp');
fetchLogs();
} else {
const errMsg = typeof res.data === 'object' ? Object.entries(res.data).map(([k,v]) => `${k}: ${v}`).join(' ') : (res.data || res.statusText);
showToast(`Registration failed: ${errMsg}`, '❌');
}
}
async function runLogin() {
const u = document.getElementById('authUsername').value.trim();
const p = document.getElementById('authPassword').value;
const res = await apiRequest('/api/auth/login/', 'POST', {
username: u,
password: p
});
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
if (res.ok && res.data?.access_token) {
activeToken = res.data.access_token;
const loggedUser = res.data?.user?.username || u;
localStorage.setItem('seller_central_access_token', activeToken);
localStorage.setItem('seller_central_active_user', loggedUser);
updateAuthUI();
showToast(`Logged in successfully as ${loggedUser}!`, '🔑');
fetchLogs();
}
}
async function runLogout() {
const res = await apiRequest('/api/auth/logout/', 'POST');
activeToken = '';
localStorage.removeItem('seller_central_access_token');
localStorage.removeItem('seller_central_active_user');
updateAuthUI();
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
showToast('Logged out. Cleared local token.', '🚪');
fetchLogs();
}
async function runVerifyOtp() {
const type = document.getElementById('otpType').value;
const otp = document.getElementById('otpCode').value;
const res = await apiRequest('/api/auth/verify-otp/', 'POST', { type, otp });
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
if (res.ok) {
showToast(`${type.toUpperCase()} OTP Verified!`, '✅');
fetchLogs();
}
}
async function runGetProfile() {
const res = await apiRequest('/api/profile/');
showInspector('profileResponseWrap', 'profileStatusCode', 'profileResponseBody', res);
fetchLogs();
}
async function runUpdateProfile() {
const res = await apiRequest('/api/profile/', 'PUT', {
store_name: 'Artisan Decor & Handlooms Studio',
business_bio: 'Authentic handcrafted heritage products curated directly from local master artisans.',
street: '45 Craftsmen Boulevard',
city: 'Bangalore',
state: 'Karnataka',
pincode: '560001',
latitude: 12.9716,
longitude: 77.5946
});
showInspector('profileResponseWrap', 'profileStatusCode', 'profileResponseBody', res);
if (res.ok) showToast('Profile details updated!', '✨');
fetchLogs();
}
async function runSubmitGstin() {
const gstin = document.getElementById('profileGstin').value;
const res = await apiRequest('/api/profile/submit-gstin/', 'POST', { gstin });
showInspector('profileResponseWrap', 'profileStatusCode', 'profileResponseBody', res);
if (res.ok) showToast('GSTIN Verified successfully!', '🏛️');
fetchLogs();
}
async function runVerifyPan() {
const pan = document.getElementById('profilePan').value;
const res = await apiRequest('/api/profile/kyc/verify-pan/', 'POST', { pan_number: pan });
showInspector('profileResponseWrap', 'profileStatusCode', 'profileResponseBody', res);
if (res.ok) showToast('PAN verification completed!', '🆔');
fetchLogs();
}
async function runListProducts() {
const s = document.getElementById('prodSearch').value.trim();
const low = document.getElementById('prodLowStock').value;
const params = new URLSearchParams();
if (s) params.set('search', s);
if (low) params.set('low_stock', low);
const res = await apiRequest(`/api/products/?${params.toString()}`);
showInspector('productResponseWrap', 'productStatusCode', 'productResponseBody', res);
fetchLogs();
}
async function runCreateSampleProduct() {
const rand = Math.floor(100 + Math.random() * 900);
const res = await apiRequest('/api/products/', 'POST', {
title: `Handcrafted Teak Bowl - Special Edition #${rand}`,
category: 'Home Decor',
price: 49.99,
stock: 25,
sku: `TEAK-BWL-${rand}`,
image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100'
});
showInspector('productResponseWrap', 'productStatusCode', 'productResponseBody', res);
if (res.ok) showToast('Product created successfully!', '📦');
fetchLogs();
}
async function runBulkUploadDemo() {
const demoProducts = [
{ title: 'Handmade Brass Lamp', category: 'Lighting', price: 65.00, stock: 12, sku: `BRASS-${Date.now()}-1` },
{ title: 'Pure Cotton Pashmina Shawl', category: 'Apparel', price: 110.00, stock: 8, sku: `PASH-${Date.now()}-2` }
];
const res = await apiRequest('/api/products/bulk-upload/', 'POST', { products: demoProducts });
showInspector('productResponseWrap', 'productStatusCode', 'productResponseBody', res);
if (res.ok) showToast('Bulk upload completed!', '🚀');
fetchLogs();
}
async function runListOrders() {
const res = await apiRequest('/api/orders/');
showInspector('orderResponseWrap', 'orderStatusCode', 'orderResponseBody', res);
if (res.ok && Array.isArray(res.data?.results || res.data) && (res.data?.results || res.data).length > 0) {
const firstOrder = (res.data?.results || res.data)[0];
document.getElementById('targetOrderId').value = firstOrder.id;
}
fetchLogs();
}
async function runAcceptOrder() {
const id = document.getElementById('targetOrderId').value || '1';
const res = await apiRequest(`/api/orders/${id}/accept/`, 'POST');
showInspector('orderResponseWrap', 'orderStatusCode', 'orderResponseBody', res);
if (res.ok) showToast(`Order #${id} accepted & carrier assigned!`, '✅');
fetchLogs();
}
async function runRejectOrder() {
const id = document.getElementById('targetOrderId').value || '1';
const res = await apiRequest(`/api/orders/${id}/reject/`, 'POST');
showInspector('orderResponseWrap', 'orderStatusCode', 'orderResponseBody', res);
if (res.ok) showToast(`Order #${id} rejected!`, '❌');
fetchLogs();
}
async function runListReturns() {
const res = await apiRequest('/api/returns/');
showInspector('returnResponseWrap', 'returnStatusCode', 'returnResponseBody', res);
if (res.ok && Array.isArray(res.data?.results || res.data) && (res.data?.results || res.data).length > 0) {
const firstRet = (res.data?.results || res.data)[0];
document.getElementById('targetReturnId').value = firstRet.id;
}
fetchLogs();
}
async function runReturnAction(action) {
const id = document.getElementById('targetReturnId').value || '1';
const res = await apiRequest(`/api/returns/${id}/action/`, 'POST', { action });
showInspector('returnResponseWrap', 'returnStatusCode', 'returnResponseBody', res);
if (res.ok) showToast(`Return #${id} set to ${action}!`, '🔄');
fetchLogs();
}
async function runGetWallet() {
const res = await apiRequest('/api/wallet/');
showInspector('walletResponseWrap', 'walletStatusCode', 'walletResponseBody', res);
fetchLogs();
}
async function runWithdraw() {
const amount = document.getElementById('withdrawAmount').value;
const res = await apiRequest('/api/wallet/withdraw/', 'POST', { amount });
showInspector('walletResponseWrap', 'walletStatusCode', 'walletResponseBody', res);
if (res.ok) showToast(`Payout request of $${amount} submitted!`, '💸');
fetchLogs();
}
// =========================================================================
// API SANDBOX LOGIC
// =========================================================================
function loadSandboxPreset(method, url) {
document.getElementById('sandboxMethod').value = method;
document.getElementById('sandboxUrl').value = url;
}
async function sendSandboxRequest() {
const method = document.getElementById('sandboxMethod').value;
const url = document.getElementById('sandboxUrl').value.trim();
const bodyRaw = document.getElementById('sandboxBody').value.trim();
let body = null;
if (bodyRaw && method !== 'GET') {
try {
body = JSON.parse(bodyRaw);
} catch {
body = bodyRaw;
}
}
const res = await apiRequest(url, method, body);
const statusEl = document.getElementById('sandboxStatusCode');
const bodyEl = document.getElementById('sandboxResponseBody');
const latencyEl = document.getElementById('sandboxLatency');
statusEl.innerText = `${res.status} ${res.statusText}`;
statusEl.className = 'status-code ' + (res.ok ? 'status-2xx' : (res.status >= 500 ? 'status-5xx' : 'status-4xx'));
latencyEl.innerText = `${res.duration} ms`;
bodyEl.innerText = typeof res.data === 'object' ? JSON.stringify(res.data, null, 2) : String(res.data);
fetchLogs();
}
function copySandboxResponse() {
const bodyEl = document.getElementById('sandboxResponseBody');
navigator.clipboard.writeText(bodyEl.innerText);
showToast('Sandbox response copied!', '📋');
}
</script>
</body>
</html>