Refine onboarding UI and fix dashboard checklist progress
This commit is contained in:
@@ -3,10 +3,14 @@
|
||||
import React from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import Barcode from 'react-barcode';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Dropdown, DropdownItem } from '@/components/ui/Dropdown';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Dropdown, DropdownItem } from '@/components/ui/Dropdown';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
import {
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_EVENT,
|
||||
ONBOARDING_DOWNLOAD_COMPLETE_KEY,
|
||||
} from '@/lib/revops';
|
||||
|
||||
function addBarcodeCaptionToSvg(svgElement: SVGElement, caption: string): string {
|
||||
const cloned = svgElement.cloneNode(true) as SVGElement;
|
||||
@@ -59,12 +63,21 @@ interface QRCodeCardProps {
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
export const QRCodeCard: React.FC<QRCodeCardProps> = ({
|
||||
qr,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}) => {
|
||||
// For dynamic QR codes, use the redirect URL for tracking
|
||||
export const QRCodeCard: React.FC<QRCodeCardProps> = ({
|
||||
qr,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}) => {
|
||||
const markDownloadComplete = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(ONBOARDING_DOWNLOAD_COMPLETE_KEY, '1');
|
||||
window.dispatchEvent(new CustomEvent(ONBOARDING_DOWNLOAD_COMPLETE_EVENT));
|
||||
};
|
||||
|
||||
// For dynamic QR codes, use the redirect URL for tracking
|
||||
// For static QR codes, use the direct URL from content
|
||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3050');
|
||||
|
||||
@@ -125,10 +138,11 @@ END:VCARD`;
|
||||
pixelRatio: 3,
|
||||
backgroundColor: '#ffffff' // White background for clean export
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
const link = document.createElement('a');
|
||||
link.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
markDownloadComplete();
|
||||
} else {
|
||||
// For SVG, if no frame, export just the QR code SVG for vector quality
|
||||
// If frame exists, use toPng as fallback since HTML-to-SVG is complex
|
||||
@@ -139,10 +153,11 @@ END:VCARD`;
|
||||
pixelRatio: 3,
|
||||
backgroundColor: '#ffffff'
|
||||
});
|
||||
const link = document.createElement('a');
|
||||
link.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
const link = document.createElement('a');
|
||||
link.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.png`;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
markDownloadComplete();
|
||||
} else {
|
||||
// No frame - export clean SVG from the svg wrapper
|
||||
const svgContainer = document.querySelector(`#qr-svg-${qr.id}`);
|
||||
@@ -170,11 +185,12 @@ END:VCARD`;
|
||||
|
||||
const blob = new Blob([svgData], { type: 'image/svg+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.svg`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${qr.title.replace(/\s+/g, '-').toLowerCase()}.svg`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
markDownloadComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,16 +200,16 @@ END:VCARD`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card hover>
|
||||
<CardContent className="p-4">
|
||||
<Card hover className="rounded-[24px] border-slate-200 p-0 shadow-none">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-gray-900 mb-1">{qr.title}</h3>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant={qr.type === 'DYNAMIC' ? 'info' : 'default'}>
|
||||
{qr.type}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge className={qr.type === 'DYNAMIC' ? 'bg-slate-900 text-white' : 'bg-slate-100 text-slate-700'}>
|
||||
{qr.type}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dropdown
|
||||
@@ -218,9 +234,12 @@ END:VCARD`;
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center bg-gray-50 rounded-lg p-4 mb-3">
|
||||
{/* Download wrapper - tightly wraps content */}
|
||||
<div id={`qr-download-${qr.id}`} className="inline-flex flex-col items-center bg-white p-4 rounded-xl">
|
||||
<div className="mb-3 flex flex-col items-center justify-center rounded-[20px] bg-slate-50 p-4">
|
||||
{/* Download wrapper - tightly wraps content */}
|
||||
<div
|
||||
id={`qr-download-${qr.id}`}
|
||||
className="inline-flex flex-col items-center rounded-[20px] border border-slate-100 bg-white p-4"
|
||||
>
|
||||
{/* Frame Label */}
|
||||
{qr.style?.frameType && qr.style.frameType !== 'none' && (
|
||||
<div
|
||||
@@ -299,7 +318,7 @@ END:VCARD`;
|
||||
<span className="text-gray-900">{formatDate(qr.createdAt)}</span>
|
||||
</div>
|
||||
{qr.type === 'DYNAMIC' && (
|
||||
<div className="pt-2 border-t">
|
||||
<div className="border-t border-slate-100 pt-2">
|
||||
<p className="text-xs text-gray-500">
|
||||
📊 Dynamic QR: Tracks scans via {baseUrl}/r/{qr.slug}
|
||||
</p>
|
||||
@@ -318,4 +337,4 @@ END:VCARD`;
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user