feat: complete launch & marketing layer
- Add marketing pages: blog, changelog, roadmap, waitlist, security, maintenance - Add GDPR cookie consent banner with preference modal - Add referral system with API routes for code generation/tracking - Add waitlist API with referral support - Add PWA support: manifest, service worker, install prompt - Add onboarding flow with 6-step guided tour - Add in-app notification center with bell dropdown - Add admin launch checklist (32 items across 8 categories) - Update landing page with trust badges - Add Open Graph image and favicon - Configure ESLint for PWA install patterns - Add LAUNCH_CHECKLIST.md with go-to-market guide Supabase migrations for: - blog_posts and blog_categories tables - waitlist_signups table - roadmap_items table - launch_checklist_items table
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// PWA Install Prompt Component
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface BeforeInstallPromptEvent extends Event {
|
||||
prompt(): Promise<void>;
|
||||
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
||||
}
|
||||
|
||||
export default function PWAInstallPrompt() {
|
||||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
||||
const [showPrompt, setShowPrompt] = useState(false);
|
||||
const [isInstalled, setIsInstalled] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const isPWA = window.matchMedia("(display-mode: standalone)").matches;
|
||||
if (isPWA) {
|
||||
setIsInstalled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const handleBeforeInstall = (e: Event) => {
|
||||
e.preventDefault();
|
||||
setDeferredPrompt(e as BeforeInstallPromptEvent);
|
||||
setTimeout(() => setShowPrompt(true), 10000);
|
||||
};
|
||||
|
||||
const handleAppInstalled = () => {
|
||||
setIsInstalled(true);
|
||||
setShowPrompt(false);
|
||||
setDeferredPrompt(null);
|
||||
};
|
||||
|
||||
window.addEventListener("beforeinstallprompt", handleBeforeInstall);
|
||||
window.addEventListener("appinstalled", handleAppInstalled);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("beforeinstallprompt", handleBeforeInstall);
|
||||
window.removeEventListener("appinstalled", handleAppInstalled);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleInstall = async () => {
|
||||
if (!deferredPrompt) return;
|
||||
|
||||
await deferredPrompt.prompt();
|
||||
const { outcome } = await deferredPrompt.userChoice;
|
||||
|
||||
if (outcome === "accepted") {
|
||||
setShowPrompt(false);
|
||||
}
|
||||
setDeferredPrompt(null);
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
setShowPrompt(false);
|
||||
sessionStorage.setItem("pwa-install-dismissed", "true");
|
||||
};
|
||||
|
||||
if (!mounted || !showPrompt || isInstalled || !deferredPrompt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 left-4 right-4 sm:left-auto sm:right-4 sm:w-80 bg-white rounded-2xl shadow-2xl border border-gray-200 p-4 z-50">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-emerald-600 to-emerald-500 rounded-xl flex items-center justify-center shrink-0">
|
||||
<svg className="w-6 h-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-gray-900">Install Route Commerce</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">Add to your home screen for quick access</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="flex-1 px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors"
|
||||
>
|
||||
Not now
|
||||
</button>
|
||||
<button
|
||||
onClick={handleInstall}
|
||||
className="flex-1 px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm font-medium hover:bg-emerald-500 transition-colors"
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user