From 36cb1ab688a44f8df5cb2ab4ac0b245b7db8083f Mon Sep 17 00:00:00 2001 From: David Ojo Date: Fri, 29 May 2026 16:56:20 +0100 Subject: [PATCH] feat: add PWA manifest and service worker for home screen install Add web app manifest, offline-capable service worker, and Next.js headers plus client registration so users can install Aframp from the browser. Co-authored-by: Cursor --- next.config.mjs | 52 +++++++++++++++++++++++++++++++++++++++++++ public/icon-192.png | Bin 0 -> 1287 bytes public/icon-512.png | Bin 0 -> 4097 bytes public/manifest.json | 31 ++++++++++++++++++++++++++ public/sw.js | 34 ++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/manifest.json create mode 100644 public/sw.js diff --git a/next.config.mjs b/next.config.mjs index 5a5a7bc..7ed8758 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -8,6 +8,58 @@ const nextConfig = { }, // Enable standalone output for Docker deployments output: 'standalone', + async headers() { + return [ + { + source: '/manifest.json', + headers: [ + { + key: 'Content-Type', + value: 'application/manifest+json; charset=utf-8', + }, + ], + }, + { + source: '/sw.js', + headers: [ + { + key: 'Content-Type', + value: 'application/javascript; charset=utf-8', + }, + { + key: 'Cache-Control', + value: 'no-cache, no-store, must-revalidate', + }, + { + key: 'Service-Worker-Allowed', + value: '/', + }, + ], + }, + { + source: '/:path*', + headers: [ + { + key: 'Link', + value: '; rel="manifest"', + }, + ], + }, + ] + }, + webpack(config, { isServer, webpack }) { + if (!isServer) { + config.plugins.push( + new webpack.BannerPlugin({ + raw: true, + entryOnly: true, + banner: + "if(typeof window!=='undefined'&&'serviceWorker'in navigator){window.addEventListener('load',function(){navigator.serviceWorker.register('/sw.js',{scope:'/',updateViaCache:'none'});});}", + }) + ) + } + return config + }, } export default nextConfig diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..c650dca5d1b1ab76673c96e7d5967427013a76d6 GIT binary patch literal 1287 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcaljKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucHFG>&978JN-d=aiyX__7a?$?Vi+sz|r(R9s zFcY|`w{dpL<-UOQg0!%SEcax8?4Le;di(`1h7e(ft1J$ti~+ubiW&3f|9pEs&*(Mk z4c$v0&zra{!k4o!vLdSP|RmnZ)NLX01MSE#_Q6Gbtm;3IZDm9?c#F7o+9_OD~FzQU|D?L5&{+*f{ zJ+Sba=)QmNp8eZ7N4bIZ(!{R> zPiq^s2G5r_fQHIt7Q5Y25*pJ??QU zQ@z3ZWd=}fxW{`V=XKu-ERHjdwCZatZqyk#|@QpYo~vy13E&QvE{IYr}cf! ry^njgTs`)GP-RMT%ZN8mujeZiY-ye8Hj@Kbcrkdo`njxgN@xNAb%N%M literal 0 HcmV?d00001 diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf38776243defdd6c5b1156e7573cc198013cb5 GIT binary patch literal 4097 zcmeH~dsGv57RT?5p#)e!%lgD92!e{pTB}HSh^f{|0*H@AM2UDr5fx3BM<5y*e4agO z1c}5_CH6RDJ)&%?QL#B-s=Lc}Jrz6agGLm3s?AQfJk%%}RQCQn{?XHR|J}d*BWLbR zzV~y#bMKuw$)D5H=K1@M@&y2YB{OFs0F5CH(s%ISF1_Kw1684b}j+f898OZkcKKk9YlyPSrzU@7;Ry1#U9t8g$2XM3!fK{LcUmt)8;cqXz?Z^LR z9oXP!Jp->Y)0{r-yMYa6yi2|EqqKoE3^NDxOcFGmV{g3^Q_~ z(P>qHn9a9(pPCDfaYATQz56+P4z13SbGn+OFlzt>evG%fTX0Ekg+dM`vfUwuj2wR& zcJWs(?s1n@OqarlngAD;_qr7VIhpdq@k-0cLy?e^EMC*ZV*tBj0gacuX~nht6^0;2G*xX`>Ov+@P5&&qX^%Q!t$I z?p+sE@VvVvPqJJM4lm)M-KkpE-2ZSSb^Be=o+)}3ajMy;u?E;`{)+D0&{#v?N5NWu z3L;hBn=YTUH+vfW8+QR4#BYyX`JiA0Z*W|cz~Kopdp)1Mf-jFB-!=lo%pj z?wy!FR+~V9x5~Rf;e7hBds+m)&+)kgMo*BP#kxJ5Wg3<-qSp|y61|S8+Iq`m3!I6! zzMcDEMrgt2#hx-!XC~G;U~Ic7G~{uXIFb_voglK9Q&+Y12hKKRCN^Xc_x*It$?#kx zIU{JeX9~ED-iC^K^OgPVDk+pMk}nSu&M)CCi4zMX_i&)8;#!1g4sLygg-b2K#&X*U zWS&P>EqvISh>%L-O@w5NeJKd(L&yU8;zdHUj_)Au@1c{qVmd;Mm0NG4lhPx=4(G!V z60hP3-lTkp(WU-Ijj>`v16><3TrJP8p|d@8Xq z6%m!jR|rJ7-(J&?$X^k46VGFHhmb`F&!VL0>{uET zLK>2)j9;5rDNgQ!K(hz%e9~@Q5-zidc1g;$iC#sClBe(A2?s5dD^?c z)9vV%z%q5r=4TTOx*Q$Np61Po;bQ-w_#oI2)pY;6C4x>`5&>-)`me6!34PL%@g)8d zp9LrHdOx~rM>eudn(k`h?dZ!9?Nsj5BPRlGuXl@{) z_k>6oBKcbax-*DNi3FQ{p<44#lo+e(`h10q#$d|P#SMRi?!M>pl8Cz@(5})GU|gF1 z2jUJT*xZLGS;f1Wx7mhv&jD?eIG1os#cv5W4Y^gC%cwS*>s}Hr_O5^%-it|(xl3OX zGYZi9i4XAZ?k9+w{y-`XhIWPic^X!l?+HGI&5M71E z97+M~lmPtxU4V4v?S;4fc)Je&>uGRZwG4PyrN4MjQJwG+{%r^4+_X6jikxl#1++^= A4FCWD literal 0 HcmV?d00001 diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..0d38e46 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,31 @@ +{ + "name": "Aframp - Buy Crypto, Pay Bills & Send Money in Africa", + "short_name": "Aframp", + "description": "Africa's premier cNGN stablecoin payment platform. Buy crypto from ₦2,000, pay bills instantly, and send money across 12 African countries.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "portrait-primary", + "background_color": "#0a0a0a", + "theme_color": "#10b981", + "icons": [ + { + "src": "/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..07a663d --- /dev/null +++ b/public/sw.js @@ -0,0 +1,34 @@ +const CACHE_NAME = 'aframp-static-v1' + +self.addEventListener('install', (event) => { + self.skipWaiting() +}) + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))) + ).then(() => self.clients.claim()) + ) +}) + +self.addEventListener('fetch', (event) => { + if (event.request.method !== 'GET') return + + const url = new URL(event.request.url) + if (url.origin !== self.location.origin) return + if (url.pathname.startsWith('/api/')) return + + event.respondWith( + caches.open(CACHE_NAME).then((cache) => + fetch(event.request) + .then((response) => { + if (response.ok) { + cache.put(event.request, response.clone()) + } + return response + }) + .catch(() => cache.match(event.request)) + ) + ) +})