feat: add dedicated Create Account form with field inputs and sub-tabs
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 23s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 23s
This commit is contained in:
parent
ea76459d63
commit
4618bb0c6b
1 changed files with 182 additions and 56 deletions
|
|
@ -900,39 +900,92 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<span>🔐</span> 1. Authentication & Sessions
|
<span>🔐</span> 1. Authentication & User Accounts
|
||||||
</div>
|
</div>
|
||||||
<span class="card-title-tag">POST /api/auth/*</span>
|
<span class="card-title-tag">POST /api/auth/*</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="font-size: 0.8rem; color: var(--text-muted);">
|
<!-- Auth Sub-tabs -->
|
||||||
Test supplier onboarding, JWT token issuance, simulated OTP verification, and session management.
|
<div class="pill-btn-group" style="width: 100%; display: flex;">
|
||||||
</p>
|
<button id="tabBtnRegister" class="pill-btn active" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('register')">
|
||||||
|
📝 Create Account
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Username / Email</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; flex-wrap: wrap;">
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="runLogin()">
|
|
||||||
🔑 Authenticate (Login)
|
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary btn-sm" onclick="runRegister()">
|
<button id="tabBtnLogin" class="pill-btn" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('login')">
|
||||||
📝 Register New User
|
🔑 Sign In
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary btn-sm" onclick="runLogout()">
|
<button id="tabBtnOtp" class="pill-btn" style="flex: 1; text-align: center;" onclick="switchAuthSubTab('otp')">
|
||||||
🚪 Logout
|
📱 Verify OTP
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="border-top: 1px solid var(--border-color); padding-top: 0.75rem; margin-top: 0.25rem;">
|
<!-- 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-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>OTP Verification Type</label>
|
<label>OTP Verification Type</label>
|
||||||
|
|
@ -942,13 +995,24 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>OTP Code (Default: 123456)</label>
|
<label>OTP Code</label>
|
||||||
<input type="text" id="otpCode" value="123456">
|
<input type="text" id="otpCode" value="123456">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-secondary btn-sm" style="margin-top: 0.5rem;" onclick="runVerifyOtp()">
|
<div style="display: flex; gap: 0.5rem; margin-top: 0.85rem;">
|
||||||
✅ Verify OTP Code
|
<button class="btn btn-primary btn-sm" onclick="runVerifyOtp()">
|
||||||
</button>
|
✅ 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>
|
</div>
|
||||||
|
|
||||||
<!-- Response Inspector -->
|
<!-- Response Inspector -->
|
||||||
|
|
@ -1332,12 +1396,23 @@
|
||||||
// Update Header Auth UI
|
// Update Header Auth UI
|
||||||
function updateAuthUI() {
|
function updateAuthUI() {
|
||||||
const authBadge = document.getElementById('headerAuthUser');
|
const authBadge = document.getElementById('headerAuthUser');
|
||||||
|
const sessionLabel = document.getElementById('sessionUserLabel');
|
||||||
|
const savedUser = localStorage.getItem('seller_central_active_user') || '';
|
||||||
|
|
||||||
if (activeToken) {
|
if (activeToken) {
|
||||||
authBadge.innerText = 'Token Active (Bearer)';
|
authBadge.innerText = savedUser ? `User: ${savedUser}` : 'Token Active (Bearer)';
|
||||||
authBadge.style.color = '#34d399';
|
authBadge.style.color = '#34d399';
|
||||||
|
if (sessionLabel) {
|
||||||
|
sessionLabel.innerText = savedUser ? `${savedUser} (Authenticated)` : 'Logged In (Bearer Token)';
|
||||||
|
sessionLabel.style.color = '#34d399';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
authBadge.innerText = 'Dummy / Anonymous';
|
authBadge.innerText = 'Dummy / Anonymous';
|
||||||
authBadge.style.color = '#94a3b8';
|
authBadge.style.color = '#94a3b8';
|
||||||
|
if (sessionLabel) {
|
||||||
|
sessionLabel.innerText = 'Anonymous / Dummy';
|
||||||
|
sessionLabel.style.color = '#94a3b8';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1582,11 +1657,83 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// API TEST SUITE FUNCTIONS
|
// 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() {
|
async function runLogin() {
|
||||||
const u = document.getElementById('authUsername').value;
|
const u = document.getElementById('authUsername').value.trim();
|
||||||
const p = document.getElementById('authPassword').value;
|
const p = document.getElementById('authPassword').value;
|
||||||
|
|
||||||
const res = await apiRequest('/api/auth/login/', 'POST', {
|
const res = await apiRequest('/api/auth/login/', 'POST', {
|
||||||
|
|
@ -1597,33 +1744,11 @@
|
||||||
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
|
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
|
||||||
if (res.ok && res.data?.access_token) {
|
if (res.ok && res.data?.access_token) {
|
||||||
activeToken = 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_access_token', activeToken);
|
||||||
|
localStorage.setItem('seller_central_active_user', loggedUser);
|
||||||
updateAuthUI();
|
updateAuthUI();
|
||||||
showToast('Logged in successfully! Token stored.', '🔑');
|
showToast(`Logged in successfully as ${loggedUser}!`, '🔑');
|
||||||
fetchLogs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runRegister() {
|
|
||||||
const rand = Math.floor(1000 + Math.random() * 9000);
|
|
||||||
const username = `seller_${rand}`;
|
|
||||||
const email = `seller_${rand}@example.com`;
|
|
||||||
const phone = `9${Math.floor(100000000 + Math.random() * 900000000)}`;
|
|
||||||
|
|
||||||
const res = await apiRequest('/api/auth/register/', 'POST', {
|
|
||||||
username,
|
|
||||||
email,
|
|
||||||
phone,
|
|
||||||
password: 'Password123!'
|
|
||||||
});
|
|
||||||
|
|
||||||
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
|
|
||||||
if (res.ok && res.data?.access_token) {
|
|
||||||
activeToken = res.data.access_token;
|
|
||||||
localStorage.setItem('seller_central_access_token', activeToken);
|
|
||||||
document.getElementById('authUsername').value = username;
|
|
||||||
updateAuthUI();
|
|
||||||
showToast(`Created ${username}! Access token saved.`, '🎉');
|
|
||||||
fetchLogs();
|
fetchLogs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1632,6 +1757,7 @@
|
||||||
const res = await apiRequest('/api/auth/logout/', 'POST');
|
const res = await apiRequest('/api/auth/logout/', 'POST');
|
||||||
activeToken = '';
|
activeToken = '';
|
||||||
localStorage.removeItem('seller_central_access_token');
|
localStorage.removeItem('seller_central_access_token');
|
||||||
|
localStorage.removeItem('seller_central_active_user');
|
||||||
updateAuthUI();
|
updateAuthUI();
|
||||||
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
|
showInspector('authResponseWrap', 'authStatusCode', 'authResponseBody', res);
|
||||||
showToast('Logged out. Cleared local token.', '🚪');
|
showToast('Logged out. Cleared local token.', '🚪');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue