Files
daily_merchant_web/src/components/StoreQRView.tsx

148 lines
6.1 KiB
TypeScript

import React, { useMemo, useRef } from 'react';
import { QRCodeSVG, QRCodeCanvas } from 'qrcode.react';
import { MapPin, ScanLine, AlertTriangle, Download } from 'lucide-react';
import { Button } from '@astryxdesign/core/Button';
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 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 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-[#662582] 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 &amp; 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
label="Download QR Code"
icon={<Download size={14} />}
onClick={downloadPng}
style={{ height: 'auto' }}
className="w-full flex items-center justify-center gap-2 py-3 px-5 bg-gradient-to-r from-[#662582] 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"
/>
</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>
);
}