Files
static/public/support.html
2025-10-18 12:00:42 -03:00

304 lines
11 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ODOO4projects - Support Chat</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--odoo-purple: #875A7B;
--odoo-dark: #5a3d52;
--bg: #f6f7fb;
--card: #ffffff;
--muted: #7b7f87;
--accent: #6bbf59;
--radius: 14px;
font-family: 'Rubik', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
}
*{box-sizing:border-box}
body{margin:0;background:linear-gradient(180deg,var(--bg),#eef2f8);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:28px}
.app{width:960px;max-width:96vw;background:transparent;display:grid;grid-template-columns:320px 1fr;gap:20px}
/* Left column - user list / details */
.sidebar{background:linear-gradient(180deg,rgba(255,255,255,0.9),var(--card));border-radius:var(--radius);padding:20px;box-shadow:0 6px 20px rgba(24,39,75,0.06);overflow:hidden}
.brand{display:flex;gap:12px;align-items:center}
.logo {
width: 44px;
height: 44px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6px 18px rgba(135,90,123,0.12);
overflow: hidden; /* ensures image respects border radius */
}
.logo img {
width: 100%;
height: 100%;
object-fit: cover; /* ensures the image fills the container */
}
.brand h1{margin:0;font-size:18px;color:var(--odoo-dark)}
.brand p{margin:0;font-size:12px;color:var(--muted)}
.sidebar .meta{margin-top:18px;font-size:13px;color:var(--muted)}
.sidebar .meta strong{color:var(--odoo-dark)}
.sidebar .hint{margin-top:14px;padding:10px;border-radius:10px;background:#fbf8ff;color:#5b4160;font-size:13px}
/* Chat area */
.chat-card{background:var(--card);border-radius:var(--radius);display:flex;flex-direction:column;height:70vh;box-shadow:0 10px 30px rgba(24,39,75,0.06);overflow:hidden}
.chat-header{display:flex;align-items:center;gap:12px;padding:18px;border-bottom:1px solid #eef1f6}
.chat-header .title{font-weight:600;color:var(--odoo-dark)}
.chat-header .sub{font-size:13px;color:var(--muted)}
.messages{flex:1;padding:22px;overflow:auto;background:linear-gradient(180deg,#fbfcff, #f7f9fc)}
.messages-inner{max-width:760px;margin:0 auto;display:flex;flex-direction:column;gap:12px}
.msg{display:flex;gap:10px;align-items:flex-end}
.msg.me{justify-content:flex-end}
.bubble{max-width:68%;padding:12px 14px;border-radius:12px;background:white;border:1px solid #eef1f6;font-size:14px;color:#222}
.bubble.me{background:linear-gradient(90deg,var(--odoo-purple),#9b6fa0);color:white;border:none}
.meta-time{font-size:11px;color:var(--muted);margin-top:6px}
.avatar{width:36px;height:36px;border-radius:10px;background:#eef1f6;display:flex;align-items:center;justify-content:center;color:var(--odoo-dark);font-weight:600}
.composer{padding:14px;border-top:1px solid #eef1f6;display:flex;gap:12px;align-items:center}
.input{flex:1;background:#f2f6fb;border-radius:12px;padding:8px 12px;display:flex;gap:8px;align-items:center}
.input input{border:0;background:transparent;outline:none;padding:10px;font-size:14px;width:100%}
.send{background:var(--odoo-purple);color:white;border:none;padding:10px 14px;border-radius:10px;font-weight:600;cursor:pointer;box-shadow:0 8px 18px rgba(135,90,123,0.14)}
.send[disabled]{opacity:0.5;cursor:default}
.small{font-size:12px;color:var(--muted)}
/* typing indicator */
.typing{display:flex;gap:6px;align-items:center;padding:6px 10px;border-radius:10px;background:#fff6fb;color:#6b3a55;width:max-content}
.dot{width:7px;height:7px;border-radius:50%;background:#d6a0c8;animation:blink 1.2s linear infinite}
.dot:nth-child(2){animation-delay:0.2s}
.dot:nth-child(3){animation-delay:0.4s}
@keyframes blink{0%{opacity:0.2}50%{opacity:1}100%{opacity:0.2}}
/* small screens */
@media (max-width:760px){
.app{grid-template-columns:1fr;}
.sidebar{display:none}
.chat-card{height:82vh}
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="brand">
<div class="logo">
<img src="https://static.odoo4projects.com/images/favicon.png" alt="ODOO4projects Logo" />
</div>
<div>
<h1>Odoo Chat</h1>
</div>
</div>
<div class="hint">
This chat is dedicated to support for ODOO4projects hosting services only.<br>
Please note: it does not cover general Odoo usage or functional questions.
</div>
<div style="margin-top:18px; font-size:13px; color:var(--muted); line-height:1.5;">
<strong>Usage Tips:</strong>
<ul style="padding-left:20px; margin:8px 0; color:var(--muted);">
<li>
<strong>Operations Agent</strong><br>
Our agent can analyze the odoo and git log files for you. We are currently working on other tasks for him.
</li>
<li>
<strong>Support Ticket</strong><br>
Type <code>/ticket</code> to get in touch with our support team.
</li>
<li>
<strong>Feature Requests</strong><br>
Suggest a new feature for our services by leaving a comment in the chat using this format:<br>
<code>/feature: [Your description here]</code><br>
<em>Example:</em><br>
<code>/feature: Add an automatic backup notification email</code>
</li>
</ul>
<div style="margin-top:10px;">
⚠️ <strong>Important:</strong> If you cannot access Odoo due to a Git change, you can easily <a href="https://ODOO4projects.com" style="color:inherit; text-decoration:underline;">revert the last change via our homepage</a>.
</div>
</div>
</aside>
<main class="chat-card" role="main">
<div class="chat-header">
<div style="display:flex;flex-direction:column">
<div class="title">Chat with the ODOO4projects support</div>
</div>
</div>
<div class="messages" id="messages" aria-live="polite">
<div class="messages-inner" id="messagesInner">
<!-- messages go here -->
</div>
</div>
<div class="composer">
<div class="input" role="search">
<input id="messageInput" placeholder="Write a message..." aria-label="Message input" />
</div>
<button id="sendBtn" class="send">Send</button>
</div>
</main>
</div>
<script>
// Configuration - use your webhook
const WEBHOOK = 'https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/702862fd-dd17-4a34-8efb-e9056d2c50df/chat';
const messagesEl = document.getElementById('messagesInner');
const input = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const params = new URLSearchParams(window.location.search);
const uuid = params.get("uuid");
// Generate UUID once per page load
function generateChatID(){
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const chatId = generateChatID();
function formatTime(date){
return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
function appendMessage(text, who='them'){
const wrap = document.createElement('div');
wrap.className = 'msg' + (who==='me' ? ' me' : '');
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = who==='me' ? 'ME' : '4';
const bubble = document.createElement('div');
bubble.className = 'bubble' + (who==='me' ? ' me' : '');
bubble.innerHTML = text.replace(/\n/g,'<br>');
const meta = document.createElement('div');
meta.className = 'meta-time';
meta.textContent = formatTime(new Date());
const col = document.createElement('div');
col.style.display = 'flex';
col.style.flexDirection = 'column';
col.appendChild(bubble);
col.appendChild(meta);
if(who==='me'){
wrap.appendChild(col);
wrap.appendChild(avatar);
} else {
wrap.appendChild(avatar);
wrap.appendChild(col);
}
messagesEl.appendChild(wrap);
messagesEl.parentElement.scrollTop = messagesEl.parentElement.scrollHeight;
}
let firstMessage = true;
async function sendMessage(){
let text = input.value.trim();
if(!text) return;
// show user message
if (firstMessage) {
text = `${text} $uuid:${uuid}`;
firstMessage = false;
}
if(text.toLowerCase().startsWith('/ticket')){
text = `/ticket UUID:${uuid} chatId: ${chatId}`; // Add UUID after /ticket
}
appendMessage(text, 'me');
input.value = '';
sendBtn.disabled = true;
// show typing indicator
const typingEl = document.createElement('div');
typingEl.className = 'msg';
typingEl.id = 'typing';
typingEl.innerHTML = `
<div class="avatar">4</div>
<div class="typing" style="margin-left:6px"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>
`;
messagesEl.appendChild(typingEl);
messagesEl.parentElement.scrollTop = messagesEl.parentElement.scrollHeight;
try{
// Post to webhook - expecting a JSON response. Adapt to your backend.
const payload = {text: text, uuid: uuid, chatid: chatId};
const res = await fetch(WEBHOOK, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
let data;
const ct = res.headers.get('content-type') || '';
if(ct.includes('application/json')) data = await res.json();
else data = {text: await res.text()};
// remove typing indicator
const t = document.getElementById('typing');
if(t) t.remove();
// Decide where message text is
// Common patterns: {message: '...'}, {text: '...'}, {reply: '...'}, plain text
let replyText = '';
if(typeof data === 'string') replyText = data;
else if(data === null) replyText = 'No response';
else if(data.reply) replyText = data.reply;
else if(data.message) replyText = data.message;
else if(data.text) replyText = data.text;
else if(data.output) replyText = data.output;
else replyText = JSON.stringify(data);
appendMessage(replyText, 'them');
}catch(err){
const t = document.getElementById('typing');
if(t) t.remove();
appendMessage('Error: ' + (err && err.message ? err.message : String(err)), 'them');
} finally {
sendBtn.disabled = false;
}
}
// wire events
sendBtn.addEventListener('click', sendMessage);
input.addEventListener('keydown', (e)=>{ if(e.key === 'Enter' && !e.shiftKey){ e.preventDefault(); sendMessage(); } });
// friendly welcome
appendMessage('I am here to help with all your hosting-related questions.Type /ticket to contact our support team directly. Your UUID is '+uuid, 'them');
</script>
</body>
</html>