feat: enhance install prompt with platform detection and animations
This commit is contained in:
parent
be30c6fbd5
commit
be4436cae8
3
.gitignore
vendored
3
.gitignore
vendored
@ -35,4 +35,5 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
e2b.toml
|
e2b.toml
|
||||||
|
certificates
|
||||||
@ -1,40 +1,139 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { X, Share, Plus, Download } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
|
||||||
export function InstallPrompt() {
|
export function InstallPrompt() {
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
const [showPrompt, setShowPrompt] = useState(false);
|
||||||
const [isStandalone, setIsStandalone] = useState(false);
|
const [platform, setPlatform] = useState<'ios' | 'android' | 'chrome' | 'other'>('other');
|
||||||
|
const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsIOS(
|
const isDismissed = localStorage.getItem('installPromptDismissed');
|
||||||
/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
|
if (isDismissed) return;
|
||||||
);
|
|
||||||
|
|
||||||
setIsStandalone(window.matchMedia('(display-mode: standalone)').matches);
|
// Detect platform
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
const isIOSDevice = /ipad|iphone|ipod/.test(userAgent) && !(window as any).MSStream;
|
||||||
|
const isAndroid = /android/.test(userAgent);
|
||||||
|
const isChrome = /chrome/.test(userAgent) && /google inc/.test(navigator.vendor.toLowerCase());
|
||||||
|
|
||||||
|
if (isIOSDevice) setPlatform('ios');
|
||||||
|
else if (isAndroid) setPlatform('android');
|
||||||
|
else if (isChrome) setPlatform('chrome');
|
||||||
|
|
||||||
|
// Don't show if already installed
|
||||||
|
if (window.matchMedia('(display-mode: standalone)').matches) return;
|
||||||
|
|
||||||
|
// Handle PWA install prompt
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDeferredPrompt(e);
|
||||||
|
setShowPrompt(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show prompt for iOS after delay
|
||||||
|
if (isIOSDevice) {
|
||||||
|
setTimeout(() => setShowPrompt(true), 2000);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isStandalone) {
|
const handleDismiss = () => {
|
||||||
return null;
|
setShowPrompt(false);
|
||||||
}
|
localStorage.setItem('installPromptDismissed', 'true');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstall = async () => {
|
||||||
|
if (!deferredPrompt) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deferredPrompt.prompt();
|
||||||
|
const choiceResult = await deferredPrompt.userChoice;
|
||||||
|
|
||||||
|
if (choiceResult.outcome === 'accepted') {
|
||||||
|
setShowPrompt(false);
|
||||||
|
setDeferredPrompt(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Install prompt error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInstructions = () => {
|
||||||
|
switch (platform) {
|
||||||
|
case 'ios':
|
||||||
|
return (
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
Tap <Share className="inline h-4 w-4 mx-1" /> and then{" "}
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
“Add to Home Screen” <Plus className="inline h-4 w-4 ml-1" />
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case 'android':
|
||||||
|
return (
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
Tap the menu <span className="font-bold">⋮</span> and select “Install app”
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
Install our app for a better experience <Download className="inline h-4 w-4 ml-1" />
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<AnimatePresence>
|
||||||
<h3>Install App</h3>
|
{showPrompt && (
|
||||||
<button>Add to Home Screen</button>
|
<motion.div
|
||||||
{isIOS && (
|
initial={{ opacity: 0, y: 50 }}
|
||||||
<p>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
To install this app on your iOS device, tap the share button
|
exit={{ opacity: 0, y: 50 }}
|
||||||
<span role="img" aria-label="share icon">
|
className="fixed bottom-4 left-4 right-4 z-[100] md:left-auto md:right-4 md:w-96"
|
||||||
{' '}
|
>
|
||||||
⎋{' '}
|
<Card className="p-4 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 shadow-lg">
|
||||||
</span>
|
<div className="flex items-start justify-between">
|
||||||
and then "Add to Home Screen"
|
<div className="space-y-2">
|
||||||
<span role="img" aria-label="plus icon">
|
<h3 className="font-medium text-lg text-neutral-900 dark:text-neutral-100">
|
||||||
{' '}
|
Install MiniPerplx
|
||||||
➕{' '}
|
</h3>
|
||||||
</span>
|
{getInstructions()}
|
||||||
.
|
</div>
|
||||||
</p>
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-6 w-6"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Dismiss</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{platform !== 'ios' && (
|
||||||
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
>
|
||||||
|
Maybe later
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={handleInstall}
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user