feat: enhance install prompt with platform detection and animations

This commit is contained in:
zaidmukaddam 2024-12-21 11:54:21 +05:30
parent be30c6fbd5
commit be4436cae8
2 changed files with 128 additions and 28 deletions

3
.gitignore vendored
View File

@ -35,4 +35,5 @@ yarn-error.log*
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
e2b.toml e2b.toml
certificates

View File

@ -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">
&ldquo;Add to Home Screen&rdquo; <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 &ldquo;Install app&rdquo;
</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 &quot;Add to Home Screen&quot; <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>
); );
} }