Refine onboarding UI and fix dashboard checklist progress
This commit is contained in:
@@ -16,6 +16,10 @@ import { useCsrf } from '@/hooks/useCsrf';
|
||||
import { showToast } from '@/components/ui/Toast';
|
||||
import { trackEvent } from '@/components/PostHogProvider';
|
||||
import { appendRedirectParam, sanitizeRedirectPath } from '@/lib/auth-flow';
|
||||
import {
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_KEY,
|
||||
} from '@/lib/revops';
|
||||
import {
|
||||
Globe, User, MapPin, Phone, FileText, Smartphone, Ticket, Star, HelpCircle, Upload, Barcode as BarcodeIcon
|
||||
} from 'lucide-react';
|
||||
@@ -149,6 +153,15 @@ export default function CreatePage() {
|
||||
|
||||
// QR preview
|
||||
const [qrDataUrl, setQrDataUrl] = useState('');
|
||||
|
||||
const markDownloadComplete = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY, '1');
|
||||
window.dispatchEvent(new CustomEvent(ONBOARDING_DOWNLOAD_COMPLETE_EVENT));
|
||||
};
|
||||
|
||||
// Check if user can customize colors (PRO+ only)
|
||||
const canCustomizeColors = userPlan === 'PRO' || userPlan === 'BUSINESS';
|
||||
@@ -255,7 +268,8 @@ export default function CreatePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const qrContent = getQRContent();
|
||||
const qrContent = getQRContent();
|
||||
const previewScale = contentType === 'BARCODE' ? 1 : Math.min(1, 240 / Math.max(size, 1));
|
||||
|
||||
const getFrameLabel = () => {
|
||||
const frame = frameOptions.find((f: { id: string; label: string }) => f.id === frameType);
|
||||
@@ -271,6 +285,7 @@ export default function CreatePage() {
|
||||
link.download = `qrcode-${title || 'download'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
markDownloadComplete();
|
||||
trackEvent('qr_code_downloaded', {
|
||||
format: 'png',
|
||||
content_type: contentType,
|
||||
@@ -304,6 +319,7 @@ export default function CreatePage() {
|
||||
a.download = `qrcode-${title || 'download'}.svg`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
markDownloadComplete();
|
||||
trackEvent('qr_code_downloaded', {
|
||||
format: 'svg',
|
||||
content_type: contentType,
|
||||
@@ -318,6 +334,7 @@ export default function CreatePage() {
|
||||
link.download = `qrcode-${title || 'download'}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
markDownloadComplete();
|
||||
trackEvent('qr_code_downloaded', {
|
||||
format: 'png',
|
||||
content_type: contentType,
|
||||
@@ -1162,13 +1179,13 @@ export default function CreatePage() {
|
||||
<CardContent className="text-center">
|
||||
<div id="create-qr-preview" className="flex justify-center mb-4 w-full min-w-0 overflow-hidden">
|
||||
{/* WRAPPER FOR REF AND FRAME */}
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="relative bg-white rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 w-full max-w-full min-w-0"
|
||||
style={{
|
||||
minHeight: '280px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={qrRef}
|
||||
className="relative flex w-full min-w-0 max-w-full flex-col items-center justify-center rounded-xl bg-white p-3 transition-all duration-300 sm:p-4"
|
||||
style={{
|
||||
minHeight: '220px',
|
||||
}}
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{getFrameLabel() && (
|
||||
<div
|
||||
@@ -1203,11 +1220,17 @@ export default function CreatePage() {
|
||||
Enter barcode value
|
||||
</div>
|
||||
)
|
||||
) : qrContent ? (
|
||||
<div className={cornerStyle === 'rounded' ? 'rounded-lg overflow-hidden' : ''}>
|
||||
<QRCodeSVG
|
||||
value={qrContent}
|
||||
size={size}
|
||||
) : qrContent ? (
|
||||
<div
|
||||
className={cornerStyle === 'rounded' ? 'overflow-hidden rounded-lg' : ''}
|
||||
style={{
|
||||
transform: `scale(${previewScale})`,
|
||||
transformOrigin: 'center center',
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG
|
||||
value={qrContent}
|
||||
size={size}
|
||||
fgColor={foregroundColor}
|
||||
bgColor={backgroundColor}
|
||||
level="H"
|
||||
|
||||
@@ -320,27 +320,11 @@ export default function DashboardPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const getPlanBadgeColor = (plan: string) => {
|
||||
switch (plan) {
|
||||
case 'PRO':
|
||||
return 'info';
|
||||
case 'BUSINESS':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const getPlanEmoji = (plan: string) => {
|
||||
// No emojis anymore
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header with Plan Badge */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900">{t('dashboard.title')}</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
{!loading && qrCodes.length === 0
|
||||
@@ -348,17 +332,17 @@ export default function DashboardPage() {
|
||||
: t('dashboard.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Badge variant={getPlanBadgeColor(userPlan)} className="text-lg px-4 py-2">
|
||||
{userPlan} Plan
|
||||
</Badge>
|
||||
{userPlan === 'FREE' && (
|
||||
<Link href="/pricing">
|
||||
<Button variant="primary">Upgrade</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge className="border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700">
|
||||
{userPlan} Plan
|
||||
</Badge>
|
||||
{userPlan === 'FREE' && (
|
||||
<Link href="/pricing">
|
||||
<Button className="bg-slate-900 text-white hover:bg-slate-800">Upgrade</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<OnboardingChecklist state={onboardingState} />
|
||||
@@ -373,9 +357,9 @@ export default function DashboardPage() {
|
||||
|
||||
{/* Recent QR Codes */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900">{t('dashboard.recent_codes')}</h2>
|
||||
<div className="flex gap-3">
|
||||
<div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">{t('dashboard.recent_codes')}</h2>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{qrCodes.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -385,12 +369,12 @@ export default function DashboardPage() {
|
||||
>
|
||||
{deletingAll ? 'Deleting...' : 'Delete All'}
|
||||
</Button>
|
||||
)}
|
||||
<Link href="/create">
|
||||
<Button>Create New QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Link href="/create">
|
||||
<Button className="bg-slate-900 text-white hover:bg-slate-800">Create New QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -410,14 +394,14 @@ export default function DashboardPage() {
|
||||
))}
|
||||
</div>
|
||||
) : qrCodes.length === 0 ? (
|
||||
<div className="text-center py-16 border-2 border-dashed border-gray-200 rounded-xl">
|
||||
<div className="rounded-[24px] border border-dashed border-gray-200 py-16 text-center">
|
||||
<QrCode className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-gray-700 mb-2">Create your first QR code</h3>
|
||||
<p className="text-gray-500 mb-6 max-w-sm mx-auto">
|
||||
You have {FREE_DYNAMIC_QR_LIMIT} free dynamic QR codes. They redirect wherever you want and track every scan.
|
||||
</p>
|
||||
<Link href="/create">
|
||||
<Button>Create QR Code — it takes 90 seconds</Button>
|
||||
<Button className="bg-slate-900 text-white hover:bg-slate-800">Create QR Code — it takes 90 seconds</Button>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user