| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Client Frontend - CORS Lab</title> |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --bg: #020617; |
| --bg-gradient: radial-gradient(circle at top right, #1e1b4b 0%, #020617 100%); |
| --glass: rgba(255, 255, 255, 0.03); |
| --glass-border: rgba(255, 255, 255, 0.1); |
| --primary: #3b82f6; |
| --primary-hover: #2563eb; |
| --accent: #8b5cf6; |
| --text: #f8fafc; |
| --text-muted: #94a3b8; |
| --success: #10b981; |
| --danger: #f43f5e; |
| --warning: #f59e0b; |
| } |
| |
| * { box-sizing: border-box; } |
| |
| body { |
| margin: 0; |
| padding: 0; |
| font-family: 'Outfit', sans-serif; |
| background: var(--bg-gradient); |
| color: var(--text); |
| min-height: 100vh; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .container { |
| width: 100%; |
| max-width: 1100px; |
| padding: 2rem; |
| } |
| |
| .header-text { |
| text-align: center; |
| margin-bottom: 2rem; |
| } |
| |
| h1 { |
| font-size: 2.5rem; |
| margin: 0 0 0.5rem 0; |
| background: linear-gradient(to right, #60a5fa, #a78bfa); |
| -webkit-background-clip: text; |
| color: transparent; |
| } |
| |
| .grid { |
| display: grid; |
| grid-template-columns: 1fr 1.5fr; |
| gap: 2rem; |
| } |
| |
| @media (max-width: 768px) { |
| .grid { grid-template-columns: 1fr; } |
| } |
| |
| .card { |
| background: var(--glass); |
| backdrop-filter: blur(12px); |
| border: 1px solid var(--glass-border); |
| border-radius: 16px; |
| padding: 2rem; |
| box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5); |
| } |
| |
| h3 { margin-top: 0; margin-bottom: 1.5rem; font-weight: 600; display: flex; align-items: center; gap: 10px; } |
| |
| .badge { |
| font-family: 'JetBrains Mono'; |
| font-size: 0.75rem; |
| padding: 0.2rem 0.5rem; |
| background: rgba(255,255,255,0.1); |
| border-radius: 4px; |
| } |
| |
| label { |
| display: block; |
| margin-bottom: 0.5rem; |
| font-size: 0.9rem; |
| color: var(--text-muted); |
| } |
| |
| input { |
| width: 100%; |
| background: rgba(0,0,0,0.4); |
| border: 1px solid rgba(255,255,255,0.2); |
| color: #fff; |
| padding: 0.9rem; |
| border-radius: 8px; |
| margin-bottom: 1.5rem; |
| font-family: 'JetBrains Mono', monospace; |
| outline: none; |
| transition: 0.3s ease; |
| } |
| |
| input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.3); } |
| |
| .btn-group { |
| display: flex; |
| flex-direction: column; |
| gap: 1rem; |
| } |
| |
| button { |
| width: 100%; |
| background: var(--primary); |
| color: #white; |
| border: none; |
| padding: 1rem; |
| border-radius: 8px; |
| font-family: 'Outfit', sans-serif; |
| font-size: 1rem; |
| font-weight: 600; |
| color: white; |
| cursor: pointer; |
| transition: 0.2s ease; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| button:hover { |
| background: var(--primary-hover); |
| transform: translateY(-2px); |
| } |
| |
| button.secondary { |
| background: rgba(255,255,255,0.05); |
| border: 1px solid rgba(255,255,255,0.1); |
| } |
| button.secondary:hover { background: rgba(255,255,255,0.1); } |
| |
| .terminal { |
| background: #000; |
| border-radius: 12px; |
| border: 1px solid #333; |
| padding: 1rem; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 0.85rem; |
| width: 100%; |
| overflow-x: auto; |
| min-height: 150px; |
| color: var(--text-muted); |
| } |
| |
| .success { color: var(--success); } |
| .error { color: var(--danger); text-shadow: 0 0 10px rgba(244, 63, 94, 0.5); } |
| .warning { color: var(--warning); } |
| .info { color: #60a5fa; } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="container"> |
| <div class="header-text"> |
| <h1>Frontend Client App</h1> |
| <p style="color: var(--text-muted);">Making cross-origin requests to your API backend.</p> |
| </div> |
|
|
| <div class="grid"> |
| |
| <div class="card"> |
| <h3>βοΈ Configuration</h3> |
| |
| <label>API Endpoint (Backend URL)</label> |
| <input id="url" value="http://localhost:5000"> |
|
|
| <label>x-api-key</label> |
| <input id="key" type="password" placeholder="Paste API Key here..."> |
|
|
| <hr style="border: 1px solid var(--glass-border); margin: 2rem 0;"> |
|
|
| <h3>π€ Payload Data</h3> |
| <label>Name</label> |
| <input id="name" placeholder="John Doe"> |
|
|
| <label>Email</label> |
| <input id="email" placeholder="john@example.com"> |
| |
| <div class="btn-group"> |
| <button onclick="send()">π POST /api/add-user</button> |
| <button class="secondary" onclick="getUsers()">π₯ GET /api/users</button> |
| </div> |
| </div> |
|
|
| |
| <div class="card" style="display: flex; flex-direction: column;"> |
| <h3>π» Network Inspector <span class="badge" id="originBadge"></span></h3> |
| |
| <label>Request Payload (Sent to server):</label> |
| <div class="terminal" id="reqBox" style="min-height: 80px; margin-bottom: 1.5rem;">{}</div> |
|
|
| <label>Response / Errors:</label> |
| <div class="terminal" id="resBox" style="flex-grow: 1;">Waiting for request...</div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.getElementById('originBadge').innerText = "Origin: " + window.location.origin; |
| |
| function logReq(data) { |
| document.getElementById('reqBox').innerText = JSON.stringify(data, null, 2); |
| } |
| |
| function logRes(msg, type="info") { |
| const box = document.getElementById('resBox'); |
| box.className = 'terminal ' + type; |
| box.innerHTML = msg; |
| } |
| |
| function buildHeaders() { |
| return { |
| "Content-Type": "application/json", |
| "x-api-key": document.getElementById('key').value |
| }; |
| } |
| |
| async function send() { |
| const url = document.getElementById('url').value; |
| const payload = { |
| name: document.getElementById('name').value, |
| email: document.getElementById('email').value |
| }; |
| |
| logReq(payload); |
| logRes("Sending POST request...", "warning"); |
| |
| try { |
| const res = await fetch(`${url}/api/add-user`, { |
| method: "POST", |
| headers: buildHeaders(), |
| body: JSON.stringify(payload) |
| }); |
| |
| const text = await res.text(); |
| if (!res.ok) throw new Error(`HTTP ${res.status}: ${text}`); |
| |
| logRes("β
Success:\n" + text, "success"); |
| } catch (err) { |
| handleFetchError(err); |
| } |
| } |
| |
| async function getUsers() { |
| const url = document.getElementById('url').value; |
| logReq({ "action": "GET request (no body)" }); |
| logRes("Sending GET request...", "warning"); |
| |
| try { |
| const res = await fetch(`${url}/api/users`, { |
| method: "GET", |
| headers: buildHeaders() |
| }); |
| |
| const text = await res.text(); |
| if (!res.ok) throw new Error(`HTTP ${res.status}: ${text}`); |
| |
| logRes("π₯ Users:\n" + text, "success"); |
| } catch (err) { |
| handleFetchError(err); |
| } |
| } |
| |
| function handleFetchError(err) { |
| |
| if (err.name === 'TypeError' || err.message.includes('Failed to fetch')) { |
| logRes(`π¨ <b>CORS / Network Error!</b>\n\nThe browser blocked the request or the server is down.\n\nCheck the browser console (F12) for exact CORS details.\n\nIf it's a CORS issue, ensure <b>${window.location.origin}</b> is added to the Allowed Origins on the API Dashboard!`, 'error'); |
| } else { |
| logRes(`β <b>Error:</b>\n` + err.message, 'error'); |
| } |
| } |
| </script> |
|
|
| </body> |
| </html> |