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:
2026-06-02 06:19:56 +00:00
parent f6d2dc4e6c
commit 7dfaba6e3d
46 changed files with 4065 additions and 2188 deletions
+99
View File
@@ -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>
);
}