diff --git a/assets/css/index.css b/assets/css/index.css index 97743ce..af656ae 100644 --- a/assets/css/index.css +++ b/assets/css/index.css @@ -1,19 +1,10 @@ -/* #bid-extension body { - margin: 0; - padding: 0; - background-color: #121212; - color: #e0e0e0; - font-family: 'Segoe UI', Tahoma, sans-serif; - width: 320px; -} */ - #bid-extension { margin: 0; padding: 0; background-color: #121212; color: #e0e0e0; font-family: "Segoe UI", Tahoma, sans-serif; - width: 320px; + width: 500px; } #bid-extension .container { @@ -36,8 +27,9 @@ } #bid-extension input, +#bid-extension select, #bid-extension textarea { - padding: 8px; + padding: 4px 8px; background-color: #1e1e1e; color: #ffffff; border: 1px solid #333; @@ -45,9 +37,11 @@ font-size: 14px; width: 100%; box-sizing: border-box; + height: 32px; } #bid-extension input:focus, +#bid-extension select:focus, #bid-extension textarea:focus { border-color: #4a90e2; outline: none; @@ -71,12 +65,12 @@ #bid-extension button { margin-top: 10px; - padding: 10px; background: linear-gradient(to right, #4a90e2, #357abd); color: white; border: none; border-radius: 6px; font-size: 15px; + height: 36px; cursor: pointer; transition: background 0.3s ease; } @@ -129,6 +123,12 @@ height: 14px; } +#bid-extension .sub-col { + display: flex; + align-items: center; + gap: 10px; +} + #toggle-bid-extension svg { width: 20px; height: 20px; diff --git a/content.js b/content.js index 296b74e..c697469 100644 --- a/content.js +++ b/content.js @@ -157,12 +157,73 @@ async function handleUpdate(event, formElements, id) { 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", @@ -170,7 +231,7 @@ async function handleUpdate(event, formElements, id) { "Content-Type": "application/json", Authorization: key, }, - body: JSON.stringify(removeFalsyValues(payload)), + body: JSON.stringify(removeFalsyValues({ ...payload, metadata })), }); const result = await response.json(); @@ -179,6 +240,8 @@ async function handleUpdate(event, formElements, id) { } catch (error) { alert("Error: " + error.message); console.error("API Error:", error); + } finally { + await showInfo(); } } @@ -289,6 +352,81 @@ const isValidModel = () => { 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} (${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); + }); + }); + } + }); + + Object.values(formElements.metadataMode).forEach((item) => { + setMinusLabel(item); + + item.addEventListener("input", (e) => { + setMinusLabel(item, Number(e.target.value)); + }); + }); +}; + const createInfoColumn = (data, formElements) => { const inputsContainer = document.querySelector("#bid-extension .inputs"); const urlCol = document.querySelector("#url-col"); @@ -297,9 +435,19 @@ const createInfoColumn = (data, formElements) => { // 1. Thêm ID và Name vào đầu inputsContainer const otherEls = ` -
- - +
+
+ + +
+ +
+ + +
@@ -324,6 +472,65 @@ const createInfoColumn = (data, formElements) => { formElements.quantity.value = data?.quantity || 1; formElements.plusPrice.value = data?.plus_price || 0; + + renderMetadaMode(data, formElements); +}; + +const showCompetitor = () => { + const script = document.createElement("script"); + script.src = chrome.runtime.getURL("injected.js"); + script.onload = function () { + this.remove(); + }; + (document.head || document.documentElement).appendChild(script); + + // Nghe dữ liệu trả về + window.addEventListener("message", function (event) { + if (event.source !== window) return; + if (event.data && event.data.source === "my-extension") { + const bidHistory = event.data?.bidHistory || []; + const maxProxy = bidHistory.reduce((max, curr) => { + return curr.proxyamount > max.proxyamount ? curr : max; + }, bidHistory[0]); + + console.log({ bidHistory }); + + const competitorEl = document.getElementById("competitor-max-bid-col"); + const competitorInput = document.querySelector( + "#competitor-max-bid-col input" + ); + + competitorEl.style.display = "block"; + + competitorInput.value = maxProxy?.proxyamount || "None"; + } + }); +}; + +const getMode = (data) => { + return ( + data.metadata.find((item) => item.key_name === "mode_key")?.value || "live" + ); +}; + +const getEarlyTrackingSeconds = (data, outsiteMode = null) => { + const mode = outsiteMode ? outsiteMode : getMode(data); + + return ( + data.metadata.find( + (item) => item.key_name === `early_tracking_seconds_${mode}` + )?.value || data.web_bid.early_tracking_seconds + ); +}; + +const getArrivalOffsetSeconds = (data, outsiteMode = null) => { + const mode = outsiteMode ? outsiteMode : getMode(data); + + return ( + data.metadata.find( + (item) => item.key_name === `arrival_offset_seconds_${mode}` + )?.value || data.web_bid.arrival_offset_seconds + ); }; const showInfo = async (model, formElements) => { @@ -353,6 +560,8 @@ const showInfo = async (model, formElements) => { return null; } + window["__result"] = result.data; + formElements.maxPrice.value = result.data.max_price; createInfoColumn(result.data, formElements); @@ -368,13 +577,22 @@ const showInfo = async (model, formElements) => { (async () => { await showPage(); handleToogle(); - const formElements = { url: document.querySelector("#form-bid #url"), maxPrice: document.querySelector("#form-bid #maxPrice"), plusPrice: document.querySelector("#form-bid #plusPrice"), quantity: document.querySelector("#form-bid #quantity"), createBtn: document.querySelector("#form-bid #createBtn"), + modeKey: document.querySelector("#form-bid #mode_key"), + metadataModeBox: document.querySelector("#form-bid #metadataMode"), + metadataMode: { + arrival_offset_seconds: document.querySelector( + "#form-bid #arrival_offset_seconds" + ), + early_tracking_seconds: document.querySelector( + "#form-bid #early_tracking_seconds" + ), + }, form: document.querySelector("#form-bid form"), }; @@ -391,6 +609,12 @@ const showInfo = async (model, formElements) => { if (!model) return; + switch (extractDomain(currentUrl)) { + case webs.allbids: { + showCompetitor(); + } + } + // set url on form formElements.url.value = currentUrl; diff --git a/injected.js b/injected.js new file mode 100644 index 0000000..67f7769 --- /dev/null +++ b/injected.js @@ -0,0 +1,16 @@ +(function () { + let data = null; + const elements = document.querySelectorAll(".ng-scope"); + for (let i = 0; i < elements.length; i++) { + const scope = window.angular?.element(elements[i]).scope(); + if (scope && scope.auction) { + console.log("Found at index:", i, "Auction:", scope.bidHistory); + data = scope.bidHistory; + break; + } + } + + if (data) { + window.postMessage({ source: "my-extension", bidHistory: data }, "*"); + } +})(); diff --git a/manifest.json b/manifest.json index b345cce..9136dbf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Bid Extension", - "version": "1.2", + "version": "1.3", "description": "Bid Extension", "action": { "default_popup": "pages/popup/popup.html", @@ -28,7 +28,8 @@ "pages/popup/popup.html", "assets/css/index.css", "config.js", - "assets/icons/*" + "assets/icons/*", + "injected.js" ], "matches": [""] } diff --git a/pages/popup/popup.html b/pages/popup/popup.html index 85744f7..4f5e323 100644 --- a/pages/popup/popup.html +++ b/pages/popup/popup.html @@ -6,6 +6,7 @@ id="toggle-bid-extension" style=" padding: 12px 20px; + max-height: 44px; background: #2c2f36; color: #ffffff; border: none; @@ -96,7 +97,7 @@ border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); padding: 20px; - width: 320px; + width: 500px; " > @@ -104,25 +105,72 @@

Bid

+ +
-
+
-
- - +
+
+ + +
+ +
+ + +
-
- - + + +