| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>WhatsApp Order Tester</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0b141a; height: 100vh; display: flex; justify-content: center; align-items: center; } |
| .phone { width: 420px; height: 92vh; background: #0b141a; border-radius: 24px; display: flex; flex-direction: column; overflow: hidden; border: 2px solid #233138; } |
| .header { background: #1f2c34; padding: 12px 16px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #233138; } |
| .header .avatar { width: 40px; height: 40px; border-radius: 50%; background: #00a884; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; font-size: 16px; flex-shrink: 0; } |
| .header .info { flex: 1; } |
| .header .info .name { color: #e9edef; font-size: 16px; font-weight: 600; } |
| .header .info .status { color: #8696a0; font-size: 12px; } |
| .header .phone-num { color: #8696a0; font-size: 11px; } |
| .chat { flex: 1; overflow-y: auto; padding: 12px 16px; background: #0b141a url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23111b21' fill-opacity='0.4'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); display: flex; flex-direction: column; gap: 4px; } |
| .msg { max-width: 85%; padding: 6px 10px 4px; border-radius: 8px; font-size: 14px; line-height: 1.4; word-wrap: break-word; white-space: pre-wrap; position: relative; } |
| .msg .time { display: block; text-align: right; font-size: 10px; color: #8696a0; margin-top: 2px; } |
| .msg.sent { align-self: flex-end; background: #005c4b; color: #e9edef; border-top-right-radius: 2px; } |
| .msg.recv { align-self: flex-start; background: #1f2c34; color: #e9edef; border-top-left-radius: 2px; } |
| .msg.system { align-self: center; background: #182229; color: #8696a0; font-size: 12px; padding: 4px 12px; border-radius: 6px; } |
| .input-bar { background: #1f2c34; padding: 8px 12px; display: flex; gap: 8px; align-items: flex-end; border-top: 1px solid #233138; } |
| .input-bar input { flex: 1; background: #2a3942; border: none; border-radius: 8px; padding: 10px 14px; color: #e9edef; font-size: 14px; outline: none; } |
| .input-bar input::placeholder { color: #8696a0; } |
| .input-bar button { background: #00a884; border: none; border-radius: 50%; width: 42px; height: 42px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } |
| .input-bar button:hover { background: #06cf9c; } |
| .input-bar button:disabled { background: #2a3942; cursor: not-allowed; } |
| .input-bar button svg { fill: #0b141a; } |
| .quick-btns { display: flex; gap: 6px; padding: 6px 12px; background: #1f2c34; flex-wrap: wrap; } |
| .quick-btns button { background: #2a3942; border: 1px solid #3b4d56; color: #e9edef; padding: 5px 12px; border-radius: 16px; font-size: 12px; cursor: pointer; } |
| .quick-btns button:hover { background: #3b4d56; } |
| .typing { align-self: flex-start; background: #1f2c34; color: #8696a0; padding: 8px 14px; border-radius: 8px; font-size: 13px; font-style: italic; } |
| </style> |
| </head> |
| <body> |
| <div class="phone"> |
| <div class="header"> |
| <div class="avatar">R</div> |
| <div class="info"> |
| <div class="name">My Restaurant</div> |
| <div class="status" id="conn-status">Connecting...</div> |
| </div> |
| <div class="phone-num">+91 98765 43210</div> |
| </div> |
| <div class="quick-btns"> |
| <button onclick="send('hi')">hi</button> |
| <button onclick="send('menu')">menu</button> |
| <button onclick="send('order')">order</button> |
| <button onclick="send('done')">done</button> |
| <button onclick="send('yes')">yes</button> |
| <button onclick="send('status')">status</button> |
| <button onclick="send('cancel')">cancel</button> |
| </div> |
| <div class="chat" id="chat"></div> |
| <div class="input-bar"> |
| <input type="text" id="msgInput" placeholder="Type a message..." onkeydown="if(event.key==='Enter')sendMsg()" autofocus /> |
| <button onclick="sendMsg()" id="sendBtn"> |
| <svg width="20" height="20" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> |
| </button> |
| </div> |
| </div> |
|
|
| <script> |
| const PHONE = '919876543210'; |
| const WEBHOOK = '/api/v1/webhooks/whatsapp'; |
| const MOCK_API = '/mock-wa'; |
| const chat = document.getElementById('chat'); |
| const input = document.getElementById('msgInput'); |
| const status = document.getElementById('conn-status'); |
| let lastSeq = 0; |
| let polling = null; |
| |
| function addMsg(text, type, time) { |
| const div = document.createElement('div'); |
| div.className = 'msg ' + type; |
| const t = time || new Date().toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' }); |
| |
| let html = text |
| .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') |
| .replace(/\*([^*]+)\*/g, '<strong>$1</strong>'); |
| div.innerHTML = html + '<span class="time">' + t + '</span>'; |
| chat.appendChild(div); |
| chat.scrollTop = chat.scrollHeight; |
| } |
| |
| function addSystem(text) { |
| const div = document.createElement('div'); |
| div.className = 'msg system'; |
| div.textContent = text; |
| chat.appendChild(div); |
| chat.scrollTop = chat.scrollHeight; |
| } |
| |
| async function send(text) { |
| if (!text.trim()) return; |
| addMsg(text, 'sent'); |
| input.value = ''; |
| |
| const payload = { |
| entry: [{ |
| changes: [{ |
| value: { |
| messages: [{ |
| from: PHONE, |
| type: 'text', |
| text: { body: text } |
| }] |
| } |
| }] |
| }] |
| }; |
| |
| try { |
| const res = await fetch(WEBHOOK, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload) |
| }); |
| if (!res.ok) addSystem('Webhook error: ' + res.status); |
| } catch (e) { |
| addSystem('Failed to send: ' + e.message); |
| } |
| } |
| |
| function sendMsg() { |
| const text = input.value.trim(); |
| if (text) send(text); |
| } |
| |
| async function pollReplies() { |
| try { |
| const res = await fetch(MOCK_API + '/messages?since=' + lastSeq); |
| if (!res.ok) throw new Error(res.status); |
| const msgs = await res.json(); |
| for (const m of msgs) { |
| if (m.to === PHONE && m.text) { |
| addMsg(m.text, 'recv', m.time); |
| } |
| lastSeq = m.seq; |
| } |
| status.textContent = 'Online'; |
| status.style.color = '#00a884'; |
| } catch (e) { |
| status.textContent = 'Mock server offline'; |
| status.style.color = '#ea4335'; |
| } |
| } |
| |
| |
| polling = setInterval(pollReplies, 500); |
| pollReplies(); |
| |
| addSystem('WhatsApp Order Tester'); |
| addSystem('Send "hi" or "menu" to start'); |
| </script> |
| </body> |
| </html> |
|
|