diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index ff5302b..85263f7 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -41,7 +41,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Build
- run: pnpm build
+ run: pnpm build:github
- name: Setup Pages
uses: actions/configure-pages@v4
diff --git a/README.md b/README.md
index 4135bcf..55d9ee9 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Bunch – Bitcoin Loyalty Punch Cards
+[](https://shakespeare.diy/clone?url=https%3A%2F%2Fgithub.com%2FNotThatKindOfDrLiz%2Fbunch.git)
+
Bunch is a drop-in loyalty layer for Bitcoin-accepting merchants. It tracks punches locally alongside existing payment flows, without touching invoices or custody. Made for the btc++ Taipei hackathon.
## Architecture highlights
@@ -45,7 +47,7 @@ When a payment is confirmed in BTCPay Server or LNbits, send a webhook to your b
// Backend webhook handler (Node.js example)
app.post('/webhooks/btcpay', async (req, res) => {
const { invoiceId, status, metadata } = req.body
-
+
if (status === 'paid' && metadata.purchaseNonce) {
// Notify merchant's Bunch instance via WebSocket or Server-Sent Events
notifyMerchant({
@@ -55,7 +57,7 @@ app.post('/webhooks/btcpay', async (req, res) => {
amount: req.body.amount
})
}
-
+
res.status(200).send('OK')
})
```
@@ -81,12 +83,12 @@ Merchant's Bunch instance polls payment system API to check invoice status:
const checkPaymentStatus = async (purchaseNonce: string) => {
// Get invoice ID from purchase nonce metadata
const invoiceId = await getInvoiceIdForNonce(purchaseNonce)
-
+
// Poll BTCPay Server API
const invoice = await fetch(`https://your-btcpay-server.com/api/invoices/${invoiceId}`, {
headers: { 'Authorization': `token ${BTCPAY_API_KEY}` }
}).then(r => r.json())
-
+
if (invoice.status === 'paid') {
await markPaid(purchaseNonce, { amount: invoice.amount })
}
@@ -123,7 +125,7 @@ const invoice = await btcpay.createInvoice({
// 2. Webhook receives payment confirmation
btcpay.on('invoice.paid', async (invoice) => {
const { purchaseNonce } = invoice.metadata
-
+
// 3. Verify and award punch
if (await verifyPurchaseNonce(purchaseNonce)) {
await merchantStore.markPaid(purchaseNonce, {
diff --git a/index.html b/index.html
index d156f18..4928fc1 100644
--- a/index.html
+++ b/index.html
@@ -2,8 +2,8 @@
-
-
+
+
Bunch – Bitcoin Loyalty Punch Cards
diff --git a/package.json b/package.json
index c7d2d2f..0008646 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
+ "build:github": "tsc && vite build --mode github",
"preview": "vite preview"
},
"devDependencies": {
diff --git a/public/404.html b/public/404.html
index 72903fd..e43b5e7 100644
--- a/public/404.html
+++ b/public/404.html
@@ -2,36 +2,29 @@
-
+
Bunch – Bitcoin Loyalty Punch Cards
diff --git a/src/main.tsx b/src/main.tsx
index 609eca7..d0470e5 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -14,24 +14,16 @@ declare global {
window.bunchVersion = '0.1.0'
-// Handle GitHub Pages 404 redirect
-// When 404.html redirects here with ?/path, extract and navigate
-if (window.location.search.includes('?/')) {
- const search = window.location.search
- const pathMatch = search.match(/\?\/+(.+?)(?:&|$)/)
- if (pathMatch) {
- const path = pathMatch[1].replace(/~and~/g, '&')
- const newPath = path.startsWith('/') ? path : '/' + path
- const baseUrl = import.meta.env.BASE_URL.endsWith('/')
- ? import.meta.env.BASE_URL.slice(0, -1)
- : import.meta.env.BASE_URL
- window.history.replaceState({}, '', `${baseUrl}${newPath}`)
- }
+// Handle 404 redirect for client-side routing
+const redirectPath = sessionStorage.getItem('redirectPath')
+if (redirectPath) {
+ sessionStorage.removeItem('redirectPath')
+ window.history.replaceState({}, '', redirectPath)
}
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+
} />
} />
diff --git a/src/screens/MerchantApp.tsx b/src/screens/MerchantApp.tsx
index 54b01bb..60046c6 100644
--- a/src/screens/MerchantApp.tsx
+++ b/src/screens/MerchantApp.tsx
@@ -9,6 +9,7 @@ import { CardStats } from '../components/CardStats'
import { EmptyStateCard } from '../components/EmptyStateCard'
import { SessionCard } from '../components/SessionCard'
import { MerchantStatusPanel } from '../components/MerchantStatusPanel'
+import { getAssetPath, getRoutePath } from '../utils/paths'
export const MerchantApp = () => {
const {
@@ -62,7 +63,7 @@ export const MerchantApp = () => {
-

+
Merchant
Drop-in Bitcoin loyalty punch cards
@@ -85,10 +86,7 @@ export const MerchantApp = () => {