mirror of
https://github.com/fccview/cronmaster.git
synced 2025-12-23 22:18:20 -05:00
fix pwa fully
This commit is contained in:
58
app/_components/ui/PWAInstallPrompt.tsx
Normal file
58
app/_components/ui/PWAInstallPrompt.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
type BeforeInstallPromptEvent = Event & {
|
||||
prompt: () => Promise<void>;
|
||||
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
||||
};
|
||||
|
||||
export const PWAInstallPrompt = (): JSX.Element | null => {
|
||||
const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(
|
||||
null
|
||||
);
|
||||
const [isInstalled, setIsInstalled] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const onBeforeInstallPrompt = (e: Event) => {
|
||||
e.preventDefault();
|
||||
setDeferred(e as BeforeInstallPromptEvent);
|
||||
};
|
||||
const onAppInstalled = () => {
|
||||
setDeferred(null);
|
||||
setIsInstalled(true);
|
||||
};
|
||||
window.addEventListener("beforeinstallprompt", onBeforeInstallPrompt);
|
||||
window.addEventListener("appinstalled", onAppInstalled);
|
||||
if (window.matchMedia("(display-mode: standalone)").matches) {
|
||||
setIsInstalled(true);
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener("beforeinstallprompt", onBeforeInstallPrompt);
|
||||
window.removeEventListener("appinstalled", onAppInstalled);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onInstall = useCallback(async () => {
|
||||
if (!deferred) return;
|
||||
try {
|
||||
await deferred.prompt();
|
||||
const choice = await deferred.userChoice;
|
||||
if (choice.outcome === "accepted") {
|
||||
setDeferred(null);
|
||||
}
|
||||
} catch (_err) {}
|
||||
}, [deferred]);
|
||||
|
||||
if (isInstalled || !deferred) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
className="px-3 py-1 rounded-md border border-border/50 bg-background/80 hover:bg-background/60"
|
||||
onClick={onInstall}
|
||||
>
|
||||
Install App
|
||||
</button>
|
||||
);
|
||||
};
|
||||
23
app/_components/ui/ServiceWorkerRegister.tsx
Normal file
23
app/_components/ui/ServiceWorkerRegister.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const ServiceWorkerRegister = (): null => {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (!("serviceWorker" in navigator)) return;
|
||||
const register = async () => {
|
||||
try {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
const alreadyRegistered = registrations.some((r) =>
|
||||
r.scope.endsWith("/")
|
||||
);
|
||||
if (alreadyRegistered) return;
|
||||
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
|
||||
} catch (_err) {}
|
||||
};
|
||||
register();
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -92,7 +92,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="absolute -right-3 top-6 w-6 h-6 bg-background border border-border rounded-full items-center justify-center hover:bg-accent transition-colors z-40 hidden lg:flex"
|
||||
className="absolute -right-3 top-[21.5vh] w-6 h-6 bg-background border border-border rounded-full items-center justify-center hover:bg-accent transition-colors z-40 hidden lg:flex"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { JetBrains_Mono, Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "./_components/ui/ThemeProvider";
|
||||
import { ServiceWorkerRegister } from "./_components/ui/ServiceWorkerRegister";
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
subsets: ["latin"],
|
||||
@@ -55,24 +56,7 @@ export default function RootLayout({
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Cr*nMaster" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" href="/icon-192x192.png" />
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(function(registration) {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
.catch(function(registrationError) {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo.png" />
|
||||
</head>
|
||||
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans`}>
|
||||
<ThemeProvider
|
||||
@@ -83,6 +67,7 @@ export default function RootLayout({
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
<ServiceWorkerRegister />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { fetchScripts } from "./_server/actions/scripts";
|
||||
import { ThemeToggle } from "./_components/ui/ThemeToggle";
|
||||
import { LogoutButton } from "./_components/ui/LogoutButton";
|
||||
import { ToastContainer } from "./_components/ui/Toast";
|
||||
import { PWAInstallPrompt } from "./_components/ui/PWAInstallPrompt";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function Home() {
|
||||
@@ -90,6 +91,7 @@ export default async function Home() {
|
||||
|
||||
<div className="flex items-center gap-2 fixed bottom-4 left-4 lg:right-4 lg:left-auto z-10 bg-background/80 backdrop-blur-md border border-border/50 rounded-lg p-1">
|
||||
<ThemeToggle />
|
||||
<PWAInstallPrompt />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,6 @@ export function middleware(request: NextRequest) {
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
||||
"/((?!_next/static|_next/image|favicon.ico|site.webmanifest|sw.js|app-icons).*)",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2,11 +2,29 @@ const withPWA = require('next-pwa')({
|
||||
dest: 'public',
|
||||
register: true,
|
||||
skipWaiting: true,
|
||||
disable: process.env.NODE_ENV === 'development'
|
||||
disable: process.env.NODE_ENV === 'development',
|
||||
buildExcludes: [/middleware-manifest\.json$/]
|
||||
})
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/manifest.json',
|
||||
headers: [
|
||||
{ key: 'Content-Type', value: 'application/manifest+json' },
|
||||
],
|
||||
},
|
||||
{
|
||||
source: '/sw.js',
|
||||
headers: [
|
||||
{ key: 'Service-Worker-Allowed', value: '/' },
|
||||
{ key: 'Cache-Control', value: 'no-cache' },
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = withPWA(nextConfig)
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
"src": "/logo.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/logo.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"categories": ["productivity", "utilities"],
|
||||
|
||||
Reference in New Issue
Block a user