Skip to content

Commit e50f2a1

Browse files
committed
Add initial extension logic
1 parent d48df4a commit e50f2a1

4 files changed

Lines changed: 227 additions & 0 deletions

File tree

manifest.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Qantas Status Credit Calculator",
4+
"version": "1.0",
5+
"description": "Calculates $ per status credit on Qantas booking pages",
6+
"permissions": ["activeTab", "scripting", "storage"],
7+
"action": {
8+
"default_popup": "src/popup.html"
9+
},
10+
"content_scripts": [
11+
{
12+
"matches": ["*://book.qantas.com/*"],
13+
"js": ["src/content.js"]
14+
}
15+
]
16+
}

src/content.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// This code is rushed more as a proof of concept than a finished product.
2+
// It is not optimized and could be improved in many ways.
3+
// It is also not tested on all possible cases and could break in some situations.
4+
5+
const informationRowSelector = ".row.information-row";
6+
const fareCellDetailsSelector = ".fare-cell-details-information";
7+
const priceSelector = ".amount-grp.fare-value";
8+
const statusCreditSelector =
9+
".e2e-fCondition-statuscredits .e2e-statuscredits-amount";
10+
11+
// Check if double status credits are enabled from the popover.js
12+
let doubleStatusEnabled = false;
13+
chrome.storage.local.get(["doubleStatus"], (result) => {
14+
doubleStatusEnabled = result.doubleStatus || false;
15+
});
16+
17+
// Listen for changes to the dom for when flight information is expanded showing the status credits
18+
const observer = new MutationObserver((mutations) => {
19+
mutations.forEach((mutation) => {
20+
mutation.addedNodes.forEach((node) => {
21+
if (node.nodeType !== Node.ELEMENT_NODE) return;
22+
if (node.matches(informationRowSelector) == false) return;
23+
addInformationToFlight(node);
24+
});
25+
});
26+
});
27+
28+
// Logic for querying the status credit, and price doms.
29+
function addInformationToFlight(node) {
30+
const detailItems = node.querySelectorAll(fareCellDetailsSelector);
31+
32+
// Get all prices for this row
33+
const container = node.closest("upsell-itinerary-avail");
34+
if (!container) return;
35+
36+
const prices = Array.from(container.querySelectorAll(priceSelector));
37+
// Process each details item
38+
detailItems.forEach((detailItem, index) => {
39+
const statusElement = detailItem.querySelector(statusCreditSelector);
40+
if (statusElement == false && prices[index] == false) return;
41+
42+
let price = getPrice(prices[index]);
43+
let statusCredits = parseInt(statusElement.innerText);
44+
// Check if statusCredits is a valid number
45+
if (isNaN(statusCredits)) return;
46+
47+
if (doubleStatusEnabled) {
48+
statusCredits *= 2;
49+
updateDoubleStatusHTML(statusCredits, statusElement);
50+
}
51+
52+
if (!isNaN(price) && statusCredits) {
53+
setConversionHTML(statusElement, price, statusCredits);
54+
}
55+
});
56+
}
57+
58+
// Show the final calculation as a new div under the original status credits centre aligned.
59+
function setConversionHTML(statusElement, price, statusCredits) {
60+
const dollarPerCredit = (price / statusCredits).toFixed(2);
61+
62+
let calculationDiv = document.createElement("div");
63+
calculationDiv.style.fontSize = "14px";
64+
calculationDiv.style.fontWeight = "bold";
65+
calculationDiv.style.color = "#666";
66+
calculationDiv.textContent = `$${dollarPerCredit}/SC`;
67+
68+
statusElement.parentNode.insertBefore(
69+
calculationDiv,
70+
statusElement.nextSibling
71+
);
72+
}
73+
74+
// If double status credits are enabled then show some UI changes
75+
function updateDoubleStatusHTML(statusCredits, statusElement) {
76+
// Create a span for the old value with strikethrough
77+
const oldSpan = document.createElement("span");
78+
oldSpan.style.textDecoration = "line-through";
79+
oldSpan.textContent = statusElement.innerText;
80+
81+
// Create a new span for the doubled value
82+
const doubledSpan = document.createElement("span");
83+
doubledSpan.style.color = "red";
84+
doubledSpan.textContent = ` ${statusCredits}`;
85+
86+
// Clear the current content of statusElement
87+
statusElement.innerHTML = "";
88+
89+
// Append the old and new spans to the statusElement
90+
statusElement.appendChild(oldSpan);
91+
statusElement.appendChild(doubledSpan);
92+
}
93+
94+
// Get the price of a flight from the element, factoring in for flights that have no pricing.
95+
function getPrice(node) {
96+
// Check if no seats are available
97+
if (node.querySelector(".no-seat")) {
98+
return NaN;
99+
}
100+
// Get the price of the flight
101+
const price = node.querySelector(".amount.cash").innerText;
102+
return parseFloat(price.replace(/[^0-9.]/g, ""));
103+
}
104+
105+
// Observe the document for changes
106+
observer.observe(document, { childList: true, subtree: true });

src/popup.html

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<!-- popup.html -->
2+
<!DOCTYPE html>
3+
<html>
4+
5+
<head>
6+
<style>
7+
body {
8+
width: 200px;
9+
padding: 16px;
10+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
11+
}
12+
13+
.toggle-container {
14+
display: flex;
15+
align-items: center;
16+
gap: 12px;
17+
}
18+
19+
.switch {
20+
position: relative;
21+
display: inline-block;
22+
width: 48px;
23+
height: 24px;
24+
}
25+
26+
.switch input {
27+
opacity: 0;
28+
width: 0;
29+
height: 0;
30+
}
31+
32+
.slider {
33+
position: absolute;
34+
cursor: pointer;
35+
top: 0;
36+
left: 0;
37+
right: 0;
38+
bottom: 0;
39+
background-color: #ccc;
40+
transition: .4s;
41+
border-radius: 24px;
42+
}
43+
44+
.slider:before {
45+
position: absolute;
46+
content: "";
47+
height: 16px;
48+
width: 16px;
49+
left: 4px;
50+
bottom: 4px;
51+
background-color: white;
52+
transition: .4s;
53+
border-radius: 50%;
54+
}
55+
56+
input:checked+.slider {
57+
background-color: #E61F25;
58+
}
59+
60+
input:checked+.slider:before {
61+
transform: translateX(24px);
62+
}
63+
64+
.label {
65+
font-size: 14px;
66+
font-weight: 500;
67+
color: #333;
68+
}
69+
</style>
70+
</head>
71+
72+
<body>
73+
<div class="toggle-container">
74+
<label class="switch">
75+
<input type="checkbox" id="doubleStatus">
76+
<span class="slider"></span>
77+
</label>
78+
<span class="label">Double Status Credits</span>
79+
</div>
80+
<script src="./popup.js"></script>
81+
</body>
82+
83+
</html>

src/popup.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// popup.js
2+
document.addEventListener("DOMContentLoaded", () => {
3+
const toggle = document.getElementById("doubleStatus");
4+
5+
// Load saved state
6+
chrome.storage.local.get(["doubleStatus"], (result) => {
7+
toggle.checked = result.doubleStatus || false;
8+
});
9+
10+
// Save state when changed
11+
toggle.addEventListener("change", (e) => {
12+
chrome.storage.local.set({ doubleStatus: e.target.checked });
13+
14+
// Notify content script of change
15+
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
16+
chrome.tabs.sendMessage(tabs[0].id, {
17+
action: "updateDoubleStatus",
18+
doubleStatus: e.target.checked,
19+
});
20+
});
21+
});
22+
});

0 commit comments

Comments
 (0)