Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Changes #12

Merged
merged 6 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Extension-React/public/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({ coupons: [] });
});
return true;
} else if (request.action === "setBadgeText") {
chrome.action.setBadgeText({
tabId: sender.tab.id,
text: request.text,
});
}

if (request.action === "openPopup") {
Expand Down
77 changes: 55 additions & 22 deletions Extension-React/public/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
*******************************************************/
(() => {
/*******************************************************
* 1. Domain-Specific Overrides
* 1. Configurations
*******************************************************/
const domainConfigurations = {
const maxWaitTime = 4000;

const domainReplacements = {
"nordcheckout.com": "nordvpn.com",
};

const domainConfigs = {
"applebees.com": {
inputSelector: "#txtPromoCode",
applyButtonSelector: ".btnPromoApply",
Expand All @@ -14,21 +20,17 @@
priceSelector: ".order-price",
removeCouponButtonSelector: ".lnkPromoRemove",
},
"nordvpn.com": {
inputSelector: "input[name='couponCode']",
preApplyButtonSelector: "p[data-testid='coupon-show-form-button'] > a",
applyButtonSelector: "button[data-testid='coupon-apply-button']",
successSelector: "div[data-testid='coupon-applied-message']",
failureSelector: "div[data-testid='coupon-error-alert']",
priceSelector: "span[data-testid='CartSummary-total-amount']",
removeCouponButtonSelector: "button[data-testid='coupon-delete-applied-button']",
},
};

/*******************************************************
* 2. E-Commerce Platform Detection + Standard Configs
*******************************************************/
function detectPlatform() {
const html = document.documentElement.innerHTML.toLowerCase();

if (html.includes("woocommerce") || html.includes("wp-content/plugins/woocommerce")) {
return "woocommerce";
}

return null;
}

const platformConfigs = {
woocommerce: {
inputSelector: "input[name='coupon_code']",
Expand All @@ -41,6 +43,19 @@
},
};

/*******************************************************
* 2. E-Commerce Platform Detection
*******************************************************/
function detectPlatform() {
const html = document.documentElement.innerHTML.toLowerCase();

if (html.includes("woocommerce") || html.includes("wp-content/plugins/woocommerce")) {
return "woocommerce";
}

return null;
}

/*******************************************************
* 3. Helper Functions / Variables
*******************************************************/
Expand Down Expand Up @@ -108,11 +123,20 @@
async function applySingleCoupon(
inputSelector,
couponCode,
preApplyButtonSelector,
applyButtonSelector,
successSelector,
failureSelector,
priceSelector
) {
const preApplyButton = preApplyButtonSelector
? document.querySelector(preApplyButtonSelector)
: null;

if (preApplyButton) {
preApplyButton.click();
}

const input = inputSelector ? document.querySelector(inputSelector) : null;
const applyButton = applyButtonSelector
? document.querySelector(applyButtonSelector)
Expand All @@ -137,7 +161,6 @@
if (successSelector && failureSelector) {
await new Promise((resolve) => {
const startTime = Date.now();
const maxWait = 4000;
const interval = setInterval(() => {
const sEl = document.querySelector(successSelector);
const fEl = document.querySelector(failureSelector);
Expand All @@ -150,7 +173,7 @@
failureBySelector = true;
clearInterval(interval);
resolve();
} else if (Date.now() - startTime > maxWait) {
} else if (Date.now() - startTime > maxWaitTime) {
clearInterval(interval);
resolve();
}
Expand Down Expand Up @@ -218,6 +241,7 @@
const result = await applySingleCoupon(
config.inputSelector,
couponCode,
config.preApplyButtonSelector,
config.applyButtonSelector,
config.successSelector,
config.failureSelector,
Expand All @@ -243,6 +267,7 @@
await applySingleCoupon(
config.inputSelector,
bestCoupon,
config.preApplyButtonSelector,
config.applyButtonSelector,
config.successSelector,
config.failureSelector,
Expand Down Expand Up @@ -553,11 +578,12 @@
* 7. Determine Config Based on Domain or Platform
*******************************************************/
function determineConfig() {
const domain = window.location.hostname.replace("www.", "");
let domain = window.location.hostname.replace("www.", "");
if (domainReplacements[domain]) domain = domainReplacements[domain];

// 1) Check domain config
if (domainConfigurations[domain]) {
return domainConfigurations[domain];
if (domainConfigs[domain]) {
return domainConfigs[domain];
}

// 2) Check platform
Expand All @@ -574,7 +600,8 @@
* 8. Main
*******************************************************/
async function main() {
const domain = window.location.hostname.replace("www.", "");
let domain = window.location.hostname.replace("www.", "");
if (domainReplacements[domain]) domain = domainReplacements[domain];
const path = window.location.pathname;

// 1) Attempt to fetch coupons
Expand All @@ -586,11 +613,17 @@
}

if (!coupons || coupons.length === 0) {
console.log("[Syrup] No coupons found.");
return; // No coupons to try
}

chrome.runtime.sendMessage({
action: "setBadgeText",
text: coupons.length.toString(),
});

// 2) Check if user is on a likely checkout page
const isCheckoutPath = ["checkout", "cart", "basket", "order"].some((keyword) =>
const isCheckoutPath = ["checkout", "cart", "basket", "order", "payment"].some((keyword) =>
path.includes(keyword)
);
if (!isCheckoutPath) {
Expand Down
36 changes: 31 additions & 5 deletions Extension-React/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,44 @@ import Header from "./components/Header";
import CouponsList from "./components/CouponsList";
import { fetchCoupons } from "./lib/utils";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const domainReplacements: any = {
"nordcheckout.com": "nordvpn.com",
};

const Popup: React.FC = () => {
const [pageDomain, setPageDomain] = useState<string>("");
const [pageIcon, setPageIcon] = useState<string>("");
const [coupons, setCoupons] = useState<Coupon[]>([]);
const [coupons, setCoupons] = useState<Coupon[] | null>(null);

useEffect(() => {
if (!chrome.tabs) {
const url = new URL(window.location.href);
let domain = url.hostname.replace("www.", "");

const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has("domain")) {
domain = searchParams.get("domain") || "";
}
if (domainReplacements[domain]) domain = domainReplacements[domain];

setPageDomain(domain);
setPageIcon(
`https://www.google.com/s2/favicons?sz=64&domain=${domain}`
);

fetchCoupons(domain, setCoupons);
return;
}

chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length > 0) {
const tab = tabs[0];
if (tab.url) {
const url = new URL(tab.url);
const domain = url.hostname.replace("www.", "");
let domain = url.hostname.replace("www.", "");
if (domainReplacements[domain])
domain = domainReplacements[domain];

setPageDomain(domain);
setPageIcon(
Expand All @@ -31,7 +57,7 @@ const Popup: React.FC = () => {
const handleCopy = (code: string, index: number) => {
navigator.clipboard.writeText(code);

const newCoupons = [...coupons];
const newCoupons = coupons ? [...coupons] : [];
newCoupons[index] = { ...newCoupons[index], copied: true };
setCoupons(newCoupons);

Expand All @@ -42,9 +68,9 @@ const Popup: React.FC = () => {
};

return (
<div className="w-96 h-[32rem] flex flex-col p-4 bg-background">
<div className="w-96 h-[32rem] flex flex-col p-4 bg-white dark:bg-slate-900">
<Header pageIcon={pageIcon} pageDomain={pageDomain} />
<h2 className="text-lg font-semibold pb-2 mb-2 text-primary text-center border-border border-b-2">
<h2 className="text-lg font-semibold pb-2 mb-2 text-primary text-center border-border border-b-2 dark:text-white">
Coupons
</h2>
<CouponsList coupons={coupons} handleCopy={handleCopy} />
Expand Down
21 changes: 17 additions & 4 deletions Extension-React/src/components/CouponCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@ export interface Coupon {
title: string;
description: string;
copied?: boolean;
expirationDate: string;
}

const CouponCard: React.FC<{
coupon: Coupon;
onCopy: () => void;
copied: boolean;
}> = ({ coupon, onCopy, copied }) => (
<Card className="p-4 flex justify-between items-center bg-card text-card-foreground">
<Card className="p-4 pt-2 pb-2 flex justify-between items-center bg-card text-card-foreground dark:bg-slate-800 dark:border-slate-700">
<div>
<p className="font-bold text-primary">{coupon.code}</p>
<p className="text-sm text-muted-foreground">{coupon.title}</p>
<p className="text-sm font-bold text-primary dark:text-white">
{coupon.code}
</p>
<p className="text-sm text-muted-foreground dark:text-slate-400">
{coupon.title}
</p>
{coupon.expirationDate && (
<p className="text-sm text-muted-foreground text-orange-400">
Expires: {coupon.expirationDate}
</p>
)}
</div>
<Button onClick={onCopy} className="bg-primary text-primary-foreground">
<Button
onClick={onCopy}
className="bg-primary text-primary-foreground dark:bg-slate-700"
>
{copied ? "Copied!" : "Copy"}
</Button>
</Card>
Expand Down
34 changes: 20 additions & 14 deletions Extension-React/src/components/CouponsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,30 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import CouponCard, { Coupon } from "./CouponCard";

const CouponsList: React.FC<{
coupons: Coupon[];
coupons: Coupon[] | null;
handleCopy: (code: string, index: number) => void;
}> = ({ coupons, handleCopy }) => (
<ScrollArea className="flex-1">
{coupons.length > 0 ? (
<div className="space-y-4">
{coupons.map((coupon, index) => (
<CouponCard
key={index}
coupon={coupon}
onCopy={() => handleCopy(coupon.code, index)}
copied={(coupon as Coupon).copied || false}
/>
))}
</div>
{coupons ? (
coupons.length > 0 ? (
<div className="space-y-2">
{coupons.map((coupon, index) => (
<CouponCard
key={index}
coupon={coupon}
onCopy={() => handleCopy(coupon.code, index)}
copied={(coupon as Coupon).copied || false}
/>
))}
</div>
) : (
<p className="text-sm text-muted-foreground dark:text-slate-400">
No coupons available for this site.
</p>
)
) : (
<p className="text-sm text-muted-foreground">
No coupons available for this site.
<p className="text-sm text-muted-foreground dark:text-slate-400">
Loading coupons for this site...
</p>
)}
</ScrollArea>
Expand Down
8 changes: 5 additions & 3 deletions Extension-React/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ const Header: React.FC<{ pageIcon: string; pageDomain: string }> = ({
pageIcon,
pageDomain,
}) => (
<div className="flex items-center gap-4 border-border border-b-2 pb-4 mb-4">
<img src={pageIcon} alt="Page Icon" className="w-8 h-8" />
<p className="text-lg font-bold text-primary">{pageDomain}</p>
<div className="flex items-center gap-4 pb-2 mb-2 border-border border-b-2">
<img src={pageIcon} alt="Page Icon" className="w-8 h-8 rounded-full" />
<p className="text-lg font-bold text-primary dark:text-white">
{pageDomain}
</p>
</div>
);

Expand Down
33 changes: 21 additions & 12 deletions Extension-React/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function cn(...inputs: ClassValue[]) {

export const fetchCoupons = async (
domain: string,
setCoupons: React.Dispatch<React.SetStateAction<Coupon[]>>
setCoupons: React.Dispatch<React.SetStateAction<Coupon[] | null>>
) => {
try {
const response = await fetch(
Expand All @@ -18,20 +18,29 @@ export const fetchCoupons = async (
if (response.ok) {
const data = await response.json();
setCoupons(
data.map(
(coupon: {
couponCode: string;
couponTitle: string;
couponDescription: string;
}) => ({
code: coupon.couponCode,
title: coupon.couponTitle,
description: coupon.couponDescription,
})
)
data
.map(
(coupon: {
couponCode: string;
couponTitle: string;
couponDescription: string;
couponExpirationDate: string;
}) => ({
code: coupon.couponCode,
title: coupon.couponTitle,
description: coupon.couponDescription,
expirationDate: coupon.couponExpirationDate,
})
)
.sort((a: Coupon, b: Coupon) =>
a.code.localeCompare(b.code)
)
);
} else {
setCoupons([]);
}
} catch (error) {
console.error("Error fetching coupons:", error);
setCoupons([]);
}
};
Loading