commit ccdabd93505057b5eb97f037903ec1755c440efd Author: Admin Date: Fri May 16 15:02:12 2025 +0700 first commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..30b1a79 Binary files /dev/null and b/.DS_Store differ diff --git a/assets/css/index.css b/assets/css/index.css new file mode 100644 index 0000000..68bea98 --- /dev/null +++ b/assets/css/index.css @@ -0,0 +1,134 @@ +/* #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; +} + +#bid-extension .container { + display: flex; + flex-direction: column; + gap: 10px; +} + +#bid-extension h2 { + text-align: center; + margin-bottom: 12px; + font-size: 22px; + color: #ffffff; +} + +#bid-extension label { + font-size: 13px; + margin-bottom: 2px; +} + +#bid-extension input, +#bid-extension textarea { + padding: 8px; + background-color: #1e1e1e; + color: #ffffff; + border: 1px solid #333; + border-radius: 6px; + font-size: 14px; + width: 100%; + box-sizing: border-box; +} + +#bid-extension input:focus, +#bid-extension textarea:focus { + border-color: #4a90e2; + outline: none; +} + +#bid-extension .row { + display: flex; + gap: 10px; +} + +#bid-extension .col { + flex: 1; + display: flex; + flex-direction: column; +} + +#bid-extension .inputs .col { + padding-left: 0px; + padding-right: 0px; +} + +#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; + cursor: pointer; + transition: background 0.3s ease; +} + +#bid-extension button:hover { + background: linear-gradient(to right, #3a78c2, #2d5faa); +} + +#bid-extension #errorMessage { + margin-top: 2px; + font-size: 11px; +} + +#bid-extension .wrapper { + position: relative; +} + +#bid-extension .key-container { + position: absolute; + top: 20px; + left: 20px; +} + +#bid-extension .key-container a { + color: #ffffff; + font-size: 14px; + padding: 4px 10px; + background: linear-gradient(to right, #4a90e2, #357abd); + border-radius: 6px; + text-decoration: none; + display: inline-block; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); +} + +#bid-extension .key-container a:hover { + background: linear-gradient(to right, #3a78c2, #2d5faa); + box-shadow: 0 6px 10px rgba(0, 0, 0, 0.3); + transform: translateY(-2px); +} + +#bid-extension .inputs { + display: flex; + flex-direction: column; + gap: 8px; +} + +#bid-extension svg { + width: 14px; + height: 14px; +} + +#toggle-bid-extension svg { + width: 20px; + height: 20px; +} diff --git a/assets/icons/128.png b/assets/icons/128.png new file mode 100644 index 0000000..c7de32f Binary files /dev/null and b/assets/icons/128.png differ diff --git a/assets/icons/16.png b/assets/icons/16.png new file mode 100644 index 0000000..46986c5 Binary files /dev/null and b/assets/icons/16.png differ diff --git a/assets/icons/32.png b/assets/icons/32.png new file mode 100644 index 0000000..b82bd11 Binary files /dev/null and b/assets/icons/32.png differ diff --git a/background.js b/background.js new file mode 100644 index 0000000..9e69979 --- /dev/null +++ b/background.js @@ -0,0 +1,4 @@ +chrome.action.onClicked.addListener((tab) => { + // 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

+
+
+ + +
+
+ +
+
+