Compare commits
60 Commits
ed8734ff76
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4482e753f | ||
|
|
02f4529bb9 | ||
|
|
bc613d668f | ||
|
|
6336cc171b | ||
|
|
82c97e721e | ||
|
|
f9b40e6437 | ||
|
|
1035e0207f | ||
|
|
0e624c3dd4 | ||
|
|
170b7e396d | ||
|
|
95d2c29e7d | ||
|
|
2937c00916 | ||
|
|
7119c7fc28 | ||
|
|
c949b285d1 | ||
|
|
2a75bb6b40 | ||
|
|
08e01e17bb | ||
|
|
23c78b3d78 | ||
|
|
e1642f421f | ||
|
|
d4bf1cfe3a | ||
|
|
1b16ea225e | ||
|
|
67db593d33 | ||
|
|
8044cd5c2d | ||
|
|
34a2ba3b05 | ||
|
|
bd26988584 | ||
|
|
53d61fbaa6 | ||
|
|
f593aca58e | ||
|
|
32a3abaacb | ||
|
|
e201366360 | ||
|
|
1fc2587518 | ||
|
|
f3f58b3371 | ||
|
|
f2aa3a3e1b | ||
|
|
9d9744a945 | ||
|
|
9d99cd572a | ||
|
|
538f73bbb2 | ||
|
|
a01142dfe4 | ||
|
|
fe02208c98 | ||
|
|
028a4418ea | ||
|
|
8fa4a9467b | ||
|
|
fad1033a48 | ||
|
|
018e60896c | ||
|
|
ff60112ddf | ||
|
|
c2ca5fe8e8 | ||
|
|
a6bea0e6c8 | ||
|
|
3d263f7c4e | ||
|
|
ef3d04d284 | ||
|
|
1258df94c2 | ||
|
|
8c20ec66c8 | ||
|
|
65e0b2b177 | ||
|
|
57e0b94717 | ||
|
|
19c65a9326 | ||
|
|
049fc274b0 | ||
|
|
5735765ac8 | ||
|
|
f330f388ce | ||
|
|
bb7fe00605 | ||
|
|
a2ae8f55ac | ||
|
|
c01c03da86 | ||
|
|
ca5d552a86 | ||
|
|
7958993195 | ||
|
|
a9a1aa23b0 | ||
|
|
17a79c5343 | ||
|
|
d9d45f8dde |
96
public/affiliateModal.js
Normal file
96
public/affiliateModal.js
Normal file
@@ -0,0 +1,96 @@
|
||||
// --- Create Affiliate Modal ---
|
||||
function createAffiliateModal() {
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "affiliateBuilder";
|
||||
Object.assign(modal.style, {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
left: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
display: "none",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: "1000"
|
||||
});
|
||||
|
||||
modal.innerHTML = `
|
||||
<div style="background: #fff; padding: 40px 30px; border-radius: 16px; max-width: 500px; width: 90%; position: relative; font-family: 'Arial', sans-serif; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">
|
||||
<span id="closeAffiliateModal" style="position: absolute; top: 15px; right: 20px; cursor: pointer; font-weight: bold; font-size: 24px; color: #555;">×</span>
|
||||
<h2 style="margin-bottom: 15px; font-size: 24px; color: #333;">Affiliate Builder</h2>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display:block; margin-bottom:5px; color:#555;">Your affiliate code provided by us:</label>
|
||||
<input type="text" id="affiliateCode" placeholder="Enter code" style="width:100%; padding:12px 15px; border:1px solid #ccc; border-radius:8px;">
|
||||
</div>
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label style="display:block; margin-bottom:5px; color:#555;">For your reference your campaign name:</label>
|
||||
<input type="text" id="affiliateCampaign" placeholder="Enter campaign" style="width:100%; padding:12px 15px; border:1px solid #ccc; border-radius:8px;">
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:10px; align-items:center; margin-bottom: 15px;">
|
||||
<input type="text" id="affiliateLink" readonly style="flex:1; padding:12px 15px; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">
|
||||
<button id="copyAffiliateLink" style="padding:12px 20px; background:#007BFF; color:#fff; border:none; border-radius:8px; cursor:pointer;">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Close modal
|
||||
document.getElementById("closeAffiliateModal").onclick = () => modal.style.display = "none";
|
||||
modal.onclick = e => { if (e.target === modal) modal.style.display = "none"; };
|
||||
|
||||
// Build link when inputs change
|
||||
const codeInput = document.getElementById("affiliateCode");
|
||||
const campaignInput = document.getElementById("affiliateCampaign");
|
||||
const linkInput = document.getElementById("affiliateLink");
|
||||
[codeInput, campaignInput].forEach(input => {
|
||||
input.addEventListener("input", () => {
|
||||
const code = codeInput.value.trim() || "affiliate";
|
||||
const campaign = campaignInput.value.trim() || "default";
|
||||
linkInput.value = `https://ODOO4projects.com?utm_source=${encodeURIComponent(code)}&utm_campaign=${encodeURIComponent(campaign)}`;
|
||||
});
|
||||
});
|
||||
|
||||
// Copy to clipboard
|
||||
document.getElementById("copyAffiliateLink").addEventListener("click", () => {
|
||||
linkInput.select();
|
||||
document.execCommand("copy");
|
||||
alert("Affiliate link copied!");
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
// --- Open Affiliate Modal ---
|
||||
function openAffiliateModal() {
|
||||
const modal = document.getElementById("affiliateBuilder");
|
||||
modal.style.display = "flex";
|
||||
// Reset fields
|
||||
document.getElementById("affiliateCode").value = "";
|
||||
document.getElementById("affiliateCampaign").value = "";
|
||||
document.getElementById("affiliateLink").value = "";
|
||||
}
|
||||
|
||||
// --- Attach Affiliate Builder Buttons ---
|
||||
function attachAffiliateButtons() {
|
||||
const buttons = Array.from(document.querySelectorAll("button, a"));
|
||||
buttons.forEach(btn => {
|
||||
const text = btn.textContent.trim();
|
||||
if (text === "Start Selling") {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
openAffiliateModal();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Initialize Affiliate Builder ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
createAffiliateModal();
|
||||
attachAffiliateButtons();
|
||||
});
|
||||
|
||||
1
public/affiliateModal.min.js
vendored
Normal file
1
public/affiliateModal.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
function createAffiliateModal(){const e=document.createElement("div");e.id="affiliateBuilder",Object.assign(e.style,{position:"fixed",top:"0",left:"0",width:"100%",height:"100%",backgroundColor:"rgba(0,0,0,0.6)",display:"none",justifyContent:"center",alignItems:"center",zIndex:"1000"}),e.innerHTML='\n <div style="background: #fff; padding: 40px 30px; border-radius: 16px; max-width: 500px; width: 90%; position: relative; font-family: \'Arial\', sans-serif; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">\n <span id="closeAffiliateModal" style="position: absolute; top: 15px; right: 20px; cursor: pointer; font-weight: bold; font-size: 24px; color: #555;">×</span>\n <h2 style="margin-bottom: 15px; font-size: 24px; color: #333;">Affiliate Builder</h2>\n \n <div style="margin-bottom: 15px;">\n <label style="display:block; margin-bottom:5px; color:#555;">Your affiliate code provided by us:</label>\n <input type="text" id="affiliateCode" placeholder="Enter code" style="width:100%; padding:12px 15px; border:1px solid #ccc; border-radius:8px;">\n </div>\n <div style="margin-bottom: 15px;">\n <label style="display:block; margin-bottom:5px; color:#555;">For your reference your campaign name:</label>\n <input type="text" id="affiliateCampaign" placeholder="Enter campaign" style="width:100%; padding:12px 15px; border:1px solid #ccc; border-radius:8px;">\n </div>\n\n <div style="display:flex; gap:10px; align-items:center; margin-bottom: 15px;">\n <input type="text" id="affiliateLink" readonly style="flex:1; padding:12px 15px; border:1px solid #ccc; border-radius:8px; background:#f9f9f9;">\n <button id="copyAffiliateLink" style="padding:12px 20px; background:#007BFF; color:#fff; border:none; border-radius:8px; cursor:pointer;">Copy</button>\n </div>\n </div>\n ',document.body.appendChild(e),document.getElementById("closeAffiliateModal").onclick=()=>e.style.display="none",e.onclick=t=>{t.target===e&&(e.style.display="none")};const t=document.getElementById("affiliateCode"),i=document.getElementById("affiliateCampaign"),n=document.getElementById("affiliateLink");return[t,i].forEach(e=>{e.addEventListener("input",()=>{const e=t.value.trim()||"affiliate",o=i.value.trim()||"default";n.value=`https://ODOO4projects.com?utm_source=${encodeURIComponent(e)}&utm_campaign=${encodeURIComponent(o)}`})}),document.getElementById("copyAffiliateLink").addEventListener("click",()=>{n.select(),document.execCommand("copy"),alert("Affiliate link copied!")}),e}function openAffiliateModal(){document.getElementById("affiliateBuilder").style.display="flex",document.getElementById("affiliateCode").value="",document.getElementById("affiliateCampaign").value="",document.getElementById("affiliateLink").value=""}function attachAffiliateButtons(){Array.from(document.querySelectorAll("button, a")).forEach(e=>{"Start Selling"===e.textContent.trim()&&e.addEventListener("click",e=>{e.preventDefault(),openAffiliateModal()})})}document.addEventListener("DOMContentLoaded",()=>{createAffiliateModal(),attachAffiliateButtons()});
|
||||
214
public/agent-odoo4projects/agent.css
Normal file
214
public/agent-odoo4projects/agent.css
Normal file
@@ -0,0 +1,214 @@
|
||||
/* ================= Chat Toggle Button ================= */
|
||||
#cw-chatToggle {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100000;
|
||||
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
|
||||
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#cw-chatToggle:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
#cw-chatToggle img {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ================= Chat Widget ================= */
|
||||
#cw-chatWidget {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
right: 24px;
|
||||
width: 500px;
|
||||
max-height: 70vh;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 12px;
|
||||
flex-direction: column;
|
||||
z-index: 100000;
|
||||
font-family: system-ui, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#cw-chatWidget.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#cw-chatWidget .cw-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16px 0 10px 0;
|
||||
background: #f8f9f9;
|
||||
}
|
||||
|
||||
#cw-chatWidget .cw-header img {
|
||||
width: 80%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Status Bar */
|
||||
#cw-status {
|
||||
padding: 10px;
|
||||
background-color: #f0f0f0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
max-height: 0;
|
||||
transition: max-height 0.5s ease, padding 0.5s ease;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
#cw-chatMessages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
background: #fafafa;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
#cw-chatForm {
|
||||
display: flex;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
#cw-chatInput {
|
||||
flex: 1;
|
||||
border: none;
|
||||
padding: 8px 20px;
|
||||
font-size: 1rem;
|
||||
border-radius: 0 0 0 12px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#cw-chatForm button {
|
||||
background-color: #0070c0;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
border-radius: 0 0 12px 0;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#cw-chatForm button:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
|
||||
/* Messages Bubbles */
|
||||
.cw-message {
|
||||
max-width: 75%;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.cw-message.user {
|
||||
background-color: #DCF8C6;
|
||||
color: #333;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cw-message.bot {
|
||||
background-color: #E0E0E0;
|
||||
color: #333;
|
||||
margin-right: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* ================= Wizard ================= */
|
||||
.wizard-headline {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 20px 0;
|
||||
background-color: #f8f9f9;
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.wizard {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.wizard li {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 2px solid #fff;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wizard li:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.wizard .header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wizard .text {
|
||||
padding: 20px 10px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Wizard state colors */
|
||||
.wizard li.open {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.wizard li.open .header {
|
||||
background-color: #5d6d7e;
|
||||
}
|
||||
|
||||
.wizard li.done {
|
||||
background-color: #1e8449;
|
||||
}
|
||||
|
||||
.wizard li.done .header {
|
||||
background-color: #145a32;
|
||||
}
|
||||
|
||||
.wizard i {
|
||||
font-size: 18px;
|
||||
}
|
||||
127
public/agent-odoo4projects/agent.js
Normal file
127
public/agent-odoo4projects/agent.js
Normal file
@@ -0,0 +1,127 @@
|
||||
// ================= CONFIG =================
|
||||
//const baseUrl = 'http://localhost:8000'; // Base URL for all resources
|
||||
const baseUrl = 'https://static.odoo4projects.com/agent-odoo4projects';
|
||||
const config = {
|
||||
cssUrl: `${baseUrl}/agent.css`, // CSS file
|
||||
api: `https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/702862fd-dd17-4a34-8efb-e9056d2c50df/chat`, // Backend webhook endpoint
|
||||
buttonImage: `${baseUrl}/images/4.svg`, // Chat toggle button image
|
||||
logoImage: `${baseUrl}/images/logo.svg`, // Logo image for chat widget
|
||||
preamble: '🤔 This is the ODOO4Projects sales agent 🛠️. Besides Sales, he can help revert module installations or restart your server using your UUID and a confirmation email. 📬 For human follow-up, leave your email.' // Initial bot message
|
||||
};
|
||||
|
||||
// ================= WIDGET HTML =================
|
||||
const widgetHTML = `
|
||||
<div id="cw-chatWidget">
|
||||
<div class="cw-header">
|
||||
<img src="${config.logoImage}" alt="Logo" class="cw-logo"/>
|
||||
</div>
|
||||
<div id="cw-chatMessages" class="cw-messages"></div>
|
||||
<form id="cw-chatForm" class="cw-form">
|
||||
<input type="text" id="cw-chatInput" placeholder="Type your message..." />
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
<div id="cw-status" class="cw-status"></div>
|
||||
</div>
|
||||
<button id="cw-chatToggle">
|
||||
<img src="${config.buttonImage}" alt="Chat"/>
|
||||
</button>
|
||||
`;
|
||||
|
||||
// ================= UTILS =================
|
||||
function loadCSS(url) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = url;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
function appendMessage(chatMessages, text, sender) {
|
||||
const msg = document.createElement('div');
|
||||
msg.classList.add('cw-message', sender === 'user' ? 'user' : 'bot');
|
||||
msg.innerHTML = text;
|
||||
chatMessages.appendChild(msg);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
|
||||
// ================= MAIN =================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
// Load CSS
|
||||
loadCSS(config.cssUrl);
|
||||
|
||||
// Inject chat widget HTML
|
||||
document.body.insertAdjacentHTML('beforeend', widgetHTML);
|
||||
document.dispatchEvent(new CustomEvent('JSsucks'));
|
||||
|
||||
// ================= DOM REFERENCES =================
|
||||
const chatToggle = document.getElementById('cw-chatToggle');
|
||||
const chatWidget = document.getElementById('cw-chatWidget');
|
||||
const chatForm = document.getElementById('cw-chatForm');
|
||||
const chatInput = document.getElementById('cw-chatInput');
|
||||
const chatMessages = document.getElementById('cw-chatMessages');
|
||||
const statusDiv = document.getElementById('cw-status');
|
||||
|
||||
const chatid = crypto.randomUUID();
|
||||
let chatOpened = false;
|
||||
|
||||
// ================= BOT COMMUNICATION =================
|
||||
async function sendMessageToBot(messageText) {
|
||||
chatWidget.classList.add('active');
|
||||
appendMessage(chatMessages, messageText, 'user');
|
||||
chatInput.value = '';
|
||||
chatInput.focus();
|
||||
|
||||
try {
|
||||
const res = await fetch(config.api, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: 'sendMessage',
|
||||
chatid,
|
||||
chatInput: messageText
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status) {
|
||||
statusDiv.innerHTML = data.status;
|
||||
requestAnimationFrame(() => {
|
||||
statusDiv.style.maxHeight = statusDiv.scrollHeight + 'px';
|
||||
statusDiv.style.padding = '10px';
|
||||
});
|
||||
} else {
|
||||
statusDiv.style.maxHeight = '0';
|
||||
statusDiv.style.padding = '0 10px';
|
||||
}
|
||||
|
||||
appendMessage(chatMessages, data.output, 'bot');
|
||||
} catch (error) {
|
||||
appendMessage(chatMessages, 'Fehler beim Verbinden mit dem Server. Bitte versuchen Sie es später erneut.', 'bot');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
window.sendMessageToBot = sendMessageToBot;
|
||||
|
||||
// ================= EVENT LISTENERS =================
|
||||
chatToggle.addEventListener('click', () => {
|
||||
const isVisible = chatWidget.classList.contains('active');
|
||||
chatWidget.classList.toggle('active', !isVisible);
|
||||
|
||||
if (!isVisible && !chatOpened) {
|
||||
appendMessage(chatMessages, config.preamble, 'bot');
|
||||
chatOpened = true;
|
||||
}
|
||||
});
|
||||
|
||||
chatForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const input = chatInput.value.trim();
|
||||
if (input) sendMessageToBot(input);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize chat widget:', err);
|
||||
}
|
||||
});
|
||||
72
public/agent-odoo4projects/images/4.svg
Normal file
72
public/agent-odoo4projects/images/4.svg
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="26.458334mm"
|
||||
height="26.458334mm"
|
||||
viewBox="0 0 26.458334 26.458334"
|
||||
version="1.1"
|
||||
id="svg351"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="4.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview353"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2"
|
||||
inkscape:cx="141.75"
|
||||
inkscape:cy="-67.25"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1127"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs348" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-27.042028,-166.7327)">
|
||||
<g
|
||||
id="g22"
|
||||
transform="matrix(0.15346171,0,0,0.15347061,-71.664544,106.29597)">
|
||||
<polygon
|
||||
class="st1"
|
||||
points="713.07,486.53 750.82,441.47 742.83,486.53 "
|
||||
id="polygon14"
|
||||
style="fill:#ed703e" />
|
||||
|
||||
<polygon
|
||||
class="st1"
|
||||
points="752.1,393.8 669.3,488.25 663.26,523.19 733.12,523.19 725.6,566.2 643.2,566.2 643.2,393.8 "
|
||||
id="polygon16"
|
||||
style="fill:#ed703e" />
|
||||
|
||||
<polygon
|
||||
class="st1"
|
||||
points="798.47,393.8 815.61,393.8 815.61,486.53 782.29,486.53 "
|
||||
id="polygon18"
|
||||
style="fill:#ed703e" />
|
||||
|
||||
<polygon
|
||||
class="st1"
|
||||
points="767.94,566.2 775.83,523.19 815.61,523.19 815.61,566.2 "
|
||||
id="polygon20"
|
||||
style="fill:#ed703e" />
|
||||
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
120
public/agent-odoo4projects/images/logo.svg
Normal file
120
public/agent-odoo4projects/images/logo.svg
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 27.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 671.21002 172.39999"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="logo-01.svg"
|
||||
width="671.21002"
|
||||
height="172.39999"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs47" /><sodipodi:namedview
|
||||
id="namedview45"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.86041667"
|
||||
inkscape:cx="335.30266"
|
||||
inkscape:cy="86.004843"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1007"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
.st0{fill:#68696D;}
|
||||
.st1{fill:#ED703E;}
|
||||
</style>
|
||||
<g
|
||||
id="g42"
|
||||
transform="translate(-144.4,-393.8)">
|
||||
<g
|
||||
id="g8">
|
||||
<path
|
||||
class="st0"
|
||||
d="m 427.65,509.26 c -9.27,-5.19 -16.61,-12.39 -22.02,-21.6 -5.42,-9.21 -8.12,-19.62 -8.12,-31.23 0,-11.61 2.71,-22.02 8.12,-31.23 5.41,-9.21 12.75,-16.38 22.02,-21.52 9.26,-5.13 19.42,-7.7 30.47,-7.7 11.16,0 21.35,2.57 30.56,7.7 9.21,5.14 16.49,12.31 21.85,21.52 5.36,9.21 8.04,19.62 8.04,31.23 0,11.61 -2.68,22.02 -8.04,31.23 -5.36,9.21 -12.67,16.41 -21.93,21.6 -9.27,5.19 -19.42,7.79 -30.47,7.79 -11.06,-0.01 -21.22,-2.6 -30.48,-7.79 z m 50.31,-30.9 c 4.86,-5.47 7.28,-12.78 7.28,-21.93 0,-9.38 -2.43,-16.77 -7.28,-22.19 -4.86,-5.41 -11.47,-8.12 -19.84,-8.12 -8.48,0 -15.13,2.71 -19.93,8.12 -4.8,5.42 -7.2,12.81 -7.2,22.19 0,9.27 2.4,16.61 7.2,22.02 4.8,5.42 11.44,8.12 19.93,8.12 8.37,0 14.99,-2.74 19.84,-8.21 z"
|
||||
id="path4" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 528.4,509.26 c -9.27,-5.19 -16.61,-12.39 -22.02,-21.6 -5.42,-9.21 -8.12,-19.62 -8.12,-31.23 0,-11.61 2.71,-22.02 8.12,-31.23 5.41,-9.21 12.75,-16.38 22.02,-21.52 9.26,-5.13 19.42,-7.7 30.47,-7.7 11.16,0 21.35,2.57 30.56,7.7 9.21,5.14 16.49,12.31 21.85,21.52 5.36,9.21 8.04,19.62 8.04,31.23 0,11.61 -2.68,22.02 -8.04,31.23 -5.36,9.21 -12.67,16.41 -21.93,21.6 -9.27,5.19 -19.42,7.79 -30.47,7.79 -11.05,-0.01 -21.21,-2.6 -30.48,-7.79 z m 50.32,-30.9 c 4.86,-5.47 7.28,-12.78 7.28,-21.93 0,-9.38 -2.43,-16.77 -7.28,-22.19 -4.86,-5.41 -11.47,-8.12 -19.84,-8.12 -8.48,0 -15.13,2.71 -19.93,8.12 -4.8,5.42 -7.2,12.81 -7.2,22.19 0,9.27 2.4,16.61 7.2,22.02 4.8,5.42 11.44,8.12 19.93,8.12 8.37,0 14.98,-2.74 19.84,-8.21 z"
|
||||
id="path6" />
|
||||
</g>
|
||||
<path
|
||||
class="st0"
|
||||
d="M 260.51,423.82 C 255,414.35 247.52,407 238.07,401.72 c -9.47,-5.28 -19.93,-7.92 -31.39,-7.92 -11.35,0 -21.79,2.64 -31.32,7.92 -9.52,5.28 -17.05,12.64 -22.62,22.1 -5.57,9.47 -8.34,20.16 -8.34,32.08 0,11.94 2.77,22.63 8.34,32.08 5.56,9.47 13.09,16.86 22.62,22.19 9.52,5.33 19.96,8 31.32,8 11.35,0 21.78,-2.67 31.3,-8 9.52,-5.33 17.03,-12.73 22.54,-22.19 5.51,-9.45 8.26,-20.14 8.26,-32.08 -0.01,-11.92 -2.77,-22.61 -8.27,-32.08 z m -33.46,54.62 c -4.99,5.62 -11.79,8.44 -20.38,8.44 -8.72,0 -15.54,-2.78 -20.48,-8.34 -4.93,-5.56 -7.39,-13.11 -7.39,-22.63 0,-9.63 2.46,-17.23 7.39,-22.79 4.93,-5.56 11.75,-8.34 20.48,-8.34 8.6,0 15.39,2.78 20.38,8.34 4.99,5.56 7.47,13.16 7.47,22.79 0.01,9.4 -2.48,16.91 -7.47,22.53 z"
|
||||
id="path10" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 381.04,424.95 c -5.15,-9.18 -12.52,-16.32 -22.1,-21.41 -9.58,-5.1 -20.73,-7.66 -33.46,-7.66 h -48 v 121.28 h 48 c 12.61,0 23.7,-2.61 33.28,-7.83 9.58,-5.22 16.97,-12.41 22.19,-21.59 5.22,-9.18 7.83,-19.62 7.83,-31.31 0.01,-11.82 -2.58,-22.31 -7.74,-31.48 z m -34.92,54.35 c -5.62,5.39 -13.36,8.09 -23.22,8.09 h -11.7 v -62.27 h 11.7 c 9.87,0 17.6,2.72 23.22,8.17 5.62,5.45 8.44,13.16 8.44,23.13 -0.01,9.87 -2.82,17.49 -8.44,22.88 z"
|
||||
id="path12" />
|
||||
<g
|
||||
id="g22">
|
||||
<polygon
|
||||
class="st1"
|
||||
points="742.83,486.53 713.07,486.53 750.82,441.47 "
|
||||
id="polygon14" />
|
||||
<polygon
|
||||
class="st1"
|
||||
points="669.3,488.25 663.26,523.19 733.12,523.19 725.6,566.2 643.2,566.2 643.2,393.8 752.1,393.8 "
|
||||
id="polygon16" />
|
||||
<polygon
|
||||
class="st1"
|
||||
points="782.29,486.53 798.47,393.8 815.61,393.8 815.61,486.53 "
|
||||
id="polygon18" />
|
||||
<polygon
|
||||
class="st1"
|
||||
points="815.61,566.2 767.94,566.2 775.83,523.19 815.61,523.19 "
|
||||
id="polygon20" />
|
||||
</g>
|
||||
<g
|
||||
id="g40">
|
||||
<path
|
||||
class="st0"
|
||||
d="m 162.99,549.74 c -1.81,1.75 -4.58,2.63 -8.3,2.63 h -6.13 v 13.31 H 144.4 V 533.8 h 10.29 c 3.6,0 6.34,0.87 8.21,2.61 1.87,1.74 2.81,3.98 2.81,6.72 0,2.65 -0.91,4.85 -2.72,6.61 z m -3.18,-2.31 c 1.1,-1.01 1.65,-2.44 1.65,-4.3 0,-3.93 -2.26,-5.9 -6.77,-5.9 h -6.13 v 11.71 h 6.13 c 2.31,0 4.02,-0.51 5.12,-1.51 z"
|
||||
id="path24" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 224.26,565.68 -7.59,-13.04 h -5.03 v 13.04 h -4.16 V 533.8 h 10.29 c 2.41,0 4.44,0.41 6.11,1.23 1.66,0.82 2.9,1.94 3.73,3.34 0.82,1.4 1.23,3 1.23,4.8 0,2.2 -0.63,4.13 -1.9,5.81 -1.27,1.68 -3.16,2.79 -5.7,3.34 l 8.01,13.36 z M 211.64,549.3 h 6.13 c 2.26,0 3.95,-0.56 5.08,-1.67 1.13,-1.11 1.69,-2.6 1.69,-4.46 0,-1.89 -0.56,-3.35 -1.67,-4.39 -1.11,-1.04 -2.81,-1.56 -5.1,-1.56 h -6.13 z"
|
||||
id="path26" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 278.24,563.92 c -2.44,-1.39 -4.37,-3.32 -5.79,-5.81 -1.42,-2.49 -2.13,-5.28 -2.13,-8.39 0,-3.11 0.71,-5.91 2.13,-8.39 1.42,-2.49 3.35,-4.42 5.79,-5.81 2.44,-1.39 5.14,-2.08 8.1,-2.08 2.99,0 5.7,0.69 8.14,2.08 2.44,1.39 4.36,3.32 5.76,5.79 1.4,2.47 2.1,5.28 2.1,8.42 0,3.14 -0.7,5.95 -2.1,8.42 -1.4,2.47 -3.32,4.4 -5.76,5.79 -2.44,1.39 -5.15,2.08 -8.14,2.08 -2.96,-0.02 -5.66,-0.71 -8.1,-2.1 z m 14.12,-3.09 c 1.78,-1.04 3.19,-2.52 4.21,-4.44 1.02,-1.92 1.53,-4.15 1.53,-6.68 0,-2.56 -0.51,-4.8 -1.53,-6.7 -1.02,-1.91 -2.42,-3.38 -4.19,-4.41 -1.77,-1.04 -3.78,-1.56 -6.04,-1.56 -2.26,0 -4.27,0.52 -6.04,1.56 -1.77,1.04 -3.16,2.51 -4.19,4.41 -1.02,1.91 -1.53,4.14 -1.53,6.7 0,2.53 0.51,4.76 1.53,6.68 1.02,1.92 2.42,3.4 4.21,4.44 1.78,1.04 3.79,1.56 6.02,1.56 2.23,-0.01 4.23,-0.52 6.02,-1.56 z"
|
||||
id="path28" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 360.5,533.79 v 23.56 c 0,2.62 -0.81,4.72 -2.42,6.29 -1.62,1.57 -3.75,2.36 -6.4,2.36 -2.68,0 -4.83,-0.8 -6.45,-2.4 -1.62,-1.6 -2.42,-3.79 -2.42,-6.56 h 4.16 c 0.03,1.56 0.43,2.82 1.21,3.8 0.78,0.98 1.94,1.46 3.5,1.46 1.56,0 2.71,-0.46 3.48,-1.39 0.76,-0.93 1.14,-2.11 1.14,-3.55 V 533.8 h 4.2 z"
|
||||
id="path30" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 409.44,537.18 v 10.66 h 11.62 v 3.43 h -11.62 v 10.98 h 12.99 v 3.43 h -17.15 v -31.93 h 17.15 v 3.43 z"
|
||||
id="path32" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 465.89,541.32 c 1.4,-2.49 3.32,-4.43 5.74,-5.83 2.42,-1.4 5.12,-2.1 8.07,-2.1 3.48,0 6.51,0.84 9.1,2.52 2.59,1.68 4.48,4.06 5.67,7.14 h -4.99 c -0.89,-1.92 -2.16,-3.4 -3.82,-4.44 -1.66,-1.04 -3.65,-1.56 -5.97,-1.56 -2.23,0 -4.22,0.52 -5.99,1.56 -1.77,1.04 -3.16,2.51 -4.16,4.41 -1.01,1.91 -1.51,4.14 -1.51,6.7 0,2.53 0.5,4.75 1.51,6.66 1.01,1.91 2.39,3.38 4.16,4.41 1.77,1.04 3.77,1.56 5.99,1.56 2.32,0 4.31,-0.51 5.97,-1.53 1.66,-1.02 2.93,-2.49 3.82,-4.41 h 4.99 c -1.19,3.05 -3.08,5.41 -5.67,7.07 -2.59,1.66 -5.63,2.49 -9.1,2.49 -2.96,0 -5.65,-0.69 -8.07,-2.08 -2.42,-1.39 -4.34,-3.32 -5.74,-5.79 -1.4,-2.47 -2.1,-5.26 -2.1,-8.37 0,-3.11 0.7,-5.92 2.1,-8.41 z"
|
||||
id="path34" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 556.88,533.79 v 3.38 h -8.69 v 28.5 h -4.16 v -28.5 h -8.74 v -3.38 z"
|
||||
id="path36" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 603.06,564.88 c -1.66,-0.75 -2.97,-1.78 -3.91,-3.11 -0.95,-1.33 -1.43,-2.86 -1.46,-4.6 h 4.44 c 0.15,1.49 0.77,2.75 1.85,3.77 1.08,1.02 2.66,1.53 4.73,1.53 1.98,0 3.55,-0.49 4.69,-1.49 1.14,-0.99 1.72,-2.26 1.72,-3.82 0,-1.22 -0.34,-2.21 -1.01,-2.97 -0.67,-0.76 -1.51,-1.34 -2.52,-1.74 -1.01,-0.4 -2.36,-0.82 -4.07,-1.28 -2.1,-0.55 -3.79,-1.1 -5.06,-1.65 -1.27,-0.55 -2.35,-1.41 -3.25,-2.59 -0.9,-1.17 -1.35,-2.75 -1.35,-4.73 0,-1.74 0.44,-3.28 1.33,-4.62 0.88,-1.34 2.13,-2.38 3.73,-3.11 1.6,-0.73 3.44,-1.1 5.51,-1.1 2.99,0 5.44,0.75 7.34,2.24 1.91,1.5 2.98,3.48 3.22,5.95 h -4.57 c -0.15,-1.22 -0.79,-2.29 -1.92,-3.23 -1.13,-0.93 -2.62,-1.39 -4.48,-1.39 -1.74,0 -3.16,0.45 -4.25,1.35 -1.1,0.9 -1.65,2.16 -1.65,3.77 0,1.16 0.33,2.11 0.98,2.84 0.66,0.73 1.46,1.29 2.42,1.67 0.96,0.38 2.31,0.82 4.05,1.3 2.1,0.58 3.8,1.15 5.08,1.72 1.28,0.57 2.38,1.43 3.29,2.61 0.91,1.17 1.37,2.77 1.37,4.78 0,1.56 -0.41,3.02 -1.24,4.39 -0.83,1.37 -2.04,2.49 -3.66,3.34 -1.62,0.85 -3.52,1.28 -5.72,1.28 -2.08,0.01 -3.97,-0.36 -5.63,-1.11 z"
|
||||
id="path38" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
18
public/agent-odoo4projects/index.html
Normal file
18
public/agent-odoo4projects/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OD8N - Odoo & n8n Automation Experts</title>
|
||||
|
||||
<script type="module" src="agent.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
20
public/agent-odoo4projects/start
Executable file
20
public/agent-odoo4projects/start
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
from flask import Flask, send_from_directory
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
directory = os.getcwd() # serve current directory
|
||||
|
||||
@app.route("/", defaults={"path": "index.html"})
|
||||
@app.route("/<path:path>")
|
||||
def serve_file(path):
|
||||
# If file exists, serve it
|
||||
if os.path.isfile(os.path.join(directory, path)):
|
||||
return send_from_directory(directory, path)
|
||||
else:
|
||||
return f"File not found: {path}", 404
|
||||
|
||||
if __name__ == "__main__":
|
||||
# debug=True enables auto-reload when this file changes
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
209
public/buyModal.js
Normal file
209
public/buyModal.js
Normal file
@@ -0,0 +1,209 @@
|
||||
// --- Category Configuration ---
|
||||
const CATEGORY_CONFIG = {
|
||||
3: { showLocation: false, webhook: "" }, // services
|
||||
4: { showLocation: true, webhook: "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c76e6b4e-af2f-4bc3-9875-6460d0ffc8e3" }, // hosting
|
||||
5: { showLocation: false, webhook: "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/fd20e194-b821-4b1f-814f-7bb93ae16046" }, // Workshops
|
||||
7: { showLocation: false, webhook: "" }, // modules
|
||||
};
|
||||
|
||||
// --- Helper: Get cookie by name ---
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
return match ? decodeURIComponent(match[2]) : null;
|
||||
}
|
||||
|
||||
// --- Helper: Set cookie if not already set ---
|
||||
function setCookieIfEmpty(name, value, days = 90) {
|
||||
if (!value) return;
|
||||
if (getCookie(name)) return; // Don't overwrite
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
// --- Add UTM fields to form using cookies ---
|
||||
function addUtmFields(form) {
|
||||
const utmParams = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
||||
const defaults = {
|
||||
utm_source: "homepage",
|
||||
utm_medium: "direct",
|
||||
utm_campaign: "none",
|
||||
utm_term: "",
|
||||
utm_content: ""
|
||||
};
|
||||
|
||||
utmParams.forEach(param => {
|
||||
let input = form.querySelector(`input[name="${param}"]`);
|
||||
if (!input) {
|
||||
input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = param;
|
||||
form.appendChild(input);
|
||||
}
|
||||
// First check cookie, then fallback to default
|
||||
input.value = getCookie(param) || defaults[param];
|
||||
});
|
||||
}
|
||||
|
||||
// --- Create Modal ---
|
||||
function createModal() {
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "buyNowModal";
|
||||
Object.assign(modal.style, {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
left: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "rgba(0,0,0,0.6)",
|
||||
display: "none",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: "1000"
|
||||
});
|
||||
|
||||
modal.innerHTML = `
|
||||
<div style="background: #fff; padding: 40px 30px; border-radius: 16px; max-width: 500px; width: 90%; position: relative; font-family: 'Arial', sans-serif; box-shadow: 0 10px 25px rgba(0,0,0,0.2);">
|
||||
<span id="closeModal" style="position: absolute; top: 15px; right: 20px; cursor: pointer; font-weight: bold; font-size: 24px; color: #555;">×</span>
|
||||
<h2 style="margin-bottom: 15px; font-size: 24px; color: #333;">Order Details</h2>
|
||||
<p id="productText" style="margin-bottom: 25px; font-weight: 500; color: #555;"></p>
|
||||
|
||||
<form id="buyForm" style="display: flex; flex-direction: column; gap: 15px;">
|
||||
<select name="location" id="locationSelect" required style="display: none; padding: 12px 15px; font-size: 16px; border: 1px solid #ccc; border-radius: 8px; outline: none;">
|
||||
<option value="">Select Location</option>
|
||||
<option value="Boston">US, Boston</option>
|
||||
<option value="Manchester">UK, Manchester</option>
|
||||
<option value="Mumbai">IN, Mumbai</option>
|
||||
<option value="Saopaulo">BR, Sao Paulo</option>
|
||||
<option value="Meppel">NL, Meppel</option>
|
||||
</select>
|
||||
|
||||
<input type="text" name="name" placeholder="Name" required style="padding: 12px 15px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<input type="text" name="company" placeholder="Company" required style="padding: 12px 15px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<input type="text" name="country" placeholder="Country" required style="padding: 12px 15px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<input type="text" name="street" placeholder="Street" required style="padding: 12px 15px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<input type="text" name="zip" placeholder="ZIP" required style="max-width: 100px; flex: 1 1 0; padding: 12px 10px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<input type="text" name="town" placeholder="Town" required style="flex: 2 1 0; padding: 12px 10px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
</div>
|
||||
<input type="email" name="email" placeholder="Email" required style="padding: 12px 15px; border: 1px solid #ccc; border-radius: 8px;">
|
||||
<input type="hidden" name="product">
|
||||
<input type="hidden" name="price">
|
||||
<input type="hidden" name="id">
|
||||
<input type="hidden" name="category">
|
||||
|
||||
<button id="submitBuy" type="submit" style="padding: 14px; background: #007BFF; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 18px; font-weight: bold;">Send Order</button>
|
||||
</form>
|
||||
|
||||
<div id="confirmation" style="display: none; margin-top: 20px; padding: 20px; background-color: #e6ffed; color: #056608; border-radius: 12px; text-align: center; font-weight: 500;">
|
||||
<p>Thank you for your purchase! 🎉</p>
|
||||
<button id="closeConfirmation" style="margin-top: 10px; padding: 10px 20px; background: #007BFF; color: #fff; border: none; border-radius: 8px; cursor: pointer;">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
document.getElementById("closeModal").onclick = () => modal.style.display = "none";
|
||||
document.getElementById("closeConfirmation").onclick = () => {
|
||||
document.getElementById("confirmation").style.display = "none";
|
||||
modal.style.display = "none";
|
||||
};
|
||||
modal.onclick = e => { if (e.target === modal) modal.style.display = "none"; };
|
||||
return modal;
|
||||
}
|
||||
|
||||
// --- Open Modal ---
|
||||
function openModal(productHref) {
|
||||
const modal = document.getElementById("buyNowModal");
|
||||
const form = modal.querySelector("#buyForm");
|
||||
const confirmation = modal.querySelector("#confirmation");
|
||||
modal.style.display = "flex";
|
||||
form.style.display = "flex";
|
||||
confirmation.style.display = "none";
|
||||
|
||||
const parts = productHref.split("/").filter(Boolean);
|
||||
const [id, price, category, ...productParts] = parts;
|
||||
const product = decodeURIComponent(productParts.join("/"));
|
||||
const categoryNum = parseInt(category, 10);
|
||||
const config = CATEGORY_CONFIG[categoryNum] || { showLocation: false, webhook: "" };
|
||||
|
||||
const locationSelect = document.getElementById("locationSelect");
|
||||
if (config.showLocation) {
|
||||
locationSelect.style.display = "block";
|
||||
locationSelect.setAttribute("required", "true");
|
||||
} else {
|
||||
locationSelect.style.display = "none";
|
||||
locationSelect.removeAttribute("required");
|
||||
locationSelect.value = "";
|
||||
}
|
||||
|
||||
form.querySelector('input[name="id"]').value = id || "";
|
||||
form.querySelector('input[name="price"]').value = price || "0";
|
||||
form.querySelector('input[name="category"]').value = category || "";
|
||||
form.querySelector('input[name="product"]').value = product || "Unknown";
|
||||
form.dataset.webhook = config.webhook || "";
|
||||
modal.querySelector("#productText").textContent = `We have send you the payment details for your order ${product} $${price} via mail.`;
|
||||
|
||||
addUtmFields(form); // Read from cookie or defaults
|
||||
}
|
||||
|
||||
// --- Handle Form Submit ---
|
||||
function handleFormSubmit() {
|
||||
const form = document.getElementById("buyForm");
|
||||
const confirmation = document.getElementById("confirmation");
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
addUtmFields(form);
|
||||
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
const webhookUrl = form.dataset.webhook;
|
||||
|
||||
if (!webhookUrl) {
|
||||
alert("No webhook configured for this category.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
form.style.display = "none";
|
||||
confirmation.style.display = "block";
|
||||
form.reset();
|
||||
} else {
|
||||
alert("Failed to submit form.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error submitting form.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Attach Buttons ---
|
||||
function attachButtons() {
|
||||
const buttons = Array.from(document.querySelectorAll("button, a"));
|
||||
buttons.forEach(btn => {
|
||||
const text = btn.textContent.trim();
|
||||
if (text === "Buy Now" || text === "Book Now") {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const href = btn.getAttribute("href") || btn.dataset.product || "Unknown/0/0";
|
||||
openModal(href);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Initialize ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
createModal();
|
||||
handleFormSubmit();
|
||||
attachButtons();
|
||||
});
|
||||
|
||||
1
public/buyModal.min.js
vendored
Normal file
1
public/buyModal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
public/images/favicon.png
Normal file
BIN
public/images/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
7
public/minify
Executable file
7
public/minify
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
npm install -g terser
|
||||
|
||||
terser buyModal.js -o buyModal.min.js -c -m
|
||||
terser tryModal.js -o tryModal.min.js -c -m
|
||||
terser setCookie.js -o setCookie.min.js -c -m
|
||||
terser affiliateModal.js -o affiliateModal.min.js -c -m
|
||||
36
public/setCookie.js
Normal file
36
public/setCookie.js
Normal file
@@ -0,0 +1,36 @@
|
||||
function setUtmCookies() {
|
||||
// Helper: get query param from URL
|
||||
function getQueryParam(param) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(param);
|
||||
}
|
||||
|
||||
// Helper: get cookie by name
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
return match ? decodeURIComponent(match[2]) : null;
|
||||
}
|
||||
|
||||
// Helper: set cookie if not already set
|
||||
function setCookieIfEmpty(name, value, days = 90) {
|
||||
if (!value) return;
|
||||
if (getCookie(name)) return; // Don't overwrite existing cookie
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
// Get UTM params
|
||||
const utmSource = getQueryParam('utm_source');
|
||||
const utmMedium = getQueryParam('utm_medium');
|
||||
const utmCampaign = getQueryParam('utm_campaign');
|
||||
|
||||
// Set cookies only if not already present
|
||||
setCookieIfEmpty('utm_source', utmSource, 90);
|
||||
setCookieIfEmpty('utm_medium', utmMedium, 90);
|
||||
setCookieIfEmpty('utm_campaign', utmCampaign, 90);
|
||||
}
|
||||
|
||||
// Run on page load
|
||||
setUtmCookies();
|
||||
|
||||
1
public/setCookie.min.js
vendored
Normal file
1
public/setCookie.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
function setUtmCookies(){function e(e){return new URLSearchParams(window.location.search).get(e)}function t(e,t,n=90){if(!t)return;if(function(e){const t=document.cookie.match(new RegExp("(^| )"+e+"=([^;]+)"));return t?decodeURIComponent(t[2]):null}(e))return;const o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3),document.cookie=`${e}=${encodeURIComponent(t)}; expires=${o.toUTCString()}; path=/; SameSite=Lax`}const n=e("utm_source"),o=e("utm_medium"),m=e("utm_campaign");t("utm_source",n,90),t("utm_medium",o,90),t("utm_campaign",m,90)}setUtmCookies();
|
||||
20
public/start
Executable file
20
public/start
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Simple static file server with live reload
|
||||
# Requires Node.js and live-server
|
||||
|
||||
# Check if live-server is installed
|
||||
if ! command -v live-server &> /dev/null
|
||||
then
|
||||
echo "Installing live-server..."
|
||||
npm install -g live-server
|
||||
fi
|
||||
|
||||
# Serve the current directory with auto-reload
|
||||
echo "Starting live-server on http://localhost:8080 ..."
|
||||
live-server .
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Odoo-style Chat</title>
|
||||
<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>
|
||||
@@ -25,7 +25,27 @@
|
||||
/* 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;background:linear-gradient(135deg,var(--odoo-purple),#b07fa9);display:flex;align-items:center;justify-content:center;color:white;font-weight:700;box-shadow:0 6px 18px rgba(135,90,123,0.12)}
|
||||
|
||||
.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)}
|
||||
|
||||
@@ -78,28 +98,52 @@
|
||||
<div class="app">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="logo">OD</div>
|
||||
<div class="logo">
|
||||
<img src="https://static.odoo4projects.com/images/favicon.png" alt="ODOO4projects Logo" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>Odoo Chat</h1>
|
||||
<p>Connected: <span id="status" class="small">disconnected</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta">Backend: <strong>Custom n8n Chat Webhook</strong></div>
|
||||
<div class="hint">This is a lightweight Odoo-styled chat UI. Messages are posted to your webhook and responses (JSON) are displayed here.</div>
|
||||
<div style="margin-top:18px;font-size:13px;color:var(--muted)">
|
||||
Usage tips:
|
||||
<ul style="padding-left:18px;margin:8px 0;color:var(--muted)">
|
||||
<li>Type a message and press <strong>Enter</strong> or click Send.</li>
|
||||
<li>Expect JSON response with a simple text field (see docs).</li>
|
||||
<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 N8N Backend</div>
|
||||
<div class="sub">Webhook: <span id="webhook" class="small">https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/702862fd-dd17-4a34-8efb-e9056d2c50df/chat</span></div>
|
||||
<div class="title">Chat with the ODOO4projects support</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -123,18 +167,19 @@
|
||||
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 statusEl = document.getElementById('status');
|
||||
const input = document.getElementById('messageInput');
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
document.getElementById('webhook').textContent = WEBHOOK;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const uuid = params.get("uuid");
|
||||
|
||||
// Generate UUID once per page load
|
||||
function generateUUID(){
|
||||
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 CLIENT_UUID = generateUUID();
|
||||
const chatId = generateChatID();
|
||||
|
||||
function formatTime(date){
|
||||
return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
||||
@@ -146,7 +191,7 @@
|
||||
|
||||
const avatar = document.createElement('div');
|
||||
avatar.className = 'avatar';
|
||||
avatar.textContent = who==='me' ? 'ME' : 'AI';
|
||||
avatar.textContent = who==='me' ? 'ME' : '4';
|
||||
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = 'bubble' + (who==='me' ? ' me' : '');
|
||||
@@ -174,26 +219,30 @@
|
||||
messagesEl.parentElement.scrollTop = messagesEl.parentElement.scrollHeight;
|
||||
}
|
||||
|
||||
function setStatus(connected){
|
||||
statusEl.textContent = connected ? 'ready' : 'disconnected';
|
||||
statusEl.style.color = connected ? 'var(--accent)' : 'var(--muted)';
|
||||
}
|
||||
let firstMessage = true;
|
||||
|
||||
async function sendMessage(){
|
||||
const text = input.value.trim();
|
||||
let text = input.value.trim();
|
||||
if(!text) return;
|
||||
// show user message
|
||||
|
||||
if (firstMessage) {
|
||||
text = `${uuid}: ${text}`;
|
||||
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;
|
||||
setStatus(false);
|
||||
|
||||
// show typing indicator
|
||||
const typingEl = document.createElement('div');
|
||||
typingEl.className = 'msg';
|
||||
typingEl.id = 'typing';
|
||||
typingEl.innerHTML = `
|
||||
<div class="avatar">AI</div>
|
||||
<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);
|
||||
@@ -201,7 +250,7 @@
|
||||
|
||||
try{
|
||||
// Post to webhook - expecting a JSON response. Adapt to your backend.
|
||||
const payload = {text: text, uuid: CLIENT_UUID};
|
||||
const payload = {text: text, uuid: uuid, chatid: chatId};
|
||||
const res = await fetch(WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -231,13 +280,11 @@
|
||||
else replyText = JSON.stringify(data);
|
||||
|
||||
appendMessage(replyText, 'them');
|
||||
setStatus(true);
|
||||
|
||||
}catch(err){
|
||||
const t = document.getElementById('typing');
|
||||
if(t) t.remove();
|
||||
appendMessage('Error: ' + (err && err.message ? err.message : String(err)), 'them');
|
||||
setStatus(false);
|
||||
} finally {
|
||||
sendBtn.disabled = false;
|
||||
}
|
||||
@@ -247,17 +294,8 @@
|
||||
sendBtn.addEventListener('click', sendMessage);
|
||||
input.addEventListener('keydown', (e)=>{ if(e.key === 'Enter' && !e.shiftKey){ e.preventDefault(); sendMessage(); } });
|
||||
|
||||
// optimistic check to see if webhook is reachable via HEAD
|
||||
(async function ping(){
|
||||
try{
|
||||
const r = await fetch(WEBHOOK, {method:'HEAD'});
|
||||
setStatus(r.ok);
|
||||
}catch(e){ setStatus(false); }
|
||||
})();
|
||||
|
||||
// friendly welcome
|
||||
appendMessage('Hello! This chat will post your message to the configured webhook and show the response here. Try sending a message. Your session id is ' + CLIENT_UUID, 'them');
|
||||
setStatus(false);
|
||||
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>
|
||||
|
||||
211
public/tryModal.js
Normal file
211
public/tryModal.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const TRYNOW_WEBHOOK_URL = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c25169c6-4234-4b47-8e74-612b9539da0a";
|
||||
|
||||
// --- Helper: Get cookie value ---
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Add UTM fields to form ---
|
||||
function addUtmFields(form) {
|
||||
const utmParams = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
||||
|
||||
const defaults = {
|
||||
utm_source: "homepage",
|
||||
utm_medium: "direct",
|
||||
utm_campaign: "none"
|
||||
};
|
||||
|
||||
utmParams.forEach(param => {
|
||||
let input = form.querySelector(`input[name="${param}"]`);
|
||||
if (!input) {
|
||||
input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = param;
|
||||
form.appendChild(input);
|
||||
}
|
||||
const cookieValue = getCookie(param);
|
||||
input.value = cookieValue || defaults[param] || "";
|
||||
});
|
||||
}
|
||||
|
||||
// --- Create modal ---
|
||||
function tryNow_createModal() {
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "trynowModal";
|
||||
modal.style.position = "fixed";
|
||||
modal.style.top = "0";
|
||||
modal.style.left = "0";
|
||||
modal.style.width = "100%";
|
||||
modal.style.height = "100%";
|
||||
modal.style.backgroundColor = "rgba(0,0,0,0.6)";
|
||||
modal.style.display = "none";
|
||||
modal.style.justifyContent = "center";
|
||||
modal.style.alignItems = "center";
|
||||
modal.style.zIndex = "1000";
|
||||
|
||||
modal.innerHTML = `
|
||||
<div style="
|
||||
background: #ffffff;
|
||||
padding: 40px 30px;
|
||||
border-radius: 16px;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
position: relative;
|
||||
font-family: 'Arial', sans-serif;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
||||
">
|
||||
<span id="trynowCloseModal" style="
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
color: #555;
|
||||
">×</span>
|
||||
|
||||
<h2 style="margin-bottom: 20px; font-size: 24px; color: #333;">Order Details</h2>
|
||||
|
||||
<form id="trynowForm" style="display: flex; flex-direction: column; gap: 15px;">
|
||||
<input type="email" name="email" placeholder="Email" required style="padding: 12px 15px; font-size: 16px; border: 1px solid #ccc; border-radius: 8px; outline: none;">
|
||||
|
||||
<select name="location" id="trynowLocationSelect" required style="padding: 12px 15px; font-size: 16px; border: 1px solid #ccc; border-radius: 8px; outline: none;">
|
||||
<option value="">Select Location</option>
|
||||
<option value="Boston">US, Boston</option>
|
||||
<option value="Manchester">UK, Manchester</option>
|
||||
<option value="Mumbai">IN, Mumbai</option>
|
||||
<option value="saopaulo">BR, Sao Paulo</option>
|
||||
<option value="Meppel">NL, Meppel</option>
|
||||
</select>
|
||||
|
||||
<select name="product" required style="padding: 12px 15px; font-size: 16px; border: 1px solid #ccc; border-radius: 8px; outline: none;">
|
||||
<option value="odoo_19" selected>ODOO</option>
|
||||
<option value="N8N">n8n</option>
|
||||
</select>
|
||||
|
||||
<button type="submit" style="
|
||||
padding: 14px;
|
||||
background: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
transition: background 0.3s;
|
||||
">Start free Trial</button>
|
||||
</form>
|
||||
|
||||
<div id="trynowConfirmation" style="
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background-color: #e6ffed;
|
||||
color: #056608;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
">
|
||||
<p>Thank you for your submission! 🎉</p>
|
||||
<button id="trynowCloseConfirmation" style="
|
||||
margin-top: 10px;
|
||||
padding: 10px 20px;
|
||||
background: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background 0.3s;
|
||||
">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add UTM fields after form exists
|
||||
const form = modal.querySelector("#trynowForm");
|
||||
addUtmFields(form);
|
||||
|
||||
// Close modal handlers
|
||||
document.getElementById("trynowCloseModal").onclick = () => { modal.style.display = "none"; };
|
||||
document.getElementById("trynowCloseConfirmation").onclick = () => {
|
||||
document.getElementById("trynowConfirmation").style.display = "none";
|
||||
modal.style.display = "none";
|
||||
};
|
||||
modal.onclick = (e) => { if (e.target === modal) modal.style.display = "none"; };
|
||||
|
||||
return modal;
|
||||
}
|
||||
|
||||
// --- Handle form submission ---
|
||||
function tryNow_handleFormSubmit() {
|
||||
const form = document.getElementById("trynowForm");
|
||||
const confirmation = document.getElementById("trynowConfirmation");
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Update UTM fields from cookies
|
||||
addUtmFields(form);
|
||||
|
||||
const data = {};
|
||||
new FormData(form).forEach((value, key) => (data[key] = value));
|
||||
|
||||
try {
|
||||
const res = await fetch(TRYNOW_WEBHOOK_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
form.style.display = "none";
|
||||
confirmation.style.display = "block";
|
||||
form.reset();
|
||||
} else {
|
||||
alert("Failed to submit form.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error submitting form.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Attach "Try Now" buttons ---
|
||||
function tryNow_attachButtons() {
|
||||
const buttons = Array.from(document.querySelectorAll("a, button"));
|
||||
buttons.forEach(btn => {
|
||||
if (btn.textContent && btn.textContent.trim() === "Try Now") {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const modal = document.getElementById("trynowModal");
|
||||
if (modal) {
|
||||
modal.style.display = "flex";
|
||||
|
||||
// Reset modal state
|
||||
const form = document.getElementById("trynowForm");
|
||||
const confirmation = document.getElementById("trynowConfirmation");
|
||||
if (form && confirmation) {
|
||||
form.style.display = "flex";
|
||||
confirmation.style.display = "none";
|
||||
addUtmFields(form); // refresh UTM fields each open
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Initialize ---
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
tryNow_createModal();
|
||||
tryNow_handleFormSubmit();
|
||||
tryNow_attachButtons();
|
||||
});
|
||||
|
||||
1
public/tryModal.min.js
vendored
Normal file
1
public/tryModal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -2,224 +2,265 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Upgrade Form</title>
|
||||
<title>Check and Upgrade Your Plan</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
background: #f9f9f9;
|
||||
font-family: "Inter", "Segoe UI", Arial, sans-serif;
|
||||
background-color: #f4f5f7;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.summary-container {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.06);
|
||||
padding: 40px;
|
||||
max-width: 720px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
color: #262626;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
form {
|
||||
|
||||
/* New small centered contract name */
|
||||
.contract-name {
|
||||
text-align: center;
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: #777;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
|
||||
.grid-item {
|
||||
background: #fafafa;
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 8px;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.grid-item.full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
input[type="number"], input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
.value {
|
||||
font-size: 1.1em;
|
||||
color: #222;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
margin-right: 6px;
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: #888;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.submit-section {
|
||||
grid-column: 1 / -1;
|
||||
|
||||
/* Button styling */
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
margin-top: 40px;
|
||||
gap: 15px;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background-color: #875A7B; /* Odoo purple */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 10px 22px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||
min-width: 180px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: #744e6a;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cost {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
|
||||
.action-btn.secondary {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
.readonly-text {
|
||||
padding: 8px;
|
||||
background: #eee;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
.action-btn.secondary:hover {
|
||||
background-color: #5a636a;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Upgrade Your Plan</h2>
|
||||
<div id="uuidDisplay" style="text-align:center; font-size: 0.9em; color: #666; margin-bottom: 10px;"></div>
|
||||
<form id="upgradeForm">
|
||||
<div>
|
||||
<label><input type="checkbox" id="git" name="git"> GIT</label>
|
||||
<div class="summary-container">
|
||||
<h2>Check and Upgrade Your Plan</h2>
|
||||
<div class="contract-name" id="contractName">Loading...</div>
|
||||
<div class="subtitle">
|
||||
Choose an upgrade. With this upgade you only pay for the <b>remaining days</b> of your current contract. Due to technical reasons, the <b>total price</b> will be included in the payment link sent to your email. If you don’t want to upgrade, simply don’t complete the payment. Current yearly prices are listed on our homepage, and new features will apply when you extend your contract next time.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="domains">Domains</label>
|
||||
<input type="number" id="domains" name="domains" min="1" max="10">
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<div class="label">GIT</div>
|
||||
<div class="value" id="gitValue">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="backupSlots">Backup Slots</label>
|
||||
<input type="number" id="backupSlots" name="backupSlots" min="2" max="10">
|
||||
<div class="grid-item">
|
||||
<div class="label">Domains</div>
|
||||
<div class="value" id="domainsValue">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="workers">Workers</label>
|
||||
<input type="number" id="workers" name="workers" min="1" max="3">
|
||||
<div class="grid-item">
|
||||
<div class="label">Backup Slots</div>
|
||||
<div class="value" id="backupValue">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label for="hdd">HDD (MB)</label>
|
||||
<input type="number" id="hdd" name="hdd" min="250" max="2048" step="250">
|
||||
<div class="grid-item">
|
||||
<div class="label">Workers</div>
|
||||
<div class="value" id="workersValue">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label>Valid Until</label>
|
||||
<div id="validUntil" class="readonly-text">Loading...</div>
|
||||
<div class="grid-item full">
|
||||
<div class="label">HDD (MB)</div>
|
||||
<div class="value" id="hddValue">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="submit-section">
|
||||
<button type="submit">Upgrade</button>
|
||||
<div class="cost" id="cost">$0,00</div>
|
||||
<div class="grid-item full">
|
||||
<div class="label">Expires</div>
|
||||
<div class="value" id="expiresValue">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="actions">
|
||||
<div class="action-row">
|
||||
<button class="action-btn" id="upgradeRiseBtn">Upgrade to "On the Rise"</button>
|
||||
<button class="action-btn" id="upgradePowerhouseBtn">Upgrade to "Powerhouse"</button>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<button class="action-btn secondary" id="addDomainBtn">Add a Domain</button>
|
||||
<button class="action-btn secondary" id="addBackupsBtn">Add 5 Backup Slots</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer" id="uuidDisplay">
|
||||
UUID: <span id="uuidText">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const webhookUrl = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/0c8536be-d175-4740-8e78-123159193b23";
|
||||
const webhook_buy = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/2366cc41-bfd9-41c0-b9b4-bea1e60726f1";
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const uuid = params.get("uuid");
|
||||
document.getElementById("uuidDisplay").textContent = uuid ? uuid.toLowerCase() : "-";
|
||||
|
||||
|
||||
const webhookBase = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/0c8536be-d175-4740-8e78-123159193b23?uuid=${uuid}";
|
||||
const webhookPost = `${webhookBase}/submit-data`;
|
||||
|
||||
async function loadData() {
|
||||
async function loadData() {
|
||||
if (!uuid) {
|
||||
alert("Missing uuid parameter in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("uuidText").textContent = uuid;
|
||||
|
||||
try {
|
||||
console.log(webhookBase)
|
||||
const res = await fetch(webhookBase); // Fetching the webhook JSON
|
||||
const res = await fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ uuid })
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Failed to fetch data");
|
||||
const data = await res.json();
|
||||
|
||||
// Save original data globally
|
||||
window.originalData = data;
|
||||
document.getElementById("gitValue").textContent = data.git ? "Enabled" : "Disabled";
|
||||
document.getElementById("domainsValue").textContent = data.domains || "-";
|
||||
document.getElementById("backupValue").textContent = data.backupSlots || "-";
|
||||
document.getElementById("workersValue").textContent = data.workers || "-";
|
||||
document.getElementById("hddValue").textContent = data.hdd ? `${data.hdd} MB` : "-";
|
||||
document.getElementById("expiresValue").textContent = data.expires || "-";
|
||||
document.getElementById("contractName").textContent = data.contract_name || "-";
|
||||
|
||||
// Save prices globally
|
||||
window.prices = data.prices || {
|
||||
git: 10,
|
||||
domain: 1,
|
||||
backupSlot: 1,
|
||||
worker: 50,
|
||||
hddUnit: 10
|
||||
};
|
||||
// Disable buttons based on contract_name
|
||||
if (data.contract_name === "On the Rise") {
|
||||
document.getElementById("upgradeRiseBtn").disabled = true;
|
||||
} else if (data.contract_name === "Powerhouse") {
|
||||
document.getElementById("upgradeRiseBtn").disabled = true;
|
||||
document.getElementById("upgradePowerhouseBtn").disabled = true;
|
||||
}
|
||||
|
||||
// Fill form fields
|
||||
document.getElementById("git").checked = data.git || false;
|
||||
document.getElementById("domains").value = data.domains || 1;
|
||||
document.getElementById("backupSlots").value = data.backupSlots || 2;
|
||||
document.getElementById("workers").value = data.workers || 1;
|
||||
document.getElementById("hdd").value = data.hdd || 250;
|
||||
document.getElementById("validUntil").textContent = data.validUntil || "-";
|
||||
|
||||
calculateCost();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error loading data.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function calculateCost() {
|
||||
if (!window.originalData || !window.prices) return;
|
||||
|
||||
const domains = parseInt(document.getElementById("domains").value) || 0;
|
||||
const backups = parseInt(document.getElementById("backupSlots").value) || 0;
|
||||
const workers = parseInt(document.getElementById("workers").value) || 0;
|
||||
const hdd = parseInt(document.getElementById("hdd").value) || 0;
|
||||
const gitChecked = document.getElementById("git").checked;
|
||||
|
||||
const baselineDomains = parseInt(window.originalData.domains) || 0;
|
||||
const baselineBackups = parseInt(window.originalData.backupSlots) || 0;
|
||||
const baselineWorkers = parseInt(window.originalData.workers) || 0;
|
||||
const baselineHDD = parseInt(window.originalData.hdd) || 0;
|
||||
const baselineGit = window.originalData.git || false;
|
||||
|
||||
const extraDomains = Math.max(domains - baselineDomains, 0);
|
||||
const extraBackups = Math.max(backups - baselineBackups, 0);
|
||||
const extraWorkers = Math.max(workers - baselineWorkers, 0);
|
||||
const extraHDDUnits = Math.max(Math.floor((hdd - baselineHDD) / 250), 0);
|
||||
const extraGit = gitChecked && !baselineGit ? 1 : 0;
|
||||
|
||||
const cost =
|
||||
extraGit * window.prices.git +
|
||||
extraDomains * window.prices.domain +
|
||||
extraBackups * window.prices.backupSlot +
|
||||
extraWorkers * window.prices.worker +
|
||||
extraHDDUnits * window.prices.hddUnit;
|
||||
|
||||
document.getElementById("cost").textContent = `$${cost.toFixed(2)}`;
|
||||
}
|
||||
|
||||
document.querySelectorAll("input").forEach(input => {
|
||||
input.addEventListener("input", calculateCost);
|
||||
});
|
||||
|
||||
document.getElementById("upgradeForm").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
uuid: uuid,
|
||||
git: document.getElementById("git").checked,
|
||||
domains: parseInt(document.getElementById("domains").value),
|
||||
backupSlots: parseInt(document.getElementById("backupSlots").value),
|
||||
workers: parseInt(document.getElementById("workers").value),
|
||||
hdd: parseInt(document.getElementById("hdd").value)
|
||||
};
|
||||
}
|
||||
|
||||
async function buyProduct(product_id) {
|
||||
if (!uuid) return;
|
||||
try {
|
||||
const res = await fetch(webhookPost, {
|
||||
const res = await fetch(webhook_buy, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify({ uuid, product_id })
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed to submit data");
|
||||
alert("Upgrade submitted successfully!");
|
||||
|
||||
if (!res.ok) throw new Error("Failed to send purchase request");
|
||||
|
||||
document.body.innerHTML = `
|
||||
<div style="text-align:center;padding:80px;background:#fff;border-radius:12px;max-width:700px;margin:auto;box-shadow:0 4px 12px rgba(0,0,0,0.1);">
|
||||
<h2>✅ Your request has been sent</h2>
|
||||
<p>Please check your email for confirmation.</p>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error submitting form.");
|
||||
alert("Error sending request.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("upgradeRiseBtn").addEventListener("click", () => buyProduct("ontherise"));
|
||||
document.getElementById("upgradePowerhouseBtn").addEventListener("click", () => buyProduct("powerhouse"));
|
||||
document.getElementById("addDomainBtn").addEventListener("click", () => buyProduct("1-domain"));
|
||||
document.getElementById("addBackupsBtn").addEventListener("click", () => buyProduct("5-backup"));
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
|
||||
251
public/upsell.html_
Normal file
251
public/upsell.html_
Normal file
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Upgrade Form</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
input[type="number"], input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
margin-right: 6px;
|
||||
}
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.submit-section {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #aaa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.cost {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
.readonly-text {
|
||||
padding: 8px;
|
||||
background: #eee;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.confirmation {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Upgrade Your Plan</h2>
|
||||
<div id="uuidDisplay" style="text-align:center; font-size: 0.9em; color: #666; margin-bottom: 10px;">Enhance your Odoo experience by upgrading your feature set here. You’ll only pay for the remaining duration of your current Odoo contract.
|
||||
When it’s time for your annual renewal, you will receive a reminder email with all the details.</div>
|
||||
|
||||
|
||||
<form id="upgradeForm">
|
||||
<div>
|
||||
<label><input type="checkbox" id="git" name="git"> GIT</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="domains">Domains</label>
|
||||
<input type="number" id="domains" name="domains" min="1" max="10">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="backupSlots">Backup Slots</label>
|
||||
<input type="number" id="backupSlots" name="backupSlots" min="2" max="10">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="workers">Workers</label>
|
||||
<input type="number" id="workers" name="workers" min="1" max="3">
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label for="hdd">HDD (MB)</label>
|
||||
<input type="number" id="hdd" name="hdd" min="250" max="2048" step="250">
|
||||
</div>
|
||||
|
||||
<div class="full-width">
|
||||
<label>Expires</label>
|
||||
<div id="expires" class="readonly-text">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="submit-section">
|
||||
<button type="submit" id="submitBtn" disabled>Upgrade</button>
|
||||
<div class="cost" id="cost">$0.00</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const uuid = params.get("uuid");
|
||||
|
||||
const webhookUrl = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/0c8536be-d175-4740-8e78-123159193b23";
|
||||
const webhookPost = "https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/3709b60f-935e-43e4-834a-5060a40182dd";
|
||||
|
||||
async function loadData() {
|
||||
if (!uuid) {
|
||||
alert("Missing uuid parameter in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(webhookUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ uuid })
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Failed to fetch data");
|
||||
const data = await res.json();
|
||||
|
||||
window.originalData = data;
|
||||
window.prices = data.prices || {
|
||||
git: 10,
|
||||
domain: 1,
|
||||
backupSlot: 1,
|
||||
worker: 50,
|
||||
hddUnit: 10
|
||||
};
|
||||
|
||||
document.getElementById("git").checked = data.git || false;
|
||||
document.getElementById("domains").value = data.domains || 1;
|
||||
document.getElementById("backupSlots").value = data.backupSlots || 2;
|
||||
document.getElementById("workers").value = data.workers || 1;
|
||||
document.getElementById("hdd").value = data.hdd || 250;
|
||||
document.getElementById("expires").textContent = data.expires || "-";
|
||||
|
||||
calculateCost();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error loading data.");
|
||||
}
|
||||
}
|
||||
|
||||
function calculateCost() {
|
||||
if (!window.originalData || !window.prices) return;
|
||||
|
||||
const domains = parseInt(document.getElementById("domains").value) || 0;
|
||||
const backups = parseInt(document.getElementById("backupSlots").value) || 0;
|
||||
const workers = parseInt(document.getElementById("workers").value) || 0;
|
||||
const hdd = parseInt(document.getElementById("hdd").value) || 0;
|
||||
const gitChecked = document.getElementById("git").checked;
|
||||
|
||||
const baselineDomains = parseInt(window.originalData.domains) || 0;
|
||||
const baselineBackups = parseInt(window.originalData.backupSlots) || 0;
|
||||
const baselineWorkers = parseInt(window.originalData.workers) || 0;
|
||||
const baselineHDD = parseInt(window.originalData.hdd) || 0;
|
||||
const baselineGit = window.originalData.git || false;
|
||||
|
||||
const extraDomains = Math.max(domains - baselineDomains, 0);
|
||||
const extraBackups = Math.max(backups - baselineBackups, 0);
|
||||
const extraWorkers = Math.max(workers - baselineWorkers, 0);
|
||||
const extraHDDUnits = Math.max(Math.floor((hdd - baselineHDD) / 250), 0);
|
||||
const extraGit = gitChecked && !baselineGit ? 1 : 0;
|
||||
|
||||
const cost =
|
||||
extraGit * window.prices.git +
|
||||
extraDomains * window.prices.domain +
|
||||
extraBackups * window.prices.backupSlot +
|
||||
extraWorkers * window.prices.worker +
|
||||
extraHDDUnits * window.prices.hddUnit;
|
||||
|
||||
document.getElementById("cost").textContent = `$${cost.toFixed(2)}`;
|
||||
|
||||
// Enable/disable button based on cost
|
||||
document.getElementById("submitBtn").disabled = cost <= 0;
|
||||
}
|
||||
|
||||
document.querySelectorAll("input").forEach(input => {
|
||||
input.addEventListener("input", calculateCost);
|
||||
});
|
||||
|
||||
document.getElementById("upgradeForm").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const payload = {
|
||||
uuid: uuid,
|
||||
git: document.getElementById("git").checked,
|
||||
domains: parseInt(document.getElementById("domains").value),
|
||||
backupSlots: parseInt(document.getElementById("backupSlots").value),
|
||||
workers: parseInt(document.getElementById("workers").value),
|
||||
hdd: parseInt(document.getElementById("hdd").value),
|
||||
cost: document.getElementById("cost").textContent.replace('$','')
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(webhookPost, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw new Error("Failed to submit data");
|
||||
|
||||
// Replace page content with confirmation message
|
||||
document.body.innerHTML = `
|
||||
<div class="confirmation">
|
||||
<h2>✅ Your upgrade request has been sent</h2>
|
||||
<p>Please check your email for confirmation.</p>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert("Error submitting form.");
|
||||
}
|
||||
});
|
||||
|
||||
loadData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
186
public/widget.js
Normal file
186
public/widget.js
Normal file
@@ -0,0 +1,186 @@
|
||||
(function() {
|
||||
console.log("WIDGET JS LOADED");
|
||||
|
||||
const container = document.getElementById("my-signup-widget");
|
||||
if (!container) return;
|
||||
|
||||
// --- Hardcoded tracking values ---
|
||||
const utm_source = "scholarix";
|
||||
const utm_campaign = "trial_widget";
|
||||
const default_product = "odoo_19";
|
||||
|
||||
const shadow = container.attachShadow({ mode: "open" });
|
||||
|
||||
const styles = `
|
||||
:host {
|
||||
all: initial;
|
||||
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Inter,Arial,sans-serif;
|
||||
display: block;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
color: #cffaff;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #084574;
|
||||
border: 1px solid #cffaff33;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: #cffaff22;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #cffaff;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 13px;
|
||||
color: #a9d8f3;
|
||||
}
|
||||
|
||||
label, .label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #cffaff;
|
||||
font-weight: 600;
|
||||
margin: 12px 0 6px;
|
||||
}
|
||||
|
||||
select, input[type="email"] {
|
||||
width: 100%;
|
||||
background: #08395f;
|
||||
border: 1px solid #cffaff44;
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
color: #cffaff;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg,#cffaff,#6ad7ff);
|
||||
color: #084574;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 18px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: linear-gradient(135deg,#b3f2ff,#84e2ff);
|
||||
}
|
||||
|
||||
.footnote {
|
||||
margin-top: 10px;
|
||||
font-size: 11px;
|
||||
color: #a9d8f3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 20px;
|
||||
font-size: 15px;
|
||||
color: #cffaff;
|
||||
background: #08395f;
|
||||
border: 1px solid #cffaff55;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
word-break: break-word;
|
||||
}
|
||||
.status.success { background: #065f46; color: #cffaff; }
|
||||
.status.error { background: #991b1b; color: #fff; }
|
||||
`;
|
||||
|
||||
const html = `
|
||||
<form class="card">
|
||||
<div class="header">
|
||||
<img class="icon" src="images/logo.jpg">
|
||||
<div>
|
||||
<div class="title">Try Managed ODOO - 4 Weeks Free</div>
|
||||
<div class="subtitle">Full-featured 4-week trial. No credit card required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="location">Location</label>
|
||||
<select id="location" name="location" required>
|
||||
<option value="" disabled selected>Select a region</option>
|
||||
<option value="manchester">UK, Manchester</option>
|
||||
<option value="boston">US, Boston</option>
|
||||
<option value="mumbai">IN, Mumbai</option>
|
||||
<option value="saopaulo">BR, Sao Paulo</option>
|
||||
<option value="Meppel">NL, Meppel</option>
|
||||
</select>
|
||||
|
||||
<label for="email">Work Email</label>
|
||||
<input id="email" name="email" type="email" required placeholder="you@company.com">
|
||||
|
||||
<input type="hidden" name="product" value="${default_product}">
|
||||
<input type="hidden" name="utm_source" value="${utm_source}">
|
||||
<input type="hidden" name="utm_campaign" value="${utm_campaign}">
|
||||
|
||||
<button type="submit" class="plausible-event-name=Trial">Start 4-Week Free Trial</button>
|
||||
|
||||
<div class="footnote">
|
||||
By submitting, you agree to the T&C of Scholarix.
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="status" class="status" style="display:none;"></div>
|
||||
`;
|
||||
|
||||
shadow.innerHTML = `<style>${styles}</style>${html}`;
|
||||
|
||||
const form = shadow.querySelector("form");
|
||||
const statusEl = shadow.getElementById("status");
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
statusEl.style.display = "block";
|
||||
statusEl.textContent = "Submitting...";
|
||||
statusEl.className = "status";
|
||||
|
||||
try {
|
||||
const formData = new FormData(form);
|
||||
const res = await fetch("https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/47129739-e60b-4944-b6c2-d3fd5ce0991b", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
|
||||
form.style.display = "none";
|
||||
statusEl.textContent = text;
|
||||
statusEl.className = "status success";
|
||||
} catch (err) {
|
||||
statusEl.textContent = "❌ Failed to submit. Please try again.";
|
||||
statusEl.className = "status error";
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user