From ccdabd93505057b5eb97f037903ec1755c440efd Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 16 May 2025 15:02:12 +0700 Subject: [PATCH] first commit --- .DS_Store | Bin 0 -> 6148 bytes assets/css/index.css | 134 ++++++++++++++ assets/icons/128.png | Bin 0 -> 4131 bytes assets/icons/16.png | Bin 0 -> 305 bytes assets/icons/32.png | Bin 0 -> 522 bytes background.js | 4 + content.js | 389 +++++++++++++++++++++++++++++++++++++++++ manifest.json | 41 +++++ pages/popup/popup.html | 170 ++++++++++++++++++ 9 files changed, 738 insertions(+) create mode 100644 .DS_Store create mode 100644 assets/css/index.css create mode 100644 assets/icons/128.png create mode 100644 assets/icons/16.png create mode 100644 assets/icons/32.png create mode 100644 background.js create mode 100644 content.js create mode 100644 manifest.json create mode 100644 pages/popup/popup.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..30b1a797bb03722ca01d1c8a2b94899d2c550200 GIT binary patch literal 6148 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 literal 0 HcmV?d00001 diff --git a/assets/icons/16.png b/assets/icons/16.png new file mode 100644 index 0000000000000000000000000000000000000000..46986c5cb4fd5f183d149b1c8f628084040b3474 GIT binary patch literal 305 zcmV-10nYx3P)Px#>q$gGR5(wS(mhH8K@ps0w%Laaov5y77Xf_ezST1e#? zEIda@BatT#4C~D9I^FJ@_szU{Z=k*gxWf)+IKis9JvNnA{C~uD77Sz`BMk9`uM$v7 zPEH^Xc*8~9tOQ))tfjPstmCBwyknyUh)^nLRRT8ginTsS?Gd-)dOeWa8W4x~L6$Y( z9-sK=xXGjhM4JwNhvv8idB$!NNXhB_;`^vj&QEe2k^{};2@bPPM0YMR$9Wp-1au`= zI~u?>#@UYM$*toMz#{ASzmh`^;0Ak`;{)E2)$DuJSIcUyB00000NkvXXu0mjf Db_amO literal 0 HcmV?d00001 diff --git a/assets/icons/32.png b/assets/icons/32.png new file mode 100644 index 0000000000000000000000000000000000000000..b82bd113285a98419f8f633ee98e00603214b260 GIT binary patch literal 522 zcmV+l0`>igP)Px$#7RU!R9HvFmpv~9K@>pGLkI;4y#zmpMktVwkch$`pwK8(@*0gONJK;-6zGIf z;3oH~GFQgF+1;7lS?_h5cX!U&JNM28^dzJNnu19%S^vJnz&#r6T` zz*Oq}3t%opqKrTmTwPoe!s!A&(;plvPy}~LNS00kc~|7tfENu65%7hlCFHA*Os&X& zXTW@@Kqx#<;td$k>F~)w4{+%yIN$o!n$S#414E{B@DW%A4t!}Dakj)Ha1}BI(%;J! z1>|*Si~3|>D`}za2m($CAAw=u5tyh$!bd>3qrg2d9$Ug!pf-sRfjT6bp#rfbp34Zt zlxT$rbelE(HYvvANQJrQD)9eay;}XoF`7l!a?nMB*;BcU%LrGUr8S!m4l` zUpBCHU<`OL?fc={N9-Di1rOS { + // Lấy URL của tab hiện tại + console.log("Current URL:", tab.url); +}); diff --git a/content.js b/content.js new file mode 100644 index 0000000..31be8d2 --- /dev/null +++ b/content.js @@ -0,0 +1,389 @@ +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; + } +} + +function extractModelId(url) { + switch (extractDomain(url)) { + case "https://www.grays.com": { + const match = url.match(/\/lot\/([\d-]+)\//); + return match ? match[1] : null; + } + case "https://www.langtons.com.au": { + const match = url.match(/auc-var-\d+/); + return match[0]; + } + case "https://www.lawsons.com.au": { + const match = url.split("_"); + return match ? match[1] : null; + } + case "https://www.pickles.com.au": { + const model = url.split("/").pop(); + return model ? model : null; + } + } +} + +const showPage = async (pageLink = "pages/popup/popup.html") => { + 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); +}; + +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, + }; + + // Validate required fields + if (!payload.url || payload.max_price === null) { + alert("Please fill out the URL and Max Price fields correctly."); + return; + } + + try { + const response = await fetch(`${CONFIG.API_BASE_URL}/bids`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: key, + }, + body: JSON.stringify(removeFalsyValues(payload)), + }); + + 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, + }; + + // Validate required fields + if (payload.max_price === null) { + alert("Please fill out the URL and Max Price fields correctly."); + return; + } + + 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)), + }); + + const result = await response.json(); + + alert(result.message); + } catch (error) { + alert("Error: " + error.message); + console.error("API Error:", error); + } +} + +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 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 = ` +
+ + +
+ +
+ + +
+ `; + + inputsContainer.insertAdjacentHTML("afterbegin", otherEls); + + // 2. Tạo và chèn Current Price ngay sau #url-col + const currentPriceDiv = document.createElement("div"); + currentPriceDiv.className = "col"; + currentPriceDiv.innerHTML = ` + + + `; + + urlCol.parentNode.insertBefore(currentPriceDiv, urlCol.nextSibling); + + formElements.quantity.value = data?.quantity || 1; + formElements.plusPrice.value = data?.plus_price || 0; +}; + +const showInfo = async (model, formElements) => { + const key = await getKey(); + if (!key) { + showKey(); + return; + } + + try { + const response = await fetch(`${CONFIG.API_BASE_URL}/bids/${model}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: key, + }, + }); + + const result = await response.json(); + + if (!result || result?.status_code !== 200 || !result?.data) { + if (result.status_code !== 404) { + alert(result.message); + } + + PREV_DATA = null; + return null; + } + + formElements.maxPrice.value = result.data.max_price; + + createInfoColumn(result.data, formElements); + + PREV_DATA = result; + return result; + } catch (error) { + alert("Error: " + error.message); + console.error("API Error:", error); + } +}; + +(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"), + form: document.querySelector("#form-bid form"), + }; + + const style = document.createElement("link"); + style.rel = "stylesheet"; + style.href = chrome.runtime.getURL("assets/css/index.css"); + document.head.appendChild(style); + + const script = document.createElement("script"); + script.type = "module"; + script.src = chrome.runtime.getURL("pages/popup/popup.js"); + script.defer = true; + + document.body.appendChild(script); + + handleSaveKey(); + + const currentUrl = window.location.href; + + const model = extractModelId(currentUrl); + + if (!model) return; + + // set url on form + formElements.url.value = currentUrl; + + await showInfo(model, formElements); + handleChangeTitleButton(!!PREV_DATA, formElements); + + formElements.form.addEventListener("submit", (e) => + PREV_DATA + ? handleUpdate(e, formElements, PREV_DATA.data.id) + : handleCreate(e, formElements) + ); +})(); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..b07736c --- /dev/null +++ b/manifest.json @@ -0,0 +1,41 @@ +{ + "manifest_version": 3, + "name": "Bid Extension", + "version": "1.0", + "description": "Bid Extension", + "action": { + "default_popup": "pages/popup/popup.html", + "default_icon": { + "16": "assets/icons/16.png", + "32": "assets/icons/32.png", + "128": "assets/icons/128.png" + } + }, + "background": { + "service_worker": "background.js" + }, + "permissions": ["storage"], + "host_permissions": ["http://*/*", "https://*/*"], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], + "web_accessible_resources": [ + { + "resources": [ + "pages/popup/popup.html", + "assets/css/index.css", + "config.js", + "assets/icons/*" + ], + "matches": [""] + } + ], + "icons": { + "16": "assets/icons/16.png", + "32": "assets/icons/32.png", + "128": "assets/icons/128.png" + } +} diff --git a/pages/popup/popup.html b/pages/popup/popup.html new file mode 100644 index 0000000..68ead10 --- /dev/null +++ b/pages/popup/popup.html @@ -0,0 +1,170 @@ +
+ +
+ +
+ + + + +
+
+

Key

+
+
+ + +
+
+ +
+
+