prology-landingpage/assets/js/modals.js

538 lines
21 KiB
JavaScript

(() => {
// ── API Config ──
const API_ENDPOINT = window.PROLOGY_CONFIG.API_URL;
// ── Shared submit function ──
async function submitToAPI(payload, btnEl, onSuccess) {
btnEl.disabled = true;
const origHTML = btnEl.innerHTML;
btnEl.innerHTML = "<span>Sending...</span>";
const restore = () => {
btnEl.innerHTML = origHTML;
btnEl.disabled = false;
};
try {
const res = await fetch(API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
},
body: JSON.stringify({
...payload,
source: window.location.href,
ts: Date.now(),
nonce: Math.random().toString(36).slice(2),
}),
});
const data = await res.json();
if (res.ok && data.ok) {
onSuccess(restore);
} else {
const msg =
data.error || "Something went wrong. Please try again.";
btnEl.innerHTML = "<span>&#x26A0; " + msg + "</span>";
setTimeout(restore, 3000);
}
} catch (e) {
btnEl.innerHTML =
"<span>&#x26A0; Network error. Please try again.</span>";
setTimeout(restore, 3000);
}
}
// ── Shared Shadow DOM CSS ──
const MODAL_CSS = `
:host { display: none; }
:host(.open) { display: block; }
.overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.75);
backdrop-filter: blur(12px);
z-index: 9999;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 20px;
overflow-y: auto;
}
.box {
background: linear-gradient(145deg, #2A3B54, #1E293B);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 24px;
width: 100%;
max-width: 580px;
max-height: calc(100vh - 40px);
overflow-y: auto;
padding: 36px;
margin: auto;
position: relative;
box-shadow: 0 40px 100px rgba(0,0,0,0.8);
font-family: 'Plus Jakarta Sans', sans-serif;
color: white;
box-sizing: border-box;
}
.badge {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #1A73E8;
background: rgba(26,115,232,0.15);
padding: 4px 12px;
border-radius: 4px;
margin-bottom: 10px;
}
h3 { font-size: 1.75rem; font-weight: 800; margin: 0 0 8px; color: white; }
.intro { font-size: 0.95rem; color: #94A3B8; line-height: 1.6; margin-bottom: 28px; }
.close {
position: absolute; top: 20px; right: 20px;
background: none; border: none; color: #94A3B8;
font-size: 1.4rem; cursor: pointer; line-height: 1;
padding: 4px 8px; border-radius: 6px; transition: color 0.2s;
}
.close:hover { color: white; }
.field { margin-bottom: 20px; }
.field-row { display: flex; gap: 16px; margin-bottom: 20px; }
.field-row .field { flex: 1; margin-bottom: 0; }
label {
display: block; font-size: 0.85rem; font-weight: 600;
color: #E2E8F0; margin-bottom: 8px;
}
.req { color: #f87171; font-size: 0.8em; margin-left: 2px; font-weight: 700; }
input, textarea, select {
width: 100%; box-sizing: border-box;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.12);
border-radius: 12px;
padding: 12px 16px;
color: white;
font-family: 'Plus Jakarta Sans', sans-serif;
font-size: 0.95rem;
outline: none;
transition: border-color 0.2s;
-webkit-appearance: none;
appearance: none;
}
input:focus, textarea:focus, select:focus {
border-color: #1A73E8;
background: rgba(255,255,255,0.09);
}
input::placeholder, textarea::placeholder { color: #64748B; }
textarea { min-height: 120px; resize: vertical; }
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2394A3B8' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
padding-right: 36px;
cursor: pointer;
}
select option { background: #1E293B; color: white; }
.btn-submit {
width: 100%; padding: 16px;
background: #1A73E8; color: white;
border: none; border-radius: 14px;
font-size: 1rem; font-weight: 700;
cursor: pointer; display: flex;
align-items: center; justify-content: center;
gap: 10px; transition: background 0.2s, transform 0.2s;
font-family: 'Plus Jakarta Sans', sans-serif;
margin-top: 8px;
}
.btn-submit:hover { background: #174ea6; transform: translateY(-2px); }
.btn-submit svg { flex-shrink: 0; }
.btn-submit:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none !important;
}
`;
const ARROW_SVG = `<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/></svg>`;
// ── Base class ──
class ProloModal extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
open(prefillEmail = "") {
this.classList.add("open");
document.body.style.overflow = "hidden";
if (prefillEmail) {
const emailInput = this.shadowRoot.querySelector(
'input[type="email"]',
);
if (emailInput) emailInput.value = prefillEmail;
}
requestAnimationFrame(() => {
const first = this.shadowRoot.querySelector("input, textarea");
if (first) first.focus();
});
}
close() {
this.classList.remove("open");
document.body.style.overflow = "";
const form = this.shadowRoot.querySelector("form");
if (form) form.reset();
}
_bindClose() {
this.shadowRoot
.querySelector(".close")
.addEventListener("click", () => this.close());
this.shadowRoot
.querySelector(".overlay")
.addEventListener("click", (e) => {
if (e.target.classList.contains("overlay")) this.close();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && this.classList.contains("open"))
this.close();
});
}
}
// ── 1. Sell / Valuation Modal ──
class PrologySellModal extends ProloModal {
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>${MODAL_CSS}</style>
<div class="overlay">
<div class="box">
<button class="close">&#x2715;</button>
<span class="badge">Asset Recovery</span>
<h3>Get a Valuation</h3>
<p class="intro">Tell us what hardware you have. Our team will provide a competitive buy-back offer within 4 business hours.</p>
<form id="f">
<div class="field">
<label>Contact Email <span class="req">*</span></label>
<input type="email" name="email" placeholder="name@company.com" required>
</div>
<div class="field">
<label>Inventory List / Part Numbers <span class="req">*</span></label>
<textarea name="inventory" placeholder="e.g. 20x WS-C3850-24P-S, 10x Nexus 9k..." required></textarea>
</div>
<div class="field">
<label>Condition</label>
<select name="condition">
<option value="used">Used / Working</option>
<option value="nib">New In Box</option>
<option value="no-power">Decommissioned / Faulty</option>
</select>
</div>
<button type="submit" class="btn-submit">
<span>Request Buy-Back Quote</span>${ARROW_SVG}
</button>
</form>
</div>
</div>`;
this._bindClose();
this.shadowRoot
.querySelector("form")
.addEventListener("submit", (e) => {
e.preventDefault();
const sr = this.shadowRoot;
const email = sr.querySelector('[name="email"]').value;
if (!email) return;
const inventory = sr.querySelector('[name="inventory"]').value;
const condition = sr.querySelector('[name="condition"]').value;
const btn = sr.querySelector(".btn-submit");
submitToAPI(
{
email,
message: `[Asset Recovery]\nCondition: ${condition}\n\n${inventory || "(no inventory listed)"}`,
channel: "sell",
},
btn,
(restore) => {
btn.innerHTML = "<span>&#x2713; Request Sent!</span>";
setTimeout(() => {
this.close();
restore();
}, 1800);
},
);
});
}
}
// ── 2. Source / Quote Modal ──
class PrologySourceModal extends ProloModal {
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>${MODAL_CSS}</style>
<div class="overlay">
<div class="box">
<button class="close">&#x2715;</button>
<span class="badge">Hardware Sourcing</span>
<h3>Request a Quote</h3>
<p class="intro">Looking for specific equipment? We source authorized and refurbished Cisco, HP, and more.</p>
<form id="f">
<div class="field">
<label>Contact Email <span class="req">*</span></label>
<input type="email" name="email" placeholder="name@company.com" required>
</div>
<div class="field">
<label>Specific Models / Requirements</label>
<textarea name="parts" placeholder="e.g. 5x C9200L-24T-4G, 2x ASA5506-X..."></textarea>
</div>
<div class="field">
<label>Required Timing</label>
<select name="timing">
<option value="urgent">Urgent (ASAP)</option>
<option value="1week">Within 1 Week</option>
<option value="budgeting">Budgeting / Future Project</option>
</select>
</div>
<button type="submit" class="btn-submit">
<span>Check Availability &amp; Price</span>${ARROW_SVG}
</button>
</form>
</div>
</div>`;
this._bindClose();
this.shadowRoot
.querySelector("form")
.addEventListener("submit", (e) => {
e.preventDefault();
const sr = this.shadowRoot;
const email = sr.querySelector('[name="email"]').value;
if (!email) return;
const parts = sr.querySelector('[name="parts"]').value;
const timing = sr.querySelector('[name="timing"]').value;
const btn = sr.querySelector(".btn-submit");
submitToAPI(
{
email,
message: `[Hardware Sourcing]\nTiming: ${timing}\n\n${parts || "(no parts listed)"}`,
channel: "source",
},
btn,
(restore) => {
btn.innerHTML = "<span>&#x2713; Request Sent!</span>";
setTimeout(() => {
this.close();
restore();
}, 1800);
},
);
});
}
}
// ── 3. Quote Modal (Request a Quote from Who We Serve) ──
class PrologyQuoteModal extends ProloModal {
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>${MODAL_CSS}</style>
<div class="overlay">
<div class="box">
<button class="close">&#x2715;</button>
<span class="badge">IT Procurement</span>
<h3>Request a Quote</h3>
<p class="intro">Please provide details about your project or hardware requirements. Our team will verify stock and get back to you within 24 hours.</p>
<form id="f">
<div class="field-row">
<div class="field"><label>Name <span class="req">*</span></label><input type="text" name="name" placeholder="John Doe" required></div>
<div class="field"><label>Email <span class="req">*</span></label><input type="email" name="email" placeholder="john@company.com" required></div>
</div>
<div class="field-row">
<div class="field"><label>Company (Optional)</label><input type="text" name="company" placeholder="Company Ltd."></div>
<div class="field"><label>Phone Number <span class="req">*</span></label><input type="tel" name="phone" placeholder="+1 234 567 8900" required></div>
</div>
<div class="field">
<label>Equipment Requirements <span class="req">*</span></label>
<textarea name="requirements" placeholder="e.g. 10x Cisco C9200L-48P-4G..." required></textarea>
</div>
<div class="field">
<label>Shipping Destination</label>
<input type="text" name="destination" placeholder="City, Country (Optional)">
</div>
<button type="submit" class="btn-submit"><span>Submit Request</span>${ARROW_SVG}</button>
</form>
</div>
</div>`;
this._bindClose();
this.shadowRoot
.querySelector("form")
.addEventListener("submit", (e) => {
e.preventDefault();
const sr = this.shadowRoot;
const name = sr.querySelector('[name="name"]').value;
const email = sr.querySelector('[name="email"]').value;
const phone = sr.querySelector('[name="phone"]').value;
const company = sr.querySelector('[name="company"]').value;
const requirements = sr.querySelector(
'[name="requirements"]',
).value;
const destination = sr.querySelector(
'[name="destination"]',
).value;
const btn = sr.querySelector(".btn-submit");
submitToAPI(
{
name,
email,
phone,
message: `[Quote Request]\nCompany: ${company || "N/A"}\nDestination: ${destination || "N/A"}\n\n${requirements}`,
channel: "quote",
},
btn,
(restore) => {
btn.innerHTML = "<span>&#x2713; Request Sent!</span>";
setTimeout(() => {
this.close();
restore();
}, 1800);
},
);
});
}
}
// ── 4. Enquire Modal (Gov & Corp) ──
class PrologyEnquireModal extends ProloModal {
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>${MODAL_CSS}</style>
<div class="overlay">
<div class="box">
<button class="close">&#x2715;</button>
<span class="badge">Government &amp; Corporate</span>
<h3>Corporate Enquiry</h3>
<p class="intro">Get in touch with our specialized team to establish customized procurement workflows for your organization.</p>
<form id="f">
<div class="field-row">
<div class="field"><label>Name <span class="req">*</span></label><input type="text" name="name" placeholder="Jane Doe" required></div>
<div class="field"><label>Work Email <span class="req">*</span></label><input type="email" name="email" placeholder="jane@agency.gov" required></div>
</div>
<div class="field-row">
<div class="field"><label>Organization / Agency <span class="req">*</span></label><input type="text" name="org" placeholder="Organization Name" required></div>
<div class="field"><label>Contact Number <span class="req">*</span></label><input type="tel" name="phone" placeholder="+1 234 567 8900" required></div>
</div>
<div class="field">
<label>Enquiry Details <span class="req">*</span></label>
<textarea name="details" placeholder="Please describe your procurement needs..." required></textarea>
</div>
<button type="submit" class="btn-submit"><span>Submit Enquiry</span>${ARROW_SVG}</button>
</form>
</div>
</div>`;
this._bindClose();
this.shadowRoot
.querySelector("form")
.addEventListener("submit", (e) => {
e.preventDefault();
const sr = this.shadowRoot;
const name = sr.querySelector('[name="name"]').value;
const email = sr.querySelector('[name="email"]').value;
const phone = sr.querySelector('[name="phone"]').value;
const org = sr.querySelector('[name="org"]').value;
const details = sr.querySelector('[name="details"]').value;
const btn = sr.querySelector(".btn-submit");
submitToAPI(
{
name,
email,
phone,
message: `[Corporate Enquiry]\nOrganization: ${org}\n\n${details}`,
channel: "enquire",
},
btn,
(restore) => {
btn.innerHTML = "<span>&#x2713; Enquiry Sent!</span>";
setTimeout(() => {
this.close();
restore();
}, 1800);
},
);
});
}
}
customElements.define("prology-sell-modal", PrologySellModal);
customElements.define("prology-source-modal", PrologySourceModal);
customElements.define("prology-quote-modal", PrologyQuoteModal);
customElements.define("prology-enquire-modal", PrologyEnquireModal);
// ── Global API (backward compatible with onclick calls) ──
window.openValuationModal = (email = "") =>
document.getElementById("wc-sell-modal").open(email);
window.openSourcingModal = () =>
document.getElementById("wc-source-modal").open();
window.openQuoteModal = () =>
document.getElementById("wc-quote-modal").open();
window.openEnquireModal = () =>
document.getElementById("wc-enquire-modal").open();
// ── Store picker modal ──
const cfg = window.PROLOGY_CONFIG || {};
// Builds a store URL, optionally appending a category slug.
// e.g. ("https://prology.net/au", "compute") → "https://prology.net/au/compute"
const buildStoreUrl = (base, slug) => {
if (!base) return "";
const trimmed = base.replace(/\/+$/, "");
return slug ? `${trimmed}/${slug}` : base;
};
const applyStoreHrefs = (slug) => {
document.querySelectorAll('[data-store="au"]').forEach((el) => {
el.setAttribute("href", buildStoreUrl(cfg.STORE_AU_URL, slug));
});
document.querySelectorAll('[data-store="us"]').forEach((el) => {
el.setAttribute("href", buildStoreUrl(cfg.STORE_US_URL, slug));
});
};
window.openStoreModal = function (categorySlug) {
applyStoreHrefs(categorySlug || "");
const overlay = document.getElementById("store-modal-overlay");
if (overlay) overlay.classList.add("open");
};
window.closeStoreModal = function () {
const overlay = document.getElementById("store-modal-overlay");
if (overlay) overlay.classList.remove("open");
};
document
.getElementById("store-modal-overlay")
.addEventListener("click", function (e) {
if (e.target === this) closeStoreModal();
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeStoreModal();
});
// Initial wire-up (no category)
applyStoreHrefs("");
// Intercept any /shop link → open store picker instead
document.addEventListener("click", function (e) {
const a = e.target.closest('a[href="/shop"]');
if (!a) return;
e.preventDefault();
window.openStoreModal();
});
window.goToSellStep2 = function () {
const emailInput = document.getElementById("sell-email-init");
const email = emailInput ? emailInput.value : "";
if (!email || !email.includes("@")) {
alert("Please input a valid email address.");
return;
}
window.openValuationModal(email);
};
})();