chore: remove PWA installation banner, associated hooks, manifest, and PWA metadata from the application.

This commit is contained in:
kuekhaoyang
2026-01-02 14:08:50 +08:00
parent cb0dee9469
commit e9d060f74a
6 changed files with 3 additions and 199 deletions
-10
View File
@@ -5,7 +5,6 @@ import { ThemeProvider } from "@/components/ThemeProvider";
import { Analytics } from "@vercel/analytics/react";
import { ServiceWorkerRegister } from "@/components/ServiceWorkerRegister";
import { PasswordGate } from "@/components/PasswordGate";
import { PWAInstallBanner } from "@/components/PWAInstallBanner";
import { siteConfig } from "@/lib/config/site-config";
@@ -24,14 +23,6 @@ export const metadata: Metadata = {
description: siteConfig.description,
icons: {
icon: '/icon.png',
apple: '/icon.png',
},
manifest: '/manifest.json',
themeColor: '#000000',
appleWebApp: {
capable: true,
statusBarStyle: 'black-translucent',
title: siteConfig.name,
},
};
@@ -47,7 +38,6 @@ export default function RootLayout({
suppressHydrationWarning
>
<ThemeProvider>
<PWAInstallBanner />
<PasswordGate hasEnvPassword={!!process.env.ACCESS_PASSWORD}>
{children}
</PasswordGate>
-83
View File
@@ -1,83 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { usePWAInstall } from '@/lib/hooks/usePWAInstall';
import { X, Download, Share } from 'lucide-react';
import { Button } from '@/components/ui/Button';
export function PWAInstallBanner() {
const { isInstallable, isIOS, handleInstallClick } = usePWAInstall();
const [isVisible, setIsVisible] = useState(false);
const [isDismissed, setIsDismissed] = useState(false);
useEffect(() => {
// Show banner after a short delay to not be too intrusive
if ((isInstallable || isIOS) && !isDismissed) {
const timer = setTimeout(() => setIsVisible(true), 2000);
return () => clearTimeout(timer);
} else {
setIsVisible(false);
}
}, [isInstallable, isIOS, isDismissed]);
const handleDismiss = () => {
setIsVisible(false);
setIsDismissed(true);
// Optionally save to localStorage to not show again for a while
localStorage.setItem('pwa-banner-dismissed', Date.now().toString());
};
useEffect(() => {
const dismissedAt = localStorage.getItem('pwa-banner-dismissed');
if (dismissedAt) {
const oneDay = 24 * 60 * 60 * 1000;
if (Date.now() - parseInt(dismissedAt) < oneDay) {
setIsDismissed(true);
}
}
}, []);
if (!isVisible) return null;
return (
<div className="fixed top-0 left-0 right-0 z-[100] p-4 animate-in fade-in slide-in-from-top duration-500">
<div className="max-w-xl mx-auto backdrop-blur-xl bg-white/10 dark:bg-black/40 border border-white/20 dark:border-white/10 rounded-2xl shadow-2xl p-4 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary/20 flex items-center justify-center overflow-hidden flex-shrink-0">
<img src="/icon.png" alt="KVideo" className="w-full h-full object-cover" />
</div>
<div>
<h3 className="text-sm font-semibold text-white"> KVideo</h3>
<p className="text-xs text-white/60">
{isIOS ? '点击浏览器分享按钮,选择“添加到主屏幕”' : '安装到桌面,体验更流畅'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{!isIOS && (
<Button
size="sm"
onClick={handleInstallClick}
className="bg-white text-black hover:bg-white/90 rounded-full px-4"
>
<Download className="w-3.5 h-3.5 mr-1.5" />
</Button>
)}
{isIOS && (
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-white/10">
<Share className="w-4 h-4 text-white" />
</div>
)}
<button
onClick={handleDismiss}
className="p-2 hover:bg-white/10 rounded-full transition-colors"
>
<X className="w-4 h-4 text-white/40" />
</button>
</div>
</div>
</div>
);
}
-86
View File
@@ -1,86 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
interface BeforeInstallPromptEvent extends Event {
readonly platforms: string[];
readonly userChoice: Promise<{
outcome: 'accepted' | 'dismissed';
platform: string;
}>;
prompt(): Promise<void>;
}
export function usePWAInstall() {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
const [isInstallable, setIsInstallable] = useState(false);
const [isInstalled, setIsInstalled] = useState(false);
const [isIOS, setIsIOS] = useState(false);
const [isStandalone, setIsStandalone] = useState(false);
useEffect(() => {
// Check if app is already installed/standalone
const checkStandalone = () => {
const isStandaloneMode = window.matchMedia('(display-mode: standalone)').matches
|| (window.navigator as any).standalone
|| document.referrer.includes('android-app://');
setIsStandalone(isStandaloneMode);
setIsInstalled(isStandaloneMode);
};
checkStandalone();
// Check if iOS
const checkIOS = () => {
const ua = window.navigator.userAgent.toLowerCase();
const isIOSDevice = /iphone|ipad|ipod/.test(ua);
setIsIOS(isIOSDevice);
};
checkIOS();
const handleBeforeInstallPrompt = (e: Event) => {
// Prevent the mini-infobar from appearing on mobile
e.preventDefault();
// Stash the event so it can be triggered later.
setDeferredPrompt(e as BeforeInstallPromptEvent);
setIsInstallable(true);
};
const handleAppInstalled = () => {
setIsInstalled(true);
setIsInstallable(false);
setDeferredPrompt(null);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
window.addEventListener('appinstalled', handleAppInstalled);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
window.removeEventListener('appinstalled', handleAppInstalled);
};
}, []);
const handleInstallClick = async () => {
if (!deferredPrompt) return;
// Show the install prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
setIsInstallable(false);
setDeferredPrompt(null);
}
};
return {
isInstallable: isInstallable && !isInstalled,
isIOS: isIOS && !isStandalone,
isInstalled,
handleInstallClick,
};
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "kvideo",
"version": "3.9.0",
"version": "3.8.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvideo",
"version": "3.9.0",
"version": "3.8.9",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "3.9.0",
"version": "3.8.9",
"private": true,
"scripts": {
"dev": "next dev",
-17
View File
@@ -1,17 +0,0 @@
{
"name": "KVideo - 视频聚合平台",
"short_name": "KVideo",
"description": "视频聚合平台",
"start_url": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000",
"icons": [
{
"src": "/icon.png",
"sizes": "1024x1024",
"type": "image/png",
"purpose": "any maskable"
}
]
}