add Stripe
This commit is contained in:
7
public/icon-mask.svg
Normal file
7
public/icon-mask.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="TouchBase">
|
||||
<!-- Maskable: full bleed background, content in center safe area (~80% inset) -->
|
||||
<rect width="256" height="256" fill="#18181b"/>
|
||||
<text x="128" y="128" text-anchor="middle" dominant-baseline="central"
|
||||
font-family="system-ui, -apple-system, sans-serif" font-weight="600"
|
||||
font-size="96" fill="#fafafa">TB</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
6
public/icon.svg
Normal file
6
public/icon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="TouchBase">
|
||||
<rect width="256" height="256" rx="48" fill="#18181b"/>
|
||||
<text x="128" y="128" text-anchor="middle" dominant-baseline="central"
|
||||
font-family="system-ui, -apple-system, sans-serif" font-weight="600"
|
||||
font-size="120" fill="#fafafa">TB</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 361 B |
106
public/sw.js
Normal file
106
public/sw.js
Normal file
@@ -0,0 +1,106 @@
|
||||
// TouchBase service worker — minimal, hand-rolled.
|
||||
//
|
||||
// Strategy:
|
||||
// - HTML pages (text/html): network-first → cache fallback for offline.
|
||||
// - Static assets (/_next/static, /icon.svg, fonts): stale-while-revalidate.
|
||||
// - API + auth + booking server actions: pass-through, NEVER cached.
|
||||
// Stale appointment data is dangerous; better to fail than to lie.
|
||||
//
|
||||
// Cache name is versioned. Bump CACHE_VERSION on any deploy that should
|
||||
// invalidate the cache for returning users.
|
||||
|
||||
const CACHE_VERSION = "v1";
|
||||
const HTML_CACHE = `tb-html-${CACHE_VERSION}`;
|
||||
const STATIC_CACHE = `tb-static-${CACHE_VERSION}`;
|
||||
const ALL_CACHES = [HTML_CACHE, STATIC_CACHE];
|
||||
|
||||
self.addEventListener("install", () => {
|
||||
// Activate the new SW immediately; users get the new caching rules on next nav.
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const names = await caches.keys();
|
||||
await Promise.all(
|
||||
names
|
||||
.filter((n) => n.startsWith("tb-") && !ALL_CACHES.includes(n))
|
||||
.map((n) => caches.delete(n)),
|
||||
);
|
||||
await self.clients.claim();
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const req = event.request;
|
||||
|
||||
// Only handle GET. Server actions (POST), auth callbacks, etc. always pass through.
|
||||
if (req.method !== "GET") return;
|
||||
|
||||
const url = new URL(req.url);
|
||||
|
||||
// Same-origin only — don't cache cross-origin (analytics, fonts CDN, etc.).
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
// Never cache API, auth, or anything in the booking flow that could go stale.
|
||||
// Stale slot data could let a customer "see" an already-taken slot.
|
||||
if (
|
||||
url.pathname.startsWith("/api/") ||
|
||||
url.pathname.startsWith("/_next/data/") ||
|
||||
url.pathname.startsWith("/admin/") ||
|
||||
url.pathname.startsWith("/account/") ||
|
||||
url.pathname.startsWith("/therapist/") ||
|
||||
url.pathname.startsWith("/book")
|
||||
) {
|
||||
return; // pass through
|
||||
}
|
||||
|
||||
// Static assets → stale-while-revalidate
|
||||
if (
|
||||
url.pathname.startsWith("/_next/static/") ||
|
||||
url.pathname === "/icon.svg" ||
|
||||
url.pathname === "/icon-mask.svg" ||
|
||||
url.pathname === "/favicon.ico" ||
|
||||
url.pathname === "/manifest.webmanifest"
|
||||
) {
|
||||
event.respondWith(staleWhileRevalidate(req, STATIC_CACHE));
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML navigation → network-first → cache fallback
|
||||
if (req.mode === "navigate" || req.headers.get("accept")?.includes("text/html")) {
|
||||
event.respondWith(networkFirst(req, HTML_CACHE));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
async function networkFirst(request, cacheName) {
|
||||
const cache = await caches.open(cacheName);
|
||||
try {
|
||||
const fresh = await fetch(request);
|
||||
if (fresh && fresh.ok) {
|
||||
cache.put(request, fresh.clone());
|
||||
}
|
||||
return fresh;
|
||||
} catch (err) {
|
||||
const cached = await cache.match(request);
|
||||
if (cached) return cached;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function staleWhileRevalidate(request, cacheName) {
|
||||
const cache = await caches.open(cacheName);
|
||||
const cached = await cache.match(request);
|
||||
const fetchPromise = fetch(request)
|
||||
.then((response) => {
|
||||
if (response && response.ok) {
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return cached ?? (await fetchPromise) ?? new Response("offline", { status: 503 });
|
||||
}
|
||||
Reference in New Issue
Block a user