const CONFIG = { // API_BASE_URL: "http://localhost:4000/api/v1", API_BASE_URL: "https://bids.apactech.io/api/v1", }; let PREV_DATA = null; function removeFalsyValues(obj, excludeKeys = []) { return Object.entries(obj).reduce((acc, [key, value]) => { if (value || excludeKeys.includes(key)) { acc[key] = value; } return acc; }, {}); } function extractDomain(url) { try { const parsedUrl = new URL(url); return parsedUrl.origin; } catch (error) { return null; } } const webs = { grays: "https://www.grays.com", langtons: "https://www.langtons.com.au", lawsons: "https://www.lawsons.com.au", pickles: "https://www.pickles.com.au", allbids: "https://www.allbids.com.au", }; function extractModelId(url) { switch (extractDomain(url)) { case webs.grays: { const match = url.match(/\/lot\/([\d-]+)\//); return match ? match[1] : null; } case webs.langtons: { const match = url.match(/auc-var-\d+/); return match[0]; } case webs.lawsons: { const match = url.split("_"); return match ? match[1] : null; } case webs.pickles: { const model = url.split("/").pop(); return model ? model : null; } case webs.allbids: { const match = url.match(/-(\d+)(?:[\?#]|$)/); return match ? match[1] : null; } } } const isValidUrl = (url) => { return !!Object.keys(webs).find((item) => url.includes(webs[item])); }; const showPage = async (pageLink = "pages/popup/popup.html") => { const url = window.location.href; // sửa lỗi ở đây if (!isValidUrl(url)) return; try { const res = await fetch(chrome.runtime.getURL(pageLink)); const html = await res.text(); const wrapper = document.createElement("div"); wrapper.innerHTML = html; document.body.appendChild(wrapper); } catch (err) { console.error("Failed to load popup page:", err); } }; const getKey = () => { return new Promise((resolve, reject) => { chrome.storage.local.get("key", (result) => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); } else { resolve(result.key || null); } }); }); }; async function handleCreate(event, formElements) { event.preventDefault(); const key = await getKey(); if (!key) { showKey(); return; } const maxPrice = parseFloat(formElements.maxPrice.value); const plusPrice = parseFloat(formElements.plusPrice.value); const quantity = parseInt(formElements.quantity.value, 10); const payload = { url: formElements.url.value.trim(), max_price: isNaN(maxPrice) ? null : maxPrice, plus_price: isNaN(plusPrice) ? null : plusPrice, quantity: isNaN(quantity) ? null : quantity, }; const keys = Object.values(formElements.form) .filter((item) => { return [ "mode_key", "early_tracking_seconds", "arrival_offset_seconds", ].includes(item?.id); }) .reduce((prev, cur) => { prev[cur.id] = cur.value; return prev; }, {}); const earlyTracking = parseInt(keys.early_tracking_seconds, 10); const arrivalOffset = parseInt(keys.arrival_offset_seconds, 10); if (earlyTracking < 600) { alert("Early Tracking Seconds must be at least 600 seconds (10 minutes)."); return; } if (arrivalOffset < 60) { alert("Arrival Offset Seconds must be at least 60 seconds (1 minute)."); return; } // Validate required fields if (!payload.url || payload.max_price === null) { alert("Please fill out the URL and Max Price fields correctly."); return; } const localKeyName = keys["mode_key"]; const newKeys = Object.entries(keys).reduce((prev, [key, value]) => { if (key === "mode_key") { prev[key] = value; } else { prev[`${key}_${localKeyName}`] = Number(value); } return prev; }, {}); let metadata = Object.entries(newKeys).map(([key, value]) => { return { key_name: key, value, }; }); console.log({ newKeys, payload, metadata }); try { const response = await fetch(`${CONFIG.API_BASE_URL}/bids`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: key, }, body: JSON.stringify(removeFalsyValues({ ...payload, metadata })), }); const result = await response.json(); alert(result.message); // showInfo await showInfo(extractModelId(payload.url), formElements); // handleChangeTitleButton handleChangeTitleButton(true, formElements); } catch (error) { alert("Error: " + error.message); console.error("API Error:", error); } } async function handleUpdate(event, formElements, id) { event.preventDefault(); const key = await getKey(); if (!key) { showKey(); return; } const maxPrice = parseFloat(formElements.maxPrice.value); const plusPrice = parseFloat(formElements.plusPrice.value); const quantity = parseInt(formElements.quantity.value, 10); const payload = { max_price: isNaN(maxPrice) ? null : maxPrice, plus_price: isNaN(plusPrice) ? null : plusPrice, quantity: isNaN(quantity) ? null : quantity, }; const keys = Object.values(formElements.form) .filter((item) => { return [ "mode_key", "early_tracking_seconds", "arrival_offset_seconds", ].includes(item?.id); }) .reduce((prev, cur) => { prev[cur.id] = cur.value; return prev; }, {}); const earlyTracking = parseInt(keys.early_tracking_seconds, 10); const arrivalOffset = parseInt(keys.arrival_offset_seconds, 10); if (earlyTracking < 600) { alert("Early Tracking Seconds must be at least 600 seconds (10 minutes)."); return; } if (arrivalOffset < 60) { alert("Arrival Offset Seconds must be at least 60 seconds (1 minute)."); return; } // Validate required fields if (payload.max_price === null) { alert("Please fill out the URL and Max Price fields correctly."); return; } const localKeyName = keys["mode_key"]; const newKeys = Object.entries(keys).reduce((prev, [key, value]) => { if (key === "mode_key") { prev[key] = value; } else { prev[`${key}_${localKeyName}`] = Number(value); } return prev; }, {}); let metadata = []; if ( window.__result.metadata.length > 0 && window.__result.metadata.some((item) => item.key_name === "mode_key") ) { metadata = window.__result.metadata.map((item) => { if (Object.keys(newKeys).includes(item.key_name)) { return { ...item, value: newKeys[item.key_name], }; } return { ...item }; }); } else { metadata = Object.entries(newKeys).map(([key, value]) => { return { key_name: key, value, }; }); } try { const response = await fetch(`${CONFIG.API_BASE_URL}/bids/info/${id}`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: key, }, body: JSON.stringify(removeFalsyValues({ ...payload, metadata })), }); const result = await response.json(); alert(result.message); } catch (error) { alert("Error: " + error.message); console.error("API Error:", error); } finally { await showInfo(); } } const showBid = () => { const formKey = document.getElementById("form-key"); const formBid = document.getElementById("form-bid"); formKey.style.display = "none"; formBid.style.display = "block"; }; const showKey = async () => { chrome.storage.local.set({ key: null }, async () => { console.log("abc"); }); const key = await getKey(); const formKey = document.getElementById("form-key"); const formBid = document.getElementById("form-bid"); const keyEl = document.querySelector("#form-key #key"); formBid.style.display = "none"; formKey.style.display = "block"; if (key && keyEl) { keyEl.value = key; } }; const handleToogle = async () => { const btn = document.getElementById("toggle-bid-extension"); const panel = document.getElementById("bid-extension"); if (btn && panel) { btn.addEventListener("click", async () => { const isHidden = panel.style.display === "none"; panel.style.display = isHidden ? "block" : "none"; if (isHidden) { await handleShowForm(); } }); } else { console.error("Không tìm thấy nút hoặc panel!"); } }; const handleShowForm = async () => { const formBid = document.getElementById("form-bid"); const formKey = document.getElementById("form-key"); const keyBtn = document.getElementById("key-btn"); const currentKey = await getKey(); if (!currentKey) { await showKey(); } else { showBid(); } keyBtn?.addEventListener("click", () => { showKey(); }); }; const handleChangeTitleButton = (result, formElements) => { if (result) { formElements.createBtn.textContent = "Update"; } else { formElements.createBtn.textContent = "Create"; } }; const handleSaveKey = () => { const form = document.querySelector("#form-key form"); if (!form) return; form.addEventListener("submit", async (e) => { e.preventDefault(); const inputKey = form.querySelector("#key"); if (!inputKey) return; const keyValue = inputKey.value.trim(); if (!keyValue) { alert("Please enter a key"); return; } // Lưu vào chrome.storage.local chrome.storage.local.set({ key: keyValue }, async () => { alert("Key saved successfully!"); showBid(); if (!isValidModel()) return; await showInfo(); }); }); }; const isValidModel = () => { const currentUrl = window.location.href; const model = extractModelId(currentUrl); return !!model; }; const renderValueModeMetadata = (data, formElements, mode = null) => { const early_tracking_seconds = getEarlyTrackingSeconds(data, mode); const arrival_offset_seconds = getArrivalOffsetSeconds(data, mode); formElements.metadataMode.arrival_offset_seconds.value = arrival_offset_seconds; formElements.metadataMode.early_tracking_seconds.value = early_tracking_seconds; return { early_tracking_seconds, arrival_offset_seconds }; }; function formatTimeFromMinutes(minutes) { // Tính ngày, giờ, phút từ số phút const days = Math.floor(minutes / (60 * 24)); const hours = Math.floor((minutes % (60 * 24)) / 60); const mins = minutes % 60; let result = ""; if (days > 0) result += `${days} ${days > 1 ? "days" : "day"} `; if (hours > 0) result += `${hours} ${hours > 1 ? "hours" : "hour"} `; if (mins > 0 || result === "") result += `${mins} minutes`; return result.trim(); } function toReadableLabel(input) { return input .split("_") // Tách chuỗi theo dấu gạch dưới .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) // Viết hoa chữ cái đầu .join(" "); // Nối lại bằng khoảng trắng } const setMinusLabel = (input, minutes = null) => { const label = document.querySelector(`[for="${input?.id}"]`); const text = toReadableLabel(input.id); label.textContent = `${text} ${ Number(input.value) ? `(${formatTimeFromMinutes(minutes || Number(input.value) || 300 / 60)})` : "" }`; }; const renderMetadaMode = (data, formElements, mode = null) => { const mode_key = mode ? mode : getMode(data); formElements.metadataModeBox.style.display = "flex"; renderValueModeMetadata(data, formElements, mode_key); Object.values(formElements.form).forEach((item) => { if (item?.id === "mode_key") { item.value = mode_key; item.addEventListener("change", (e) => { renderValueModeMetadata(data, formElements, e.target.value); Object.values(formElements.metadataMode).forEach((i) => { setMinusLabel(i); }); }); } }); }; const createInfoColumn = (data, formElements) => { const inputsContainer = document.querySelector("#bid-extension .inputs"); const urlCol = document.querySelector("#url-col"); if (!inputsContainer || !urlCol) return; // 1. Thêm ID và Name vào đầu inputsContainer // const otherEls = ` //