-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/lunch dinner scanner #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/lunch dinner scanner #26
Conversation
…ogic and leaderboard visibility toggle
- Added lunch and dinner action types to scanner - Added Restaurant and DinnerDining Material-UI icons - Implemented duplicate prevention for lunch and dinner - Lunch uses amber color scheme, dinner uses blue - Required check-in before lunch/dinner access - Updated action selector grid to 3 columns (2 on mobile) - All actions now tracked in checkins collection - Console logging for all scan events
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR aims to add lunch and dinner meal distribution tracking to the staff QR scanner, but includes numerous unrelated changes that should be in separate pull requests. The core feature adds two new action types with duplicate prevention, but the PR also contains leaderboard controls, admin enhancements, new milestone pages, printer improvements, and critically, exposes Firebase service account credentials.
Key Changes (as described in PR):
- Added "lunch" and "dinner" action types to staff scanner with duplicate detection
- Updated UI to 3-column grid layout on desktop (2 columns on mobile)
- Required check-in before meal access
Additional Undocumented Changes:
- Leaderboard visibility toggle system via new admin settings page
- Five new milestone submission pages (milestones 5-10)
- Admin team management enhancements (manual participant creation)
- Printer client WiFi printer selection UI
- Multiple seed scripts for database initialization
- Field name change with typo:
allottedClassroom→allotedClassrooms - CRITICAL: Firebase service account private keys committed to repository
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
serviceAccountKey.json |
CRITICAL SECURITY ISSUE: Firebase private keys exposed in repository |
src/app/staff/scanner/page.tsx |
Core feature: adds lunch/dinner actions with duplicate checking; missing payment verification; fragile timeout workaround |
src/app/leaderboard/page.tsx |
Field name change introduces typo and inconsistency across codebase |
src/app/admin/milestones/[id]/page.tsx |
Manual grading support and hardcoded deadline date |
src/app/admin/teams/page.tsx |
Manual participant creation with weak UID generation |
src/app/admin/settings/page.tsx |
New admin settings page for leaderboard visibility control |
src/app/admin/page.tsx |
Added settings link to admin dashboard |
src/app/printer/client/page.tsx |
Non-functional printer discovery using nonexistent Web API |
src/app/hackathon/milestone_ten/page.tsx |
New final submission page |
src/app/hackathon/milestone_[five-nine]/page.tsx |
Five new manual grading milestone pages |
update-classrooms.ts |
New script for classroom seeding with spelling error in field name |
toggle-leaderboard.ts |
CLI tool for leaderboard visibility control |
seed-firestore.ts |
Modified to seed staff/admin and test teams instead of participants |
reseed-staff-admin.ts |
New script for re-seeding staff and admin accounts |
staff-admin-seed.csv, staff-admin-correct.csv, test-team-seed.csv |
Seed data files for testing and staff accounts |
ADMIN_ROUTES.md |
New documentation for admin and staff routes |
Comments suppressed due to low confidence (1)
serviceAccountKey.json:13
- Critical security vulnerability: Firebase service account private keys are being committed to the repository. This file should never be committed to version control as it contains sensitive credentials that grant full administrative access to your Firebase project.
The private key in this file can be used to:
- Read/write all data in your Firestore database
- Access Firebase Storage
- Perform any administrative operation
Immediate actions required:
- Remove this file from the repository immediately
- Add serviceAccountKey.json to .gitignore
- Revoke this service account key in Firebase Console and generate a new one
- Store credentials securely using environment variables or secret management systems
- Review your Firebase security rules and audit logs for any unauthorized access
For CI/CD, use GitHub Secrets or similar secure secret management instead of committing credentials.
{
"type": "service_account",
"project_id": "techsprint-gitam",
"private_key_id": "fe0c532876c7456432194af575556291e08f5fee",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD5Hv/O51kWbuoF\nOyLMZxb91d7So5V9eUomZarE7+gJ3Uz3g6T6cPTqgYrApPgiC+zvoSvkb15r2kMf\nUPVbqXSJd/ZkVCo3Q1ShSU4+rDGxDDeKx1ocE0wVZCEhLSKaHq2xIvhfLMEY9jh4\n0EyFR+uk3e/nWEY0+DkCczjSJz/0asfHAzY+p1JRR8P07bQsI+QM8xFUnRIgIuOB\nTxSvF1tNfRoa47ldlBZJTy0xgRlfzFk8KIdZrPWMIrfAUJnl5/LJvVlsA98Tv0sA\nhFtjfBMd1SM6sb83rmpmaH6DeeVyisfRRlxDILDhWFLL22Cwmwqn5pkCuG93MxYR\nA6FrkpQfAgMBAAECggEAb2p4URJx6xEO8+j3TsCabUs+HSnRPW1GBvc4UVzUg7jo\nZ9iGLAXh25G1OyRs31lDDgcgqMlQSt8yXuqn2WdnueWfmk50FQQ1cO7moiwEC2Fh\n49z1xZGx2O1PzdUwYQpwd0UjHPnYoK/aINpUJtW8I5+o4Gg/+ge8A4cBtiecuFQ5\ngVHUb3ty02ZqR7tPpfDhffhM3pOP6kZxTnzx2KfE3U1YlFe0seZAkmF5PpjOejFF\n+1O1dtSzvZpau2+6Mk/N45qvXYEm32LbSwCshoi3Vilfey6QVBsa/NFxBPMxsDpq\nrWLrkhy9m4uVA+HBeBszFqHlvMuIUktAB0xJipl3IQKBgQD91JqP3MnuVB2rLm1p\ndbf+KYTBbhtSSAX2jcz2EIPUxYpEAxZi2q1aL/UvK7O+n9iG4elcGxAnVfug+NuH\njJpl1yu+YK8g6bC6E9j9+tW9kgXqJtNlNczEBWZNJ5iKpcbcZBkVznYOiwlyCE97\nOCjVCwItDd55GrWw4Zcn99qqYQKBgQD7QBdPyR2a6zfbEWHEbsnqn6JuAgG0H952\nkfkDe1eOpPYZeYVRAycivH6u2iCCzMa2itiIRVT506qd5sK1Kikoh0HH5pqmwtFz\nxpfviWaAmPGU4GgybWuXNWHrbjYUDff2YXavenEAHMQyp6JmqEf6fk9fHZ2GovXG\nLlneLb7OfwKBgQCE+oLW55aq00qPyczsOQ3hi6LPK34Ix07IclV0fAZ0y+C57Nwn\ngeTboNBUnBKYxWlMkMIOzObTlMo09Osdwl2JCQcTv9c/6O37Lja6KFUd8YhDuX96\nQIs8DpAfz6Sszli2UYKK2BUlXVXfddcd+Lf7lL7ZF7D7xTB2sFjeSY03QQKBgFl7\nmRHccgPT1F/cT/Ky9ozub944Lr0lQIkAMizQR/3QuKmYAyg4ND8F3SSPIVcUcY1f\n5ACcmMglX7W/EweMzX3WtlHoypmr9wcB6ujwCaaxUhEQ32teVxxScd50sSPxWafR\nTIDw3cAJfsL/uzJOqtwHEmOw24KxFVGQ/obHyhYnAoGBAJi1ShoasGdYeKeukA99\n+64n9PshktE7jXIvaFxZi+P0j4qlqiQ+PZCJ9w7rht9SaYx+ZThwdvVATeGK6INR\nTFNq/HG6eVdV2WwEDL8qBN28raNKnVq9VCFjio2mvmcdK9r/kEI3F64W5ghvIiWw\n5Ulvs0fj/30VQih/i/rJ0ZKy\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@techsprint-gitam.iam.gserviceaccount.com",
"client_id": "102465646528741241791",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40techsprint-gitam.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const [teamCode, classroom] of Object.entries(TEAM_CLASSROOM_MAP)) { | ||
| try { | ||
| await db.collection('teams').doc(teamCode).update({ | ||
| allotedClassrooms: classroom, |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error: "allotedClassrooms" should be "allottedClassrooms" (with double 't'). The correct spelling of the past tense of "allot" is "allotted", not "alloted".
This typo is being propagated into the database schema via this seed script, and then used inconsistently across the codebase (see related comment on leaderboard/page.tsx).
| // Check for lunch duplicate (STRICT - only once per user) | ||
| if (selectedAction === "lunch") { | ||
| const lunchQuery = query( | ||
| collection(db, "checkins"), | ||
| where("userId", "==", uid), | ||
| where("type", "==", "lunch") | ||
| ); | ||
| const lunchSnapshot = await getDocs(lunchQuery); | ||
| if (!lunchSnapshot.empty) { | ||
| const lunchDate = lunchSnapshot.docs[0].data().timestamp?.toDate?.()?.toLocaleString(); | ||
| alert(`⚠️ DUPLICATE BLOCKED!\n\nLunch already served to this user!\nServed at: ${lunchDate || "earlier"}`); | ||
| setProcessing(false); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // Check for dinner duplicate (STRICT - only once per user) | ||
| if (selectedAction === "dinner") { | ||
| const dinnerQuery = query( | ||
| collection(db, "checkins"), | ||
| where("userId", "==", uid), | ||
| where("type", "==", "dinner") | ||
| ); | ||
| const dinnerSnapshot = await getDocs(dinnerQuery); | ||
| if (!dinnerSnapshot.empty) { | ||
| const dinnerDate = dinnerSnapshot.docs[0].data().timestamp?.toDate?.()?.toLocaleString(); | ||
| alert(`⚠️ DUPLICATE BLOCKED!\n\nDinner already served to this user!\nServed at: ${dinnerDate || "earlier"}`); | ||
| setProcessing(false); | ||
| return; | ||
| } | ||
| } | ||
|
|
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing payment verification for lunch and dinner: Unlike swag and photobooth (lines 171-176), lunch and dinner actions don't check payment_status. This means users who haven't paid can still receive meals, which may not be the intended behavior.
If meals should only be available to paying participants, add the same payment check:
if ((selectedAction === "lunch" || selectedAction === "dinner") &&
userData.payment_status !== "captured") {
alert("❌ Payment not confirmed. Cannot proceed with this action.");
setProcessing(false);
return;
}If meals should be available to all checked-in participants regardless of payment status, this is acceptable but should be explicitly documented in comments to clarify the intentional difference from swag/photobooth.
| import { Html5QrcodeScanner } from "html5-qrcode"; | ||
|
|
||
| type ActionType = "check-in" | "check-out" | "swag" | "photobooth"; | ||
| type ActionType = "check-in" | "check-out" | "swag" | "photobooth" | "lunch" | "dinner"; |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR includes many changes that are unrelated to the "Lunch & Dinner Scanner" feature described in the PR title and description. The PR contains:
Described in PR: Lunch and dinner action types for the staff scanner
NOT described in PR but included:
- Leaderboard visibility toggle system (new admin settings page)
- Multiple new milestone pages (5-10)
- Admin team management enhancements (create participant feature)
- Printer client WiFi printer selection
- New seed scripts for staff/admin and classrooms
- Documentation files (ADMIN_ROUTES.md)
- Leaderboard field name change (allottedClassroom → allotedClassrooms)
These changes should be split into separate PRs:
- This PR: Lunch & Dinner scanner feature only
- Separate PR: Leaderboard visibility controls
- Separate PR: Milestone pages 5-10
- Separate PR: Admin enhancements
- Separate PR: Printer improvements
- Separate PR: Data seeding scripts
Mixing unrelated features makes code review difficult, increases merge conflict risk, and makes it harder to track which changes caused issues if problems arise.
|
|
||
| // Delay scanner initialization to ensure DOM element is rendered | ||
| setTimeout(() => { | ||
| const html5QrcodeScanner = new Html5QrcodeScanner( | ||
| "qr-reader", | ||
| { fps: 10, qrbox: { width: 250, height: 250 } }, | ||
| false | ||
| ); | ||
|
|
||
| html5QrcodeScanner.render( | ||
| (decodedText) => { | ||
| handleScan(decodedText); | ||
| html5QrcodeScanner.clear(); | ||
| setScanning(false); | ||
| }, | ||
| (errorMessage) => { | ||
| // Ignore errors, just keep scanning | ||
| } | ||
| ); | ||
| html5QrcodeScanner.render( | ||
| (decodedText) => { | ||
| handleScan(decodedText); | ||
| html5QrcodeScanner.clear(); | ||
| setScanning(false); | ||
| }, | ||
| (errorMessage) => { | ||
| // Ignore errors, just keep scanning | ||
| } | ||
| ); | ||
|
|
||
| setScanner(html5QrcodeScanner); | ||
| setScanner(html5QrcodeScanner); | ||
| }, 100); |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 100ms delay to ensure DOM element rendering is a fragile workaround that may fail on slower devices or under heavy load. Consider using a more robust approach:
- Use React's useEffect with proper dependency tracking
- Check for element existence before initializing:
if (document.getElementById('qr-reader')) - Use MutationObserver to wait for the element
- Or ensure the element is rendered before enabling the scan button
The current timeout approach can still fail if the device is slow or the browser is busy, leading to scanner initialization errors.
| teamCode: data.teamCode || doc.id, | ||
| teamName: data.teamName || "Unknown Team", | ||
| classroom: data.allottedClassroom || "Not Assigned", | ||
| classroom: data.allotedClassrooms || "Not Assigned", |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent field name change: This PR changes from allottedClassroom to allotedClassrooms (note the typo: "allotted" is misspelled as "alloted"). However, this change is not applied consistently across the codebase.
Files still using the old field name allottedClassroom:
- src/app/admin/teams/page.tsx
- src/app/hackathon/pitch-deck/page.tsx
- src/app/leaderboard/live/page.tsx
- src/app/photobooth/capture/page.tsx
- src/app/photobooth/select/page.tsx
- src/app/profile/page.tsx
- src/app/staff/analytics/page.tsx
This inconsistency will cause bugs where classroom information fails to display correctly in some parts of the application. Additionally, if the intent was to fix the typo "allotted" → "alloted", this is actually introducing a new typo rather than fixing one.
Recommendations:
- Either revert this change or apply it consistently across ALL files
- If changing, use the correctly spelled "allottedClassroom" or "allottedClassrooms"
- Create a database migration to rename the field in Firestore if needed
| classroom: data.allotedClassrooms || "Not Assigned", | |
| classroom: data.allottedClassroom || data.allotedClassrooms || "Not Assigned", |
| const teamDetailsId = snapshot.docs[0].id; | ||
| setTeamDocId(teamDetailsId); | ||
| const dt = snapshot.docs[0].data()["milestone_ten_time"]; | ||
| if (dt != undefined && dt != null) { |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This guard always evaluates to true.
| if (dt != undefined && dt != null) { | |
| if (dt != null) { |
| @@ -0,0 +1,54 @@ | |||
| "use client"; | |||
|
|
|||
| import { useEffect } from "react"; | |||
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import useEffect.
| import { useEffect } from "react"; |
| import { useEffect } from "react"; | ||
| import { useRouter } from "next/navigation"; | ||
| import { Info } from "@mui/icons-material"; | ||
|
|
||
| export default function Page() { | ||
| const router = useRouter(); | ||
|
|
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable router.
| import { useEffect } from "react"; | |
| import { useRouter } from "next/navigation"; | |
| import { Info } from "@mui/icons-material"; | |
| export default function Page() { | |
| const router = useRouter(); | |
| import { Info } from "@mui/icons-material"; | |
| export default function Page() { |
| "use client"; | ||
| import { db } from "@/lib/firebase"; | ||
| import { collection, getDocs, onSnapshot } from "firebase/firestore"; | ||
| import { collection, getDocs, onSnapshot, doc } from "firebase/firestore"; |
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import getDocs.
| import { collection, getDocs, onSnapshot, doc } from "firebase/firestore"; | |
| import { collection, onSnapshot, doc } from "firebase/firestore"; |
| @@ -0,0 +1,119 @@ | |||
| import { initializeApp, cert, getApps } from 'firebase-admin/app'; | |||
| import { getFirestore, Timestamp } from 'firebase-admin/firestore'; | |||
| import * as fs from 'fs'; | |||
Copilot
AI
Jan 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import fs.
| import * as fs from 'fs'; |
🍽️ Feature: Lunch & Dinner Scanner
What's Changed
Features Added
checkinscollectionTechnical Details
ActionTypeto include"lunch"and"dinner"Restaurant,DinnerDiningTesting
Screenshots
Staff scanner now shows 6 action options: