Last active
November 14, 2024 01:45
-
-
Save qwo/1b2d82dbe1dd823796e9f2bf2b357786 to your computer and use it in GitHub Desktop.
SCL 2024 Embed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
last file is revision from townies API here.
heres the gist: https://www.val.town/v/stanley/scl_2024
heres the api: https://stanley-scl_2024.web.val.run/
heres a web preview: https://stanley-scl_2024.web.val.run/view
API router is
const router = {
"https://stanley-scl_2024.web.val.run/": getFullData,
"https://stanley-scl_2024.web.val.run/progress": getDonationProgress,
"https://stanley-scl_2024.web.val.run/checkout": getCheckoutDetails,
"https://stanley-scl_2024.web.val.run/campaign": getCampaignInfo,
"https://stanley-scl_2024.web.val.run/view": getHtmlView,
"https://stanley-scl_2024.web.val.run/fundraiser-html": getFundraiserHtml,
"https://stanley-scl_2024.web.val.run/live-data": getScrapedData,
"https://stanley-scl_2024.web.val.run/demo-data": getDemoData,
};