Skip to content

Instantly share code, notes, and snippets.

@qwo
Last active November 14, 2024 01:45
Show Gist options
  • Save qwo/1b2d82dbe1dd823796e9f2bf2b357786 to your computer and use it in GitHub Desktop.
Save qwo/1b2d82dbe1dd823796e9f2bf2b357786 to your computer and use it in GitHub Desktop.
SCL 2024 Embed
<!DOCTYPE html>
<html>
<head>
<title>Fetched Checkout Data</title>
</head>
<body>
<div id="checkout-info">
</div>
<script>
// Replace 'https://stanley-courteousgreenmandrill.web.val.run/' with your actual API endpoint
fetch('https://stanley-scl_2024.web.val.run/progress')
.then(response => response.text())
.then(data => {
document.getElementById('checkout-info').innerHTML=data;
})
.catch(error => {
console.error('Error fetching data:', error);
const checkoutInfo = document.getElementById('checkout-info');
checkoutInfo.innerHTML = 'Error fetching checkout data.';
});
</script>
</body>
</html>
<div>
<div
class="progress-bar-container"
title="Updated every few hours, please be patient if you don't see your donation move the bar!"
>
<div class="pb-text">
<div>
Total Raised: <b>$<span id="progressBarTotal"></span></b>
</div>
</div>
<div class="progress-bar-container2">
<div class="red-progress-bar" id="progress-bar"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div id="numDaysRemaining" class="pb-text"></div>
<div class="pb-text">GOAL: $<span id="goalAmount"></span>K</div>
</div>
</div>
</div>
<script>
// INPUTS
let totalRaised = 0;
let goalAmount = 10000;
let endDate = "1/1/2025";
// Calculations
let daysRemaining = Math.floor((Date.parse(endDate) - Date.now()) / (1000 * 60 * 60 * 24));
daysRemaining = Math.max(0, daysRemaining + 1);
let percentage = (totalRaised / goalAmount) * 100;
let barWidth = percentage > 100 ? 100 : percentage;
let d = document.getElementById("numDaysRemaining");
d.innerHTML = daysRemaining.toString() + " DAYS LEFT";
let t = document.getElementById("progressBarTotal");
t.innerHTML = totalRaised.toLocaleString();
let bar = document.getElementById("progress-bar");
bar.style.width = barWidth.toString() + "%";
let goal = document.getElementById("goalAmount");
goal.innerHTML = goalAmount / 1000;
// Feature Flag to turn this on/off, Square was having some issues
if (false) {
try {
fetch("https://progress-fetcher.herokuapp.com/W3OO2333C5MMCSJS3SBUOQN4")
.then((response) => response.json())
.then((data) => {
if (data.amount) { // if a valid amount was returned
let totalRaised2 = data.amount;
let t2 = document.getElementById("progressBarTotal");
t2.innerHTML = totalRaised2.toLocaleString();
let percentage2 = (totalRaised2 / goalAmount) * 100;
let barWidth2 = percentage2 > 100 ? 100 : percentage2;
let bar2 = document.getElementById("progress-bar");
bar2.style.width = barWidth2.toString() + "%";
}
else if (data.name) {
console.log(data.name);
}
});
} catch (e) {
console.log(e);
}
}
</script>
<style>
.progress-bar-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100px;
text-align: left;
}
.progress-bar-container2 {
position: relative;
display: flex;
justify-content: space-between;
margin: 10px 0px;
background: #eaeaea;
border-radius: 25px;
height: 25px;
}
.grey-progress-bar {
height: 25px;
width: 100%;
}
.yellow-progress-bar {
background: #f6c342;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.red-progress-bar {
background: #a8192e;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.goal-circle {
background: white;
border: 5px solid #a9262e;
border-radius: 25px;
height: 25px;
z-index: 5;
width: 25px;
box-sizing: border-box;
right: 0;
position: absolute;
}
.progress-bar-description {
display: flex;
justify-content: space-between;
}
.pb-text {
color: black;
font-size: 16px;
font-family: "Open Sans", sans-serif;
}
</style>
import { blob } from "https://esm.town/v/std/blob";
const url = "https://checkout.square.site/merchant/B4M6RCB1WWG5F/checkout/W3OO2333C5MMCSJS3SBUOQN4";
const cacheKey = "donation_data_cache";
// Utility functions for formatting
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZoneName: "short",
});
}
function formatCurrency(amount: number, currencyCode: string): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currencyCode,
}).format(amount / 100); // Assuming amount is in cents
}
function formatGoalAmount(amount: number): string {
if (amount >= 1000000) {
return `${Math.round(amount / 1000000)}M`;
} else if (amount >= 1000) {
return `${Math.round(amount / 1000)}K`;
} else {
return Math.round(amount).toString();
}
}
function calculateBarWidth(totalRaised: number, goalAmount: number): string {
const percentage = (totalRaised / goalAmount) * 100;
return `${Math.min(Math.round(percentage), 100)}%`;
}
async function fetchAndCacheData() {
try {
console.log("Fetching URL:", url);
const response = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const html = await response.text();
console.log("HTML content length:", html.length);
const scriptRegex = /window\.bootstrap\s*=\s*({[\s\S]*?});/;
const match = html.match(scriptRegex);
if (!match) {
throw new Error("Bootstrap data not found in the HTML");
}
const bootstrapData = JSON.parse(match[1]);
console.log("Extracted full bootstrap data");
await blob.setJSON(cacheKey, {
timestamp: new Date().toISOString(),
data: bootstrapData,
});
return bootstrapData;
} catch (error) {
console.error("Error fetching and caching data:", error);
const cachedData = await blob.getJSON(cacheKey);
if (cachedData) {
console.log("Using cached data from:", cachedData.timestamp);
return {
...cachedData.data,
_cachedAt: cachedData.timestamp,
_cacheNotice: "This is cached data. Live fetching failed.",
};
}
throw error;
}
}
async function getFullData() {
const data = await fetchAndCacheData();
const donationGoal = data.checkoutLink.checkout_link_data.donation_goal;
const totalRaised = data.donationGoalProgress;
const goalAmount = donationGoal.target.amount;
const endDate = new Date(donationGoal.end_time);
const daysRemaining = Math.max(0, Math.ceil((endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)));
const percentage = Math.round((totalRaised / goalAmount) * 100);
return Response.json({
...data,
fundraiserData: {
totalRaised: formatCurrency(totalRaised, data.currencyCode),
daysRemaining,
percentage,
goalAmount: formatCurrency(goalAmount, data.currencyCode),
},
});
}
async function getDonationProgress() {
const data = await fetchAndCacheData();
const donationGoal = data.checkoutLink.checkout_link_data.donation_goal;
return Response.json({
donationGoalProgress: formatCurrency(data.donationGoalProgress, data.currencyCode),
donationGoal: {
...donationGoal,
begin_time: formatDate(donationGoal.begin_time),
end_time: formatDate(donationGoal.end_time),
target: formatCurrency(donationGoal.target.amount, donationGoal.target.currency),
},
});
}
async function getCheckoutDetails() {
const data = await fetchAndCacheData();
return Response.json({
checkoutTitle: data.checkoutTitle,
currencyCode: data.currencyCode,
checkoutLinkId: data.checkoutLinkId,
});
}
async function getCampaignInfo() {
const data = await fetchAndCacheData();
const linkData = data.checkoutLink.checkout_link_data;
return Response.json({
name: linkData.name,
description: linkData.description,
beginTime: formatDate(linkData.donation_goal.begin_time),
endTime: formatDate(linkData.donation_goal.end_time),
});
}
async function getScrapedData() {
const data = await fetchAndCacheData();
const donationGoal = data.checkoutLink.checkout_link_data.donation_goal;
const totalRaised = data.donationGoalProgress;
const goalAmount = donationGoal.target.amount;
const endDate = new Date(donationGoal.end_time);
const daysRemaining = Math.max(0, Math.ceil((endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)));
return Response.json({
goalAmount: formatGoalAmount(goalAmount / 100), // Convert cents to dollars
daysRemaining: daysRemaining.toString(),
progressPercentage: calculateBarWidth(totalRaised, goalAmount),
amountRaised: Math.round(totalRaised / 100).toLocaleString(), // Convert cents to dollars and round
});
}
function getDemoData() {
const goalAmount = 1000000; // $100,00
const totalRaised = Math.floor(Math.random() * goalAmount);
const daysRemaining = Math.floor(Math.random() * 30) + 1;
return Response.json({
goalAmount: formatGoalAmount(goalAmount / 100), // Convert cents to dollars
daysRemaining: daysRemaining.toString(),
progressPercentage: calculateBarWidth(totalRaised, goalAmount),
amountRaised: Math.round(totalRaised / 100).toLocaleString(), // Convert cents to dollars and round
});
}
async function getHtmlView() {
const scrapedData = await getScrapedData();
const demoData = await getDemoData();
const scrapedDataJson = JSON.parse(await scrapedData.text());
const demoDataJson = JSON.parse(await demoData.text());
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fundraiser Progress</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #2c3e50; }
h2 { color: #34495e; }
.progress-bar-container { display: flex; flex-direction: column; width: 100%; height: 100px; text-align: left; margin-bottom: 20px; }
.progress-bar-container2 { position: relative; display: flex; justify-content: space-between; margin: 10px 0px; background: #eaeaea; border-radius: 25px; height: 25px; }
.red-progress-bar { background: #a8192e; border-radius: 25px; height: 25px; z-index: 5; }
.blue-progress-bar { background: #3498db; border-radius: 25px; height: 25px; z-index: 5; }
.goal-circle { background: white; border: 5px solid #a9262e; border-radius: 25px; height: 25px; z-index: 5; width: 25px; box-sizing: border-box; right: 0; position: absolute; }
.progress-bar-description { display: flex; justify-content: space-between; }
.pb-text { color: black; font-size: 16px; font-family: "Open Sans", sans-serif; }
</style>
</head>
<body>
<h1>Fundraiser Progress</h1>
<h2>Actual Progress</h2>
<div class="progress-bar-container">
<div class="pb-text">
<div>
Total Raised: <b>$${scrapedDataJson.amountRaised}</b>
</div>
</div>
<div class="progress-bar-container2">
<div class="red-progress-bar" style="width: ${scrapedDataJson.progressPercentage};"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div class="pb-text">${scrapedDataJson.daysRemaining} DAYS LEFT</div>
<div class="pb-text">GOAL: $${scrapedDataJson.goalAmount}</div>
</div>
</div>
<h2>Demo Progress</h2>
<div class="progress-bar-container">
<div class="pb-text">
<div>
Total Raised: <b>$${demoDataJson.amountRaised}</b>
</div>
</div>
<div class="progress-bar-container2">
<div class="blue-progress-bar" style="width: ${demoDataJson.progressPercentage};"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div class="pb-text">${demoDataJson.daysRemaining} DAYS LEFT</div>
<div class="pb-text">GOAL: $${demoDataJson.goalAmount}</div>
</div>
</div>
</body>
</html>
`;
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
}
async function getFundraiserHtml() {
const scrapedData = await getScrapedData();
const demoData = await getDemoData();
const scrapedDataJson = JSON.parse(await scrapedData.text());
const demoDataJson = JSON.parse(await demoData.text());
const html = `
<div>
<div
class="progress-bar-container"
title="Updated every few hours, please be patient if you don't see your donation move the bar!"
>
<h2>Live Data</h2>
<div class="pb-text">
<div>
Total Raised: <b>$<span id="amountRaised">${scrapedDataJson.amountRaised}</span></b>
</div>
</div>
<div class="progress-bar-container2">
<div class="red-progress-bar" id="progress-bar" style="width: ${scrapedDataJson.progressPercentage};"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div id="daysRemaining" class="pb-text">${scrapedDataJson.daysRemaining} DAYS LEFT</div>
<div class="pb-text">GOAL: $<span id="goalAmount">${scrapedDataJson.goalAmount}</span></div>
</div>
</div>
<div
class="progress-bar-container"
title="This is demo data that changes dynamically"
>
<h2>Demo Data</h2>
<div class="pb-text">
<div>
Total Raised: <b>$<span id="demo-amountRaised">${demoDataJson.amountRaised}</span></b>
</div>
</div>
<div class="progress-bar-container2">
<div class="blue-progress-bar" id="demo-progress-bar" style="width: ${demoDataJson.progressPercentage};"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div id="demo-daysRemaining" class="pb-text">${demoDataJson.daysRemaining} DAYS LEFT</div>
<div class="pb-text">GOAL: $<span id="demo-goalAmount">${demoDataJson.goalAmount}</span></div>
</div>
</div>
</div>
<style>
.progress-bar-container {
display: flex;
flex-direction: column;
width: 100%;
height: 150px;
text-align: left;
margin-bottom: 20px;
}
.progress-bar-container2 {
position: relative;
display: flex;
justify-content: space-between;
margin: 10px 0px;
background: #eaeaea;
border-radius: 25px;
height: 25px;
}
.red-progress-bar {
background: #a8192e;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.blue-progress-bar {
background: #3498db;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.goal-circle {
background: white;
border: 5px solid #a9262e;
border-radius: 25px;
height: 25px;
z-index: 5;
width: 25px;
box-sizing: border-box;
right: 0;
position: absolute;
}
.progress-bar-description {
display: flex;
justify-content: space-between;
}
.pb-text {
color: black;
font-size: 16px;
font-family: "Open Sans", sans-serif;
}
h2 {
margin-bottom: 10px;
color: #2c3e50;
}
</style>
`;
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
}
const router = {
"/": getFullData,
"/progress": getDonationProgress,
"/checkout": getCheckoutDetails,
"/campaign": getCampaignInfo,
"/view": getHtmlView,
"/fundraiser-html": getFundraiserHtml,
"/live-data": getScrapedData,
"/demo-data": getDemoData,
};
export default async function(req: Request): Promise<Response> {
const { pathname } = new URL(req.url);
const handler = router[pathname];
if (handler) {
try {
return await handler();
} catch (error) {
console.error("Error in endpoint handler:", error);
return Response.json({ error: "Internal server error", message: error.message }, { status: 500 });
}
} else {
return Response.json({ error: "Not Found" }, { status: 404 });
}
}
<!DOCTYPE html>
<html>
<head>
<title>Fetched Checkout Data</title>
</head>
<body>
<div id="checkout-info">
</div>
<script>
fetch('https://stanley-scl_2024.web.val.run/progress')
.then(response => response.text())
.then(data => {
document.getElementById('checkout-info').innerHTML=data;
})
.catch(error => {
console.error('Error fetching data:', error);
const checkoutInfo = document.getElementById('checkout-info');
checkoutInfo.innerHTML = 'Error fetching checkout data.';
});
</script>
</body>
</html>
<div>
<div
class="progress-bar-container"
title="Updated every few hours, please be patient if you don't see your donation move the bar!"
>
<div class="pb-text">
<div>
Total Raised: <b>$<span id="progressBarTotal"></span></b>
</div>
</div>
<div class="progress-bar-container2">
<div class="red-progress-bar" id="progress-bar"></div>
<div class="goal-circle"></div>
</div>
<div class="progress-bar-description">
<div id="numDaysRemaining" class="pb-text"></div>
<div class="pb-text">GOAL: $<span id="goalAmount"></span></div>
</div>
</div>
</div>
<script>
// Simplified Data Population from New API Response
//fetch('https://stanley-scl_2024.web.val.run/live-data')
fetch('https://stanley-scl_2024.web.val.run/demo-data')
.then((response) => response.json())
.then((data) => {
// Populate the data directly from the JSON response
document.getElementById('progressBarTotal').innerHTML = data.amountRaised;
document.getElementById('goalAmount').innerHTML = data.goalAmount;
document.getElementById('numDaysRemaining').innerHTML = data.daysRemaining + ' DAYS LEFT';
// Set progress bar width based on percentage
document.getElementById('progress-bar').style.width = data.progressPercentage;
})
.catch((error) => {
console.error('Error fetching data:', error);
});
</script>
<style>
.progress-bar-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100px;
text-align: left;
}
.progress-bar-container2 {
position: relative;
display: flex;
justify-content: space-between;
margin: 10px 0px;
background: #eaeaea;
border-radius: 25px;
height: 25px;
}
.grey-progress-bar {
height: 25px;
width: 100%;
}
.yellow-progress-bar {
background: #f6c342;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.red-progress-bar {
background: #a8192e;
border-radius: 25px;
height: 25px;
z-index: 5;
}
.goal-circle {
background: white;
border: 5px solid #a9262e;
border-radius: 25px;
height: 25px;
z-index: 5;
width: 25px;
box-sizing: border-box;
right: 0;
position: absolute;
}
.progress-bar-description {
display: flex;
justify-content: space-between;
}
.pb-text {
color: black;
font-size: 16px;
font-family: "Open Sans", sans-serif;
}
</style>
@qwo
Copy link
Author

qwo commented Nov 14, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment