Add printable QR code feature for event check-in
This commit is contained in:
146
PRINTABLE_QR_CODE_FEATURE.md
Normal file
146
PRINTABLE_QR_CODE_FEATURE.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Printable QR Code Feature
|
||||
|
||||
## Overview
|
||||
|
||||
Users can now print their personal check-in QR code for easy access at cleanup events. This feature provides a dedicated printable page with a larger QR code and clear instructions.
|
||||
|
||||
## Implementation
|
||||
|
||||
### New Page
|
||||
|
||||
**`src/pages/PrintableQRCodePage.tsx`**
|
||||
- Dedicated page for printing QR codes
|
||||
- Larger QR code (300x300px) for better scanning
|
||||
- Print-optimized layout with CSS media queries
|
||||
- User information and instructions included
|
||||
- Print button triggers browser print dialog
|
||||
- Back button for easy navigation
|
||||
|
||||
### Modified Files
|
||||
|
||||
**`src/components/user/UserQRCode.tsx`**
|
||||
- Added "Print QR Code" button
|
||||
- Button navigates to the printable page
|
||||
- Maintains existing QR code display functionality
|
||||
|
||||
**`src/App.tsx`**
|
||||
- Added route: `/qr-code/print`
|
||||
- Protected route (requires authentication)
|
||||
- Lazy loaded for performance
|
||||
|
||||
**`src/pages/index.ts`**
|
||||
- Added export for `PrintableQRCodePage`
|
||||
|
||||
## Features
|
||||
|
||||
### Printable Page Layout
|
||||
|
||||
1. **Header Section**
|
||||
- Event check-in title
|
||||
- Litter Scouts branding
|
||||
|
||||
2. **QR Code Display**
|
||||
- Large 300x300px QR code
|
||||
- High error correction level (H)
|
||||
- Border for visual separation
|
||||
|
||||
3. **User Information**
|
||||
- User avatar (if available)
|
||||
- Full name or username
|
||||
- Username handle
|
||||
|
||||
4. **Instructions**
|
||||
- Step-by-step guide on how to use the QR code
|
||||
- Numbered list for clarity
|
||||
|
||||
5. **Footer**
|
||||
- Security reminder
|
||||
- Unique code notice
|
||||
|
||||
### Print Optimization
|
||||
|
||||
- CSS media queries hide navigation elements when printing
|
||||
- Only the QR code content is visible in print
|
||||
- Optimized layout for standard paper sizes
|
||||
- Clean, professional appearance
|
||||
|
||||
### User Experience
|
||||
|
||||
1. User views their QR code on profile page
|
||||
2. Clicks "Print QR Code" button
|
||||
3. Navigates to printable page
|
||||
4. Reviews QR code and information
|
||||
5. Clicks "Print QR Code" button or uses browser print (Cmd/Ctrl+P)
|
||||
6. Prints or saves as PDF
|
||||
7. Can navigate back using "Back" button
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Convenience**: Users can print QR codes in advance
|
||||
- **Reliability**: No need for phone battery or internet at events
|
||||
- **Accessibility**: Physical QR codes work for all users
|
||||
- **Professional**: Clean, branded printout
|
||||
- **Flexible**: Can save as PDF or print on paper
|
||||
|
||||
## Technical Details
|
||||
|
||||
### QR Code Data Format
|
||||
|
||||
```json
|
||||
{
|
||||
"userId": "user_clerk_id",
|
||||
"type": "attendee-checkin",
|
||||
"timestamp": 1234567890
|
||||
}
|
||||
```
|
||||
|
||||
### Print Styles
|
||||
|
||||
```css
|
||||
@media print {
|
||||
/* Hide everything except printable content */
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
#printable-qr-code, #printable-qr-code * {
|
||||
visibility: visible;
|
||||
}
|
||||
/* Position printable content at top */
|
||||
#printable-qr-code {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
/* Hide action buttons */
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### For Users
|
||||
|
||||
1. Navigate to your profile page
|
||||
2. Scroll to "Your Check-in QR Code" section
|
||||
3. Click "Print QR Code" button
|
||||
4. Review the printable page
|
||||
5. Click "Print QR Code" or use Cmd/Ctrl+P
|
||||
6. Print or save as PDF
|
||||
|
||||
### For Event Organizers
|
||||
|
||||
- Attendees can show printed QR codes
|
||||
- Scan works the same as digital QR codes
|
||||
- No special handling required
|
||||
- Reduces check-in time at events
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Add event-specific QR codes
|
||||
- Include event details on printout
|
||||
- Support for multiple QR codes per page
|
||||
- Customizable branding options
|
||||
- QR code expiration dates
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
202
src/pages/PrintableQRCodePage.tsx
Normal file
202
src/pages/PrintableQRCodePage.tsx
Normal 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;
|
||||
@@ -9,3 +9,4 @@ export { ReportDetailDemo } from './ReportDetailDemo';
|
||||
export { WhyCleanPage } from './WhyCleanPage';
|
||||
export { UserProfilePage } from './UserProfilePage';
|
||||
export { EnvironmentalDataDemo } from './EnvironmentalDataDemo';
|
||||
export { PrintableQRCodePage } from './PrintableQRCodePage';
|
||||
|
||||
Reference in New Issue
Block a user