dispatch page
This commit is contained in:
146
src/components/StoreQRView.tsx
Normal file
146
src/components/StoreQRView.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { QRCodeSVG, QRCodeCanvas } from 'qrcode.react';
|
||||
import { MapPin, ScanLine, AlertTriangle, Download } from 'lucide-react';
|
||||
import { buildStoreQrPayload } from '../services/fiestaApi';
|
||||
|
||||
interface StoreQRViewProps {
|
||||
/** Merchant tenant the QR is scoped to. */
|
||||
tenantId: number;
|
||||
/** Resolved outlet id. When absent/0, the QR can't be scoped to a store. */
|
||||
locationid?: number;
|
||||
/** Outlet display name (shown on the printable card). */
|
||||
storeName: string;
|
||||
/** Zone label, e.g. "R.S. Puram, Coimbatore". */
|
||||
storeZone?: string;
|
||||
/** Full street address, when available. */
|
||||
storeAddress?: string;
|
||||
}
|
||||
|
||||
const QR_FG = '#000000';
|
||||
const QR_BG = '#ffffff';
|
||||
const QR_MARGIN = 4;
|
||||
const QR_LEVEL = 'Q' as const;
|
||||
|
||||
export default function StoreQRView({
|
||||
tenantId,
|
||||
locationid,
|
||||
storeName,
|
||||
storeZone,
|
||||
storeAddress,
|
||||
}: StoreQRViewProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const payload = useMemo(
|
||||
() => (locationid ? buildStoreQrPayload({ tenantid: tenantId, locationid }) : ''),
|
||||
[tenantId, locationid],
|
||||
);
|
||||
|
||||
const slug = useMemo(() => {
|
||||
return storeName
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '') || 'store';
|
||||
}, [storeName]);
|
||||
|
||||
const downloadPng = () => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const a = document.createElement('a');
|
||||
a.href = canvas.toDataURL('image/png');
|
||||
a.download = `${slug}-qr.png`;
|
||||
a.click();
|
||||
};
|
||||
|
||||
if (!locationid) {
|
||||
return (
|
||||
<div className="max-w-xl mx-auto animate-in fade-in duration-300">
|
||||
<div className="bg-white border border-slate-200/70 rounded-3xl p-10 text-center shadow-[0_10px_40px_rgba(0,0,0,0.06)]">
|
||||
<div className="mx-auto h-16 w-16 rounded-2xl bg-amber-50 text-amber-600 ring-1 ring-amber-100 flex items-center justify-center mb-6">
|
||||
<AlertTriangle size={30} />
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-slate-900 tracking-tight mb-2">No store to encode</h1>
|
||||
<p className="text-sm text-slate-500 leading-relaxed">
|
||||
This QR opens a specific outlet's storefront, but no outlet is resolved yet. Once a store
|
||||
location is assigned, its scannable code appears here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex flex-col items-center" style={{ width: '100%', maxWidth: '380px', minWidth: '280px' }}>
|
||||
|
||||
{/* Premium Table Tent Mockup Card */}
|
||||
<div className="bg-white rounded-3xl border border-slate-200/70 shadow-[0_20px_50px_rgba(88,28,135,0.08)] overflow-hidden relative group transition-all duration-355 hover:shadow-[0_25px_60px_rgba(88,28,135,0.14)]" style={{ width: '100%' }}>
|
||||
|
||||
{/* Ambient Background Glow inside card */}
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-purple-500/5 rounded-full blur-xl -mr-6 -mt-6 pointer-events-none group-hover:bg-purple-500/10 transition-colors" />
|
||||
|
||||
{/* Gradient Header banner */}
|
||||
<div className="relative bg-gradient-to-br from-[#581c87] via-[#6b21a8] to-purple-950 px-6 py-8 text-center text-white overflow-hidden">
|
||||
<div className="absolute -top-12 -right-10 w-36 h-36 bg-purple-400/25 rounded-full blur-2xl pointer-events-none" />
|
||||
<span className="relative inline-flex items-center gap-1.5 text-[9px] font-extrabold uppercase tracking-[0.16em] text-purple-100 bg-white/10 border border-white/20 rounded-full px-3.5 py-1.5 backdrop-blur-sm shadow-inner-sm">
|
||||
<ScanLine size={11} className="text-purple-200 animate-pulse" /> Scan & Shop
|
||||
</span>
|
||||
<h3 className="relative font-black text-lg mt-4 leading-snug tracking-tight text-white">{storeName}</h3>
|
||||
{(storeZone || storeAddress) && (
|
||||
<p className="relative text-[10px] text-purple-200 mt-2 flex items-center justify-center gap-1 font-medium">
|
||||
<MapPin size={11} className="shrink-0 text-purple-300" />
|
||||
<span className="truncate max-w-[200px]">{storeZone || storeAddress}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* QR Code Frame with Overlap Layout */}
|
||||
<div className="px-6 pb-8 pt-6 flex flex-col items-center relative z-10">
|
||||
<div className="-mt-12 p-4 bg-white rounded-2xl border border-slate-100 shadow-[0_12px_36px_rgba(15,23,42,0.12)] hover:scale-[1.02] transition-transform duration-300">
|
||||
<QRCodeSVG
|
||||
data-qr-svg="true"
|
||||
value={payload}
|
||||
size={180}
|
||||
level={QR_LEVEL}
|
||||
marginSize={QR_MARGIN}
|
||||
fgColor={QR_FG}
|
||||
bgColor={QR_BG}
|
||||
title={`${storeName} storefront QR`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Counter instructions info block */}
|
||||
<div className="mt-5 text-center space-y-1">
|
||||
<p className="text-[11px] font-extrabold text-slate-800 uppercase tracking-wider">Storefront QR Code</p>
|
||||
<p className="text-[10px] text-slate-454 max-w-[220px] leading-relaxed mx-auto font-medium">
|
||||
Customers scan this using the consumer app to shop this branch.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Action Button */}
|
||||
<div className="w-full mt-6">
|
||||
<button
|
||||
onClick={downloadPng}
|
||||
className="w-full flex items-center justify-center gap-2 py-3 px-5 bg-gradient-to-r from-[#581c87] to-indigo-650 hover:from-purple-800 hover:to-indigo-700 text-white rounded-xl text-xs font-bold uppercase tracking-wider cursor-pointer active:scale-95 transition-all shadow-md border-none"
|
||||
>
|
||||
<Download size={14} />
|
||||
Download QR Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Hidden high-res canvas — source for the PNG download */}
|
||||
<div className="sr-only" aria-hidden="true">
|
||||
<QRCodeCanvas
|
||||
ref={canvasRef}
|
||||
value={payload}
|
||||
size={1024}
|
||||
level={QR_LEVEL}
|
||||
marginSize={QR_MARGIN}
|
||||
fgColor={QR_FG}
|
||||
bgColor={QR_BG}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user