new upsell

This commit is contained in:
Oliver
2025-10-12 09:06:37 -03:00
parent 67db593d33
commit 1b16ea225e
3 changed files with 464 additions and 197 deletions

20
public/start Executable file
View 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 .

View File

@@ -2,247 +2,243 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Upgrade Form</title> <title>Check and Upgrade Your Plan</title>
<style> <style>
body { body {
font-family: Arial, sans-serif; font-family: "Inter", "Segoe UI", Arial, sans-serif;
padding: 20px; background-color: #f4f5f7;
max-width: 700px; color: #333;
margin: auto; margin: 0;
background: #f9f9f9; 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 { h2 {
text-align: center; text-align: center;
color: #333; color: #262626;
margin-bottom: 8px;
font-weight: 600;
} }
form {
.subtitle {
text-align: center;
font-size: 0.9em;
color: #777;
margin-bottom: 30px;
line-height: 1.5;
}
.grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 20px; gap: 20px;
background: #fff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
} }
label {
display: block; .grid-item {
font-weight: bold; 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; margin-bottom: 6px;
} }
input[type="number"], input[type="text"] {
width: 100%; .value {
padding: 8px; font-size: 1.1em;
border-radius: 6px; color: #222;
border: 1px solid #ccc;
} }
input[type="checkbox"] {
transform: scale(1.2); .footer {
margin-right: 6px; text-align: center;
font-size: 0.9em;
color: #888;
margin-top: 30px;
} }
.full-width {
grid-column: 1 / -1; /* Button styling */
} .actions {
.submit-section {
grid-column: 1 / -1;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
gap: 12px; margin-top: 40px;
margin-top: 20px; gap: 15px;
} }
button {
padding: 10px 20px; .action-row {
background-color: #4CAF50; display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.action-btn {
background-color: #875A7B; /* Odoo purple */
color: white; color: white;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
padding: 10px 22px;
font-size: 15px;
cursor: pointer; cursor: pointer;
font-size: 16px; transition: background-color 0.2s ease, transform 0.1s ease;
min-width: 180px;
} }
button:disabled {
background-color: #aaa; .action-btn:hover {
cursor: not-allowed; background-color: #744e6a;
transform: translateY(-1px);
} }
.cost {
font-weight: bold; .action-btn.secondary {
font-size: 18px; background-color: #6c757d;
} }
.readonly-text {
padding: 8px; .action-btn.secondary:hover {
background: #eee; background-color: #5a636a;
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> </style>
</head> </head>
<body> <body>
<h2>Upgrade Your Plan</h2> <div class="summary-container">
<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. Youll only pay for the remaining duration of your current Odoo contract. <h2>Check and Upgrade Your Plan</h2>
When its time for your annual renewal, you will receive a reminder email with all the details.</div> <div class="subtitle">
Review your current Odoo configuration and choose an upgrade option below.
<form id="upgradeForm">
<div>
<label><input type="checkbox" id="git" name="git"> GIT</label>
</div> </div>
<div> <div class="grid">
<label for="domains">Domains</label> <div class="grid-item">
<input type="number" id="domains" name="domains" min="1" max="10"> <div class="label">GIT</div>
<div class="value" id="gitValue">Loading...</div>
</div> </div>
<div> <div class="grid-item">
<label for="backupSlots">Backup Slots</label> <div class="label">Domains</div>
<input type="number" id="backupSlots" name="backupSlots" min="2" max="10"> <div class="value" id="domainsValue">Loading...</div>
</div> </div>
<div> <div class="grid-item">
<label for="workers">Workers</label> <div class="label">Backup Slots</div>
<input type="number" id="workers" name="workers" min="1" max="3"> <div class="value" id="backupValue">Loading...</div>
</div> </div>
<div class="full-width"> <div class="grid-item">
<label for="hdd">HDD (MB)</label> <div class="label">Workers</div>
<input type="number" id="hdd" name="hdd" min="250" max="2048" step="250"> <div class="value" id="workersValue">Loading...</div>
</div> </div>
<div class="full-width"> <div class="grid-item full">
<label>Expires</label> <div class="label">HDD (MB)</div>
<div id="expires" class="readonly-text">Loading...</div> <div class="value" id="hddValue">Loading...</div>
</div> </div>
<div class="submit-section"> <div class="grid-item full">
<button type="submit" id="submitBtn" disabled>Upgrade</button> <div class="label">Expires</div>
<div class="cost" id="cost">$0.00</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> </div>
</form>
<script> <script>
// Configuration
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/buy"; // <-- easy to configure
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const uuid = params.get("uuid"); 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() { async function loadData() {
if (!uuid) { if (!uuid) {
alert("Missing uuid parameter in URL"); alert("Missing uuid parameter in URL");
return; return;
} }
document.getElementById("uuidText").textContent = uuid;
try { try {
const res = await fetch(webhookUrl, { const res = await fetch(webhookUrl, {
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json"
},
body: JSON.stringify({ uuid }) body: JSON.stringify({ uuid })
}); });
if (!res.ok) throw new Error("Failed to fetch data"); if (!res.ok) throw new Error("Failed to fetch data");
const data = await res.json(); const data = await res.json();
window.originalData = data; document.getElementById("gitValue").textContent = data.git ? "Enabled" : "Disabled";
window.prices = data.prices || { document.getElementById("domainsValue").textContent = data.domains || "-";
git: 10, document.getElementById("backupValue").textContent = data.backupSlots || "-";
domain: 1, document.getElementById("workersValue").textContent = data.workers || "-";
backupSlot: 1, document.getElementById("hddValue").textContent = data.hdd ? `${data.hdd} MB` : "-";
worker: 50, document.getElementById("expiresValue").textContent = data.expires || "-";
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) { } catch (err) {
console.error(err); console.error(err);
alert("Error loading data."); alert("Error loading data.");
} }
} }
function calculateCost() { async function buyProduct(product_id) {
if (!window.originalData || !window.prices) return; if (!uuid) 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 { try {
const res = await fetch(webhookPost, { const res = await fetch(webhook_buy, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, 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");
// Replace page content with confirmation message if (!res.ok) throw new Error("Failed to send purchase request");
document.body.innerHTML = ` document.body.innerHTML = `
<div class="confirmation"> <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 upgrade request has been sent</h2> <h2>✅ Your request has been sent</h2>
<p>Please check your email for confirmation.</p> <p>Please check your email for confirmation.</p>
</div> </div>
`; `;
} catch (err) { } catch (err) {
console.error(err); console.error(err);
alert("Error submitting form."); alert("Error sending request.");
} }
}); }
// Button event listeners with product_id mapping
document.getElementById("upgradeRiseBtn").addEventListener("click", () => buyProduct(4));
document.getElementById("upgradePowerhouseBtn").addEventListener("click", () => buyProduct(5));
document.getElementById("addDomainBtn").addEventListener("click", () => buyProduct(100));
document.getElementById("addBackupsBtn").addEventListener("click", () => buyProduct(101));
loadData(); loadData();
</script> </script>

251
public/upsell.html_ Normal file
View 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. Youll only pay for the remaining duration of your current Odoo contract.
When its 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>