| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Ufone Dashboard Replica</title> |
| <style> |
| :root { |
| --primary: #FF7F00; |
| --secondary: #2C8A4B; |
| --bg: #f4f4f4; |
| --text: #333; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background-color: var(--bg); |
| margin: 0; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| min-height: 100vh; |
| } |
| |
| .container { |
| width: 100%; |
| max-width: 400px; |
| background: white; |
| border-radius: 12px; |
| box-shadow: 0 4px 20px rgba(0,0,0,0.1); |
| overflow: hidden; |
| position: relative; |
| } |
| |
| |
| .screen { |
| display: none; |
| padding: 30px; |
| flex-direction: column; |
| gap: 15px; |
| } |
| |
| .screen.active { |
| display: flex; |
| } |
| |
| h2 { margin-top: 0; color: var(--secondary); } |
| |
| input { |
| padding: 12px; |
| border: 1px solid #ddd; |
| border-radius: 8px; |
| font-size: 16px; |
| } |
| |
| button { |
| padding: 12px; |
| background-color: var(--secondary); |
| color: white; |
| border: none; |
| border-radius: 8px; |
| font-size: 16px; |
| cursor: pointer; |
| transition: background 0.3s; |
| } |
| |
| button:hover { background-color: #1e6636; } |
| |
| |
| .user-info { |
| background: var(--secondary); |
| color: white; |
| padding: 20px; |
| margin: -30px -30px 20px -30px; |
| text-align: center; |
| } |
| |
| .usage-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 10px; |
| } |
| |
| .card { |
| background: #fff; |
| border: 1px solid #eee; |
| border-radius: 8px; |
| padding: 15px; |
| text-align: center; |
| box-shadow: 0 2px 5px rgba(0,0,0,0.05); |
| } |
| |
| .card h3 { margin: 5px 0; font-size: 14px; color: #666; } |
| .card .value { font-size: 18px; font-weight: bold; color: var(--primary); } |
| |
| .rewards-list { |
| margin-top: 20px; |
| max-height: 200px; |
| overflow-y: auto; |
| } |
| |
| .reward-item { |
| display: flex; |
| justify-content: space-between; |
| padding: 10px; |
| border-bottom: 1px solid #eee; |
| font-size: 14px; |
| } |
| |
| |
| .loader { |
| display: none; |
| position: absolute; |
| top: 0; left: 0; right: 0; bottom: 0; |
| background: rgba(255,255,255,0.8); |
| justify-content: center; |
| align-items: center; |
| } |
| .loader.active { display: flex; } |
| .spinner { |
| width: 40px; height: 40px; |
| border: 4px solid #f3f3f3; |
| border-top: 4px solid var(--primary); |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } |
| |
| .error-msg { |
| color: red; |
| font-size: 12px; |
| text-align: center; |
| display: none; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="container"> |
| <div class="loader" id="loader"><div class="spinner"></div></div> |
|
|
| |
| <div id="login-screen" class="screen active"> |
| <h2>Ufone Login</h2> |
| <p>Enter your number to generate OTP.</p> |
| <input type="text" id="phone-input" placeholder="033xxxxxxx" value="03356757673"> |
| <button onclick="generateOTP()">Generate OTP</button> |
| <div class="error-msg" id="login-error"></div> |
| </div> |
|
|
| |
| <div id="verify-screen" class="screen"> |
| <h2>Verify OTP</h2> |
| <p>Enter the code sent to your phone.</p> |
| <input type="text" id="otp-input" placeholder="Enter OTP (e.g. 123456)"> |
| <button onclick="verifyOTP()">Verify & Login</button> |
| <button onclick="showScreen('login-screen')" style="background:#888">Back</button> |
| <div class="error-msg" id="verify-error"></div> |
| </div> |
|
|
| |
| <div id="dashboard-screen" class="screen"> |
| <div class="user-info"> |
| <h3 id="user-name">User Name</h3> |
| <span id="user-number">033xxxxxxx</span><br> |
| <small id="user-pkg">Package Name</small> |
| </div> |
|
|
| <h4>Current Usage</h4> |
| <div class="usage-grid" id="usage-container"> |
| |
| </div> |
|
|
| <h4>Spin Rewards Available</h4> |
| <div class="rewards-list" id="rewards-container"> |
| |
| </div> |
| |
| <button onclick="location.reload()" style="margin-top:20px; background:#888">Logout</button> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const BASE_URL = "https://ufon.fak-official.workers.dev/api"; |
| |
| const DEVICE_ID = "jswjc821k3bdngx05t8"; |
| |
| |
| let state = { |
| phoneNumber: "", |
| token: null, |
| subToken: null |
| }; |
| |
| |
| |
| function showLoader(show) { |
| document.getElementById('loader').classList.toggle('active', show); |
| } |
| |
| function showScreen(screenId) { |
| document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); |
| document.getElementById(screenId).classList.add('active'); |
| } |
| |
| function showError(elementId, msg) { |
| const el = document.getElementById(elementId); |
| el.style.display = 'block'; |
| el.innerText = msg; |
| } |
| |
| |
| |
| |
| function getHeaders(bodyStr) { |
| const timestamp = Date.now().toString(); |
| |
| |
| |
| |
| const dummySignature = "4c1ab0dec70001a1eaec3d0c0d3124796769e848940acba6ef9ba8c2a2702e78"; |
| |
| const headers = { |
| "Content-Type": "application/json", |
| "Accept": "application/json, text/plain, */*", |
| "X-Timestamp": timestamp, |
| "X-Signature": dummySignature, |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/143.0.0.0 Safari/537.36" |
| }; |
| |
| if (state.token) { |
| headers["Authorization"] = `Bearer ${state.token}`; |
| } |
| |
| return headers; |
| } |
| |
| |
| |
| async function generateOTP() { |
| const phone = document.getElementById('phone-input').value; |
| if (!phone) return showError('login-error', 'Enter phone number'); |
| |
| state.phoneNumber = phone; |
| showLoader(true); |
| |
| const payload = JSON.stringify({ |
| phoneNumber: state.phoneNumber, |
| deviceId: DEVICE_ID |
| }); |
| |
| try { |
| const res = await fetch(`${BASE_URL}/generate-otp`, { |
| method: "POST", |
| headers: getHeaders(payload), |
| body: payload |
| }); |
| |
| |
| const data = await res.json(); |
| |
| if (res.ok) { |
| showScreen('verify-screen'); |
| } else { |
| showError('login-error', data.responseDesc || "Failed to generate OTP"); |
| } |
| } catch (e) { |
| console.error(e); |
| showError('login-error', "Network Error (Check Console for CORS)"); |
| } finally { |
| showLoader(false); |
| } |
| } |
| |
| async function verifyOTP() { |
| const otp = document.getElementById('otp-input').value; |
| if (!otp) return showError('verify-error', 'Enter OTP'); |
| |
| showLoader(true); |
| |
| const payload = JSON.stringify({ |
| phoneNumber: state.phoneNumber, |
| deviceId: DEVICE_ID, |
| otp: otp |
| }); |
| |
| try { |
| const res = await fetch(`${BASE_URL}/verify-otp`, { |
| method: "POST", |
| headers: getHeaders(payload), |
| body: payload |
| }); |
| |
| const data = await res.json(); |
| |
| if (data.success) { |
| |
| state.token = data.token; |
| state.subToken = data.subToken; |
| |
| |
| await getAllData(); |
| } else { |
| showError('verify-error', "Invalid OTP"); |
| } |
| } catch (e) { |
| console.error(e); |
| showError('verify-error', "Verification Failed"); |
| } finally { |
| showLoader(false); |
| } |
| } |
| |
| async function getAllData() { |
| const payload = JSON.stringify({ |
| phoneNumber: state.phoneNumber, |
| deviceId: DEVICE_ID, |
| token: state.token, |
| subToken: state.subToken |
| }); |
| |
| try { |
| const res = await fetch(`${BASE_URL}/get-all-data`, { |
| method: "POST", |
| headers: getHeaders(payload), |
| body: payload |
| }); |
| |
| const responseJson = await res.json(); |
| |
| if (responseJson.success) { |
| renderDashboard(responseJson.data); |
| showScreen('dashboard-screen'); |
| } else { |
| alert("Failed to load dashboard data"); |
| } |
| |
| } catch (e) { |
| console.error(e); |
| } |
| } |
| |
| |
| |
| function renderDashboard(data) { |
| const dash = data.dashboard; |
| const rewards = data.spinRewards.model; |
| |
| |
| document.getElementById('user-name').innerText = data.customerDetails?.name || "Customer"; |
| document.getElementById('user-number').innerText = state.phoneNumber; |
| document.getElementById('user-pkg').innerText = dash.packageName; |
| |
| |
| const usageContainer = document.getElementById('usage-container'); |
| usageContainer.innerHTML = ''; |
| |
| dash.cumulativeUsage.forEach(usage => { |
| const div = document.createElement('div'); |
| div.className = 'card'; |
| div.innerHTML = ` |
| <h3>${usage.title}</h3> |
| <div class="value">${usage.remaining} ${usage.remainingUnit}</div> |
| <small>Total: ${usage.total}</small> |
| `; |
| usageContainer.appendChild(div); |
| }); |
| |
| |
| const rewardContainer = document.getElementById('rewards-container'); |
| rewardContainer.innerHTML = ''; |
| |
| rewards.forEach(reward => { |
| const div = document.createElement('div'); |
| div.className = 'reward-item'; |
| div.innerHTML = ` |
| <span>${reward.value}</span> |
| <span style="color:green; font-weight:bold">Free</span> |
| `; |
| rewardContainer.appendChild(div); |
| }); |
| } |
| |
| </script> |
|
|
| </body> |
| </html> |