Add printable QR code feature for event check-in

This commit is contained in:
2025-11-20 19:30:59 +00:00
parent 4baa2892b9
commit 2dcd1ce78d
5 changed files with 379 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ const WhyCleanPage = lazy(() => import('./pages/WhyCleanPage'));
const UserProfilePage = lazy(() => import('./pages/UserProfilePage'));
const EnvironmentalDataDemo = lazy(() => import('./pages/EnvironmentalDataDemo'));
const EventCheckInPage = lazy(() => import('./pages/EventCheckInPage'));
const PrintableQRCodePage = lazy(() => import('./pages/PrintableQRCodePage'));
// Services
import { apiClient } from './services/api';
@@ -163,6 +164,14 @@ function App() {
</ProtectedRoute>
}
/>
<Route
path="/qr-code/print"
element={
<ProtectedRoute>
<PrintableQRCodePage />
</ProtectedRoute>
}
/>
{/* Demo routes for testing */}
<Route

View File

@@ -1,8 +1,10 @@
import { QRCodeSVG } from 'qrcode.react';
import { useUser } from '@clerk/clerk-react';
import { useNavigate } from 'react-router-dom';
export function UserQRCode() {
const { user } = useUser();
const navigate = useNavigate();
if (!user) return null;
@@ -32,6 +34,25 @@ export function UserQRCode() {
<p className="text-xs text-gray-500 mt-4 text-center">
{user.username || user.firstName || 'User'}
</p>
<button
onClick={() => navigate('/qr-code/print')}
className="w-full mt-4 bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium inline-flex items-center justify-center"
>
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"
/>
</svg>
Print QR Code
</button>
</div>
);
}

View File

@@ -0,0 +1,202 @@
import { useEffect } from 'react';
import { useUser } from '@clerk/clerk-react';
import { QRCodeSVG } from 'qrcode.react';
import { useNavigate } from 'react-router-dom';
export function PrintableQRCodePage() {
const { user, isLoaded } = useUser();
const navigate = useNavigate();
useEffect(() => {
// Add print-specific styles
const style = document.createElement('style');
style.textContent = `
@media print {
body * {
visibility: hidden;
}
#printable-qr-code, #printable-qr-code * {
visibility: visible;
}
#printable-qr-code {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
.no-print {
display: none !important;
}
}
`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
if (!isLoaded) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
);
}
if (!user) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-md p-6 max-w-md w-full text-center">
<p className="text-gray-600 mb-4">Please sign in to view your QR code</p>
<button
onClick={() => navigate('/')}
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
Go Home
</button>
</div>
</div>
);
}
const qrData = JSON.stringify({
userId: user.id,
type: 'attendee-checkin',
timestamp: Date.now(),
});
const handlePrint = () => {
window.print();
};
return (
<div className="min-h-screen bg-gray-50 py-8 px-4">
{/* Action Buttons - Hidden when printing */}
<div className="max-w-4xl mx-auto mb-6 no-print">
<div className="flex items-center justify-between">
<button
onClick={() => navigate(-1)}
className="text-blue-600 hover:text-blue-700 inline-flex items-center"
>
<svg
className="w-5 h-5 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
Back
</button>
<button
onClick={handlePrint}
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors font-medium inline-flex items-center"
>
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"
/>
</svg>
Print QR Code
</button>
</div>
</div>
{/* Printable Content */}
<div id="printable-qr-code" className="max-w-4xl mx-auto">
<div className="bg-white rounded-lg shadow-md p-12">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
Event Check-In QR Code
</h1>
<p className="text-gray-600">
Litter Scouts - Beach Cleanup Events
</p>
</div>
{/* QR Code */}
<div className="flex justify-center mb-8">
<div className="p-8 bg-white border-4 border-gray-200 rounded-lg">
<QRCodeSVG
value={qrData}
size={300}
level="H"
includeMargin={true}
/>
</div>
</div>
{/* User Info */}
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center mb-4">
{user.imageUrl && (
<img
src={user.imageUrl}
alt={user.fullName || user.username || 'User'}
className="w-16 h-16 rounded-full border-2 border-gray-300"
/>
)}
</div>
<h2 className="text-2xl font-semibold text-gray-900 mb-1">
{user.fullName || user.username || 'User'}
</h2>
<p className="text-gray-600">@{user.username}</p>
</div>
{/* Instructions */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-3 text-center">
How to Use
</h3>
<div className="space-y-2 text-gray-700">
<div className="flex items-start">
<span className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-600 font-semibold text-sm mr-3 flex-shrink-0 mt-0.5">
1
</span>
<p>Bring this QR code to any Litter Scouts cleanup event</p>
</div>
<div className="flex items-start">
<span className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-600 font-semibold text-sm mr-3 flex-shrink-0 mt-0.5">
2
</span>
<p>Show it to the event organizer for quick check-in</p>
</div>
<div className="flex items-start">
<span className="inline-flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-600 font-semibold text-sm mr-3 flex-shrink-0 mt-0.5">
3
</span>
<p>Your attendance will be automatically recorded</p>
</div>
</div>
</div>
{/* Footer */}
<div className="mt-8 pt-6 border-t border-gray-200 text-center text-sm text-gray-500">
<p>This QR code is unique to your account</p>
<p className="mt-1">Keep it safe and don't share with others</p>
</div>
</div>
</div>
</div>
);
}
export default PrintableQRCodePage;

View File

@@ -9,3 +9,4 @@ export { ReportDetailDemo } from './ReportDetailDemo';
export { WhyCleanPage } from './WhyCleanPage';
export { UserProfilePage } from './UserProfilePage';
export { EnvironmentalDataDemo } from './EnvironmentalDataDemo';
export { PrintableQRCodePage } from './PrintableQRCodePage';