- add optional collection_photo field to OrderItem type and backend support for pickup photo uploads - implement in-memory + localStorage cache for order items to eliminate tab-switch loading skeletons - update service worker to use stale-while-revalidate for navigation requests to reduce perceived lag - fix LocationTracker to only activate when logged in and hoist to root layout for persistent tracking - adjust InstallPrompt positioning to avoid overlapping bottom navigation - throttle map cluster recomputation to reduce unnecessary work during pan/zoom - clear cached items on logout to prevent cross-driver data leakage
69 lines
No EOL
3 KiB
TypeScript
69 lines
No EOL
3 KiB
TypeScript
'use client';
|
|
|
|
import { usePWAInstall } from '@/lib/pwa';
|
|
import { Download, X, Smartphone } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { useState } from 'react';
|
|
|
|
export default function InstallPrompt() {
|
|
const { isInstallable, isInstalled, install, dismiss } = usePWAInstall();
|
|
const [isDismissed, setIsDismissed] = useState(false);
|
|
|
|
if (isInstalled || isDismissed || !isInstallable) return null;
|
|
|
|
return (
|
|
// bottom-28 = 7rem = 112px. Sits clearly above the 64px bottom nav
|
|
// + 3px brand-accent + safe-area padding, with ~40px of visual gap
|
|
// so the prompt never reads as part of the chrome. Previously
|
|
// bottom-20 (80px) put the prompt's bottom edge flush with the
|
|
// nav's top, which made it look like the nav was being covered.
|
|
//
|
|
// pointer-events: none on the outer container so taps on the
|
|
// empty card area pass through to whatever's underneath (the map,
|
|
// a list, etc.) — only the actual buttons stay clickable.
|
|
<div className="fixed bottom-28 left-4 right-4 z-[70] animate-fade-in" style={{ pointerEvents: 'none' }}>
|
|
<div
|
|
className="bg-[#e6e5e0] rounded-2xl p-4 border border-[rgba(38,37,30,0.1)]"
|
|
style={{
|
|
boxShadow: '0 28px 70px rgba(0,0,0,0.14), 0 14px 32px rgba(0,0,0,0.1)',
|
|
pointerEvents: 'auto',
|
|
}}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<div className="w-10 h-10 bg-[#ebeae5] rounded-xl flex items-center justify-center flex-shrink-0">
|
|
<Smartphone size={20} className="text-[#26251e]" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-sm font-semibold text-[#26251e]">Install 1Driver</h3>
|
|
<p className="text-xs text-[rgba(38,37,30,0.55)] mt-0.5">
|
|
Add to home screen for the best experience
|
|
</p>
|
|
<div className="flex items-center gap-2 mt-3">
|
|
<button
|
|
onClick={install}
|
|
className="flex items-center gap-1.5 px-4 py-2 bg-[#f54e00] text-white rounded-lg text-sm font-medium active:bg-[#d44200] transition-colors touch-manipulation"
|
|
style={{ boxShadow: '0 4px 12px rgba(245,78,0,0.3)' }}
|
|
>
|
|
<Download size={14} />
|
|
Install
|
|
</button>
|
|
<button
|
|
onClick={() => { dismiss(); setIsDismissed(true); }}
|
|
className="px-3 py-2 text-sm text-[rgba(38,37,30,0.55)] font-medium active:bg-[rgba(38,37,30,0.06)] rounded-lg transition-colors touch-manipulation"
|
|
>
|
|
Not now
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => { dismiss(); setIsDismissed(true); }}
|
|
aria-label="Dismiss install prompt"
|
|
className="p-1 text-[rgba(38,37,30,0.55)] hover:text-[#26251e] active:bg-[rgba(38,37,30,0.06)] rounded-lg transition-colors"
|
|
>
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |