Merge pull request #65 from multica-ai/feat/pwa-support
feat(web): add PWA support
This commit is contained in:
commit
d6f79d2df6
8 changed files with 144 additions and 0 deletions
|
|
@ -10,6 +10,7 @@ import { AppSidebar } from "@multica/ui/components/app-sidebar";
|
|||
import { ThemeProvider } from "@multica/ui/components/theme-provider";
|
||||
import { Toaster } from "@multica/ui/components/ui/sonner";
|
||||
import { HubSidebar } from "@multica/ui/components/hub-sidebar";
|
||||
import { ServiceWorkerRegister } from "./sw-register";
|
||||
|
||||
const gatewayUrl = process.env.NEXT_PUBLIC_GATEWAY_URL;
|
||||
if (gatewayUrl) {
|
||||
|
|
@ -37,6 +38,14 @@ const playfair = Playfair_Display({
|
|||
export const metadata: Metadata = {
|
||||
title: "Multica",
|
||||
description: "Distributed AI agent framework",
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
statusBarStyle: "black-translucent",
|
||||
title: "Multica",
|
||||
},
|
||||
icons: {
|
||||
apple: "/icon-192x192.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
@ -65,6 +74,7 @@ export default function RootLayout({
|
|||
</SidebarProvider>
|
||||
</ThemeProvider>
|
||||
<Toaster />
|
||||
<ServiceWorkerRegister />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
35
apps/web/app/manifest.ts
Normal file
35
apps/web/app/manifest.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "Multica",
|
||||
short_name: "Multica",
|
||||
description: "Distributed AI agent framework",
|
||||
id: "/",
|
||||
scope: "/",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
orientation: "any",
|
||||
background_color: "#09090b",
|
||||
theme_color: "#09090b",
|
||||
icons: [
|
||||
{
|
||||
src: "/icon-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/icon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any",
|
||||
},
|
||||
{
|
||||
src: "/icon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
22
apps/web/app/sw-register.tsx
Normal file
22
apps/web/app/sw-register.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function ServiceWorkerRegister() {
|
||||
useEffect(() => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js", {
|
||||
scope: "/",
|
||||
updateViaCache: "none",
|
||||
})
|
||||
.catch((err) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("SW registration failed:", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
22
apps/web/app/~offline/page.tsx
Normal file
22
apps/web/app/~offline/page.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export default function OfflinePage() {
|
||||
return (
|
||||
<div
|
||||
className="flex h-dvh w-full items-center justify-center"
|
||||
style={{ display: "flex", height: "100dvh", width: "100%", alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<div
|
||||
className="flex flex-col items-center gap-4 text-center"
|
||||
style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "1rem", textAlign: "center" }}
|
||||
>
|
||||
<div className="text-4xl" style={{ fontSize: "2.25rem" }}>*</div>
|
||||
<h1 className="text-xl font-semibold" style={{ fontSize: "1.25rem", fontWeight: 600 }}>
|
||||
You are offline
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground" style={{ fontSize: "0.875rem", color: "#a1a1aa" }}>
|
||||
Multica requires an internet connection. Please check your network and
|
||||
try again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,6 +2,21 @@ import type { NextConfig } from "next";
|
|||
|
||||
const nextConfig: NextConfig = {
|
||||
transpilePackages: ["@multica/ui", "@multica/store"],
|
||||
headers: async () => [
|
||||
{
|
||||
source: "/sw.js",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value: "no-cache, no-store, must-revalidate",
|
||||
},
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "application/javascript; charset=utf-8",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
BIN
apps/web/public/icon-192x192.png
Normal file
BIN
apps/web/public/icon-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
apps/web/public/icon-512x512.png
Normal file
BIN
apps/web/public/icon-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 271 KiB |
40
apps/web/public/sw.js
Normal file
40
apps/web/public/sw.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const CACHE_NAME = "multica-v1";
|
||||
|
||||
// App shell resources to precache
|
||||
const PRECACHE_URLS = ["/~offline"];
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
// Clean up old caches
|
||||
event.waitUntil(
|
||||
caches.keys().then((names) =>
|
||||
Promise.all(
|
||||
names
|
||||
.filter((name) => name !== CACHE_NAME)
|
||||
.map((name) => caches.delete(name))
|
||||
)
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
// Only handle navigation requests (HTML pages)
|
||||
if (event.request.mode === "navigate") {
|
||||
event.respondWith(
|
||||
fetch(event.request).catch(() =>
|
||||
caches.match("/~offline").then(
|
||||
(response) =>
|
||||
response ||
|
||||
new Response("Offline", { status: 503, statusText: "Offline" })
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue