cfff74e2db
Souq Marketplace: - Native souq pages: browse+filters, create listing, detail+seller profile, orders+chat - New API: reviews (POST), sellers/[id], upload (multipart), polar-checkout webhook - Listings API enhanced: minPrice, maxPrice, sortBy, deliveryDays filters - Seller workflow: start order, mark delivered, upload files, confirm delivery - Wallet: 5 Polar.sh FLH tiers, production checkout, mock fallback, success banner - Fix: order redirect /souq/history → /souq/orders Auth System: - Unified auth page, removed OAuth provider routing (2 files deleted) - Deleted oauth.ts (319 lines) — simplified auth chain - Streamlined login/register API routes Prisma Schema (+179 lines): - New models: Listing, Purchase, Review, Group/GroupMember - Gamification: DailyStreak, XpTransaction, Achievement, UserLevel - Learning: LearnCourse/Module/Enrollment/Certificate - Notifications, Referrals, Dhikr, PremiumBenefits Deleted legacy code: - src/app/api/marketplace/* (3 files) — replaced by /api/souq/* - src/app/api/files/[listingId]/route.ts — replaced by /api/souq/upload - src/app/api/seller/[sellerId]/route.ts — replaced by /api/souq/sellers/[id] Infrastructure: - Dockerfile: standalone output, Prisma runtime migration - docker-compose.yml: Polar env vars, healthcheck fix - docker-compose.staging.yml: staging on port 4014 - .gitea/workflows/ci.yml: CI pipeline
90 lines
2.2 KiB
JavaScript
90 lines
2.2 KiB
JavaScript
// Falah PWA Service Worker
|
|
const CACHE = "falah-cache-v1";
|
|
const IMMUTABLE_CACHE = "falah-immutable-v1";
|
|
|
|
// Assets to pre-cache on install
|
|
const PRECACHE_ASSETS = [
|
|
"/mobile/manifest.json",
|
|
"/mobile/icons/icon-192x192.png",
|
|
"/mobile/icons/icon-512x512.png",
|
|
"/mobile/apple-touch-icon.png",
|
|
"/mobile/offline"
|
|
];
|
|
|
|
// Install: pre-cache core assets
|
|
self.addEventListener("install", (event) => {
|
|
event.waitUntil(
|
|
caches.open(IMMUTABLE_CACHE).then((cache) => cache.addAll(PRECACHE_ASSETS))
|
|
);
|
|
self.skipWaiting();
|
|
});
|
|
|
|
// Activate: clean old caches
|
|
self.addEventListener("activate", (event) => {
|
|
event.waitUntil(
|
|
caches.keys().then((keys) =>
|
|
Promise.all(
|
|
keys
|
|
.filter((k) => k !== CACHE && k !== IMMUTABLE_CACHE)
|
|
.map((k) => caches.delete(k))
|
|
)
|
|
)
|
|
);
|
|
self.clients.claim();
|
|
});
|
|
|
|
// Fetch: network-first for pages, cache-first for static assets
|
|
self.addEventListener("fetch", (event) => {
|
|
const { request } = event;
|
|
const url = new URL(request.url);
|
|
|
|
// Only handle same-origin /mobile requests
|
|
if (!url.pathname.startsWith("/mobile")) return;
|
|
|
|
// API calls — never cache
|
|
if (url.pathname.startsWith("/mobile/api/")) return;
|
|
|
|
// Static assets (icons, fonts, images) — cache-first
|
|
if (
|
|
url.pathname.match(/\.(png|jpg|jpeg|gif|svg|ico|webp|woff2?|css)$/)
|
|
) {
|
|
event.respondWith(cacheFirst(request));
|
|
return;
|
|
}
|
|
|
|
// Navigation & pages — network-first with fallback
|
|
if (request.mode === "navigate") {
|
|
event.respondWith(networkFirst(request));
|
|
return;
|
|
}
|
|
|
|
// Everything else — network-only
|
|
event.respondWith(fetch(request).catch(() => caches.match(request)));
|
|
});
|
|
|
|
async function networkFirst(request) {
|
|
try {
|
|
const response = await fetch(request);
|
|
if (response.ok) {
|
|
const cache = await caches.open(CACHE);
|
|
cache.put(request, response.clone());
|
|
}
|
|
return response;
|
|
} catch {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
// Offline fallback page
|
|
return caches.match("/mobile/offline");
|
|
}
|
|
}
|
|
|
|
async function cacheFirst(request) {
|
|
const cached = await caches.match(request);
|
|
if (cached) return cached;
|
|
try {
|
|
return await fetch(request);
|
|
} catch {
|
|
return new Response("", { status: 408 });
|
|
}
|
|
}
|