Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions ADMIN_ROUTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Admin and Staff Routes

## πŸ” Admin Routes
*Requires `role === "admin"` or `role === "staff"` in registrations collection*

### Main Dashboard
- **`/admin`** - Admin control panel with all tools

### Settings & Configuration
- **`/admin/settings`** βš™οΈ - Event configuration
- Toggle leaderboard visibility
- Control event settings

### Team Management
- **`/admin/teams`** πŸ‘₯ - Team management
- **Create new participants/registrations** (fill all fields manually)
- Create teams from registrations
- Assign team leaders
- Confirm payment status
- View team details

### Judge Management
- **`/admin/judge-assignments`** πŸ“‹ - Judge assignment system
- Assign judges to teams
- Manage judging schedules
- View assignment status

### Milestones & Judging
- **`/admin/milestones`** πŸ“ - Milestone overview
- **`/admin/milestones/[id]`** 🎯 - Individual milestone judging
- Score teams on assigned milestones
- Enter rubric scores
- Add time bonuses
- View real-time scoring

---

## πŸ‘” Staff Routes
*Requires `role === "staff"` or `role === "admin"` in registrations collection*

### QR Scanner
- **`/staff/scanner`** πŸ“± - Multi-purpose QR code scanner
- Check-in participants
- Distribute swag items
- Photobooth access control
- Real-time attendance tracking

### Analytics Dashboard
- **`/staff/analytics`** πŸ“Š - Event analytics
- Check-in statistics
- Swag distribution tracking
- Photobooth usage stats
- Export attendance data (CSV)
- Real-time event metrics

---

## πŸ† Public Routes (No Authentication Required)

### Leaderboard
- **`/leaderboard`** - Team rankings and scores
- Real-time score updates
- Filter by milestone
- Filter by classroom
- Top 3 podium display
- *Can be hidden by admins via settings*

---

## πŸ“‹ Participant Routes
*Requires authentication with `role === "participant"` or `role === "leader"`*

### Dashboard
- **`/dashboard`** - Participant home page
- **`/team`** - Team information
- **`/profile`** - User profile

### Registration & Team Creation
- **`/register`** - Event registration
- **`/create-team`** - Create new team
- **`/edit-team`** - Edit team details
- **`/view-team`** - View team information

### Hackathon Resources
- **`/hackathon/milestone_one`** - Milestone 1 details
- **`/hackathon/milestone_two`** - Milestone 2 details
- **`/hackathon/milestone_three`** - Milestone 3 details
- **`/hackathon/milestone_four`** - Milestone 4 details

### Information Pages
- **`/schedule`** - Event schedule
- **`/timeline`** - Event timeline
- **`/faq`** - Frequently asked questions
- **`/coc`** - Code of Conduct

---

## πŸ”‘ Access Control Summary

| Role | Admin Panel | Staff Tools | Judging | Team Mgmt | Settings |
|------|------------|-------------|---------|-----------|----------|
| **Admin** | βœ… Full | βœ… Full | βœ… Yes | βœ… Yes | βœ… Yes |
| **Staff** | βœ… Limited | βœ… Full | ❌ No | βœ… View | ❌ No |
| **Leader** | ❌ No | ❌ No | ❌ No | βœ… Own Team | ❌ No |
| **Participant** | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |

---

## πŸ“ Notes

### Admin Settings Page Features:
1. **Leaderboard Visibility Control**
- Toggle leaderboard on/off
- Real-time updates for all users
- Useful for hiding scores between milestones

### Staff vs Admin Access:
- **Staff** can access most tools but cannot:
- Assign judges (admin only)
- Access admin settings
- Modify core configurations

- **Admin** has full system access including:
- All staff capabilities
- Judge assignment system
- Event configuration settings
- System-wide controls

### Security:
All routes check authentication on page load:
```typescript
getDoc(doc(db, "registrations", user.uid)).then((document) => {
const response = document.data();
if (response.role !== "admin" && response.role !== "staff") {
alert("Admin access required");
window.location.href = "/";
}
});
```
425 changes: 425 additions & 0 deletions Registrations - TechSprint 2026 - CLEARED - PHASE 1.tsv

Large diffs are not rendered by default.

140 changes: 140 additions & 0 deletions reseed-staff-admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getFirestore, Timestamp } from 'firebase-admin/firestore';
import * as fs from 'fs';
import * as path from 'path';

// Initialize Firebase Admin
if (!getApps().length) {
initializeApp({
credential: cert(require('./serviceAccountKey.json'))
});
}

const db = getFirestore();

interface Registration {
uid: string;
firstName: string;
lastName: string;
email: string;
gender: string;
university: string;
otherUniversity: string;
displayPicture: string;
payment_status: string;
teamCode: string;
role: string;
createdAt: string;
updatedAt: string;
}

function parseCSV(filePath: string): any[] {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n').filter(line => line.trim());

if (lines.length === 0) return [];

const headers = lines[0].split(',').map(h => h.trim());
const rows: any[] = [];

for (let i = 1; i < lines.length; i++) {
const values: string[] = [];
let currentValue = '';
let insideQuotes = false;

for (let char of lines[i]) {
if (char === '"') {
insideQuotes = !insideQuotes;
} else if (char === ',' && !insideQuotes) {
values.push(currentValue.trim());
currentValue = '';
} else {
currentValue += char;
}
}
values.push(currentValue.trim());

if (values.length === headers.length) {
const row: any = {};
headers.forEach((header, index) => {
row[header] = values[index] || '';
});
rows.push(row);
}
}

return rows;
}

async function reseedStaffAndAdmin() {
console.log('πŸ”„ Re-seeding staff and admin accounts with correct UIDs...');

const staffAdmin = parseCSV(path.join(__dirname, 'staff-admin-correct.csv')) as Registration[];
console.log(`Found ${staffAdmin.length} staff/admin accounts to re-seed\n`);

let adminCount = 0;
let staffCount = 0;
let errorCount = 0;

for (const account of staffAdmin) {
try {
const { uid, createdAt, updatedAt, ...data } = account;

await db.collection('registrations').doc(uid).set({
...data,
createdAt: Timestamp.now(),
updatedAt: Timestamp.now(),
});

if (data.role === 'admin') {
adminCount++;
console.log(` βœ“ Admin: ${data.firstName} ${data.lastName} (${data.email})`);
} else {
staffCount++;
console.log(` βœ“ Staff: ${data.firstName} ${data.lastName} (${data.email})`);
}
} catch (error) {
console.error(` βœ— Error seeding account ${account.uid}:`, error);
errorCount++;
}
}

console.log('\n' + '='.repeat(70));
console.log('πŸ“Š RE-SEEDING SUMMARY');
console.log('='.repeat(70));
console.log(`βœ… Admins: ${adminCount} accounts`);
console.log(`βœ… Staff: ${staffCount} accounts`);
console.log(`❌ Errors: ${errorCount}`);
console.log('='.repeat(70));

if (errorCount === 0) {
console.log('βœ… All staff and admin accounts re-seeded successfully!');
} else {
console.log('⚠️ Re-seeding completed with some errors');
}

return { adminCount, staffCount, errorCount };
}

async function main() {
console.log('πŸš€ Starting staff/admin re-seeding...\n');
console.log('=' .repeat(70));

const startTime = Date.now();

try {
await reseedStaffAndAdmin();

const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);

console.log(`\n⏱️ Duration: ${duration}s`);

process.exit(0);
} catch (error) {
console.error('❌ Fatal error during re-seeding:', error);
process.exit(1);
}
}

main();
81 changes: 76 additions & 5 deletions seed-firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,28 +151,99 @@ async function seedTeams() {
return { successCount, errorCount };
}

async function seedStaffAndAdmin() {
console.log('πŸ‘¨β€πŸ’Ό Seeding staff and admin accounts...');

const staffAdmin = parseCSV(path.join(__dirname, 'staff-admin-seed.csv')) as Registration[];
console.log(`Found ${staffAdmin.length} staff/admin accounts to seed`);

let successCount = 0;
let errorCount = 0;

for (const account of staffAdmin) {
try {
const { uid, createdAt, updatedAt, ...data } = account;

await db.collection('registrations').doc(uid).set({
...data,
createdAt: Timestamp.now(),
updatedAt: Timestamp.now(),
});

successCount++;
console.log(` βœ“ Seeded ${data.role} account: ${data.firstName} ${data.lastName}`);
} catch (error) {
console.error(` βœ— Error seeding account ${account.uid}:`, error);
errorCount++;
}
}

console.log(`βœ… Staff/Admin complete: ${successCount} success, ${errorCount} errors\n`);
return { successCount, errorCount };
}

async function seedTestTeam() {
console.log('πŸ§ͺ Seeding test team...');

const teams = parseCSV(path.join(__dirname, 'test-team-seed.csv')) as Team[];
console.log(`Found ${teams.length} test team to seed`);

let successCount = 0;
let errorCount = 0;

for (const team of teams) {
try {
const { teamCode, createdAt, memberIds, ...data } = team;

// Parse memberIds JSON array string
let memberIdsArray: string[] = [];
try {
memberIdsArray = JSON.parse(memberIds);
} catch (e) {
console.error(` ⚠ Warning: Could not parse memberIds for ${teamCode}`);
memberIdsArray = [];
}

await db.collection('teams').doc(teamCode).set({
...data,
memberIds: memberIdsArray,
createdAt: Timestamp.now(),
});

successCount++;
console.log(` βœ“ Seeded test team ${teamCode} (${memberIdsArray.length} members)`);
} catch (error) {
console.error(` βœ— Error seeding team ${team.teamCode}:`, error);
errorCount++;
}
}

console.log(`βœ… Test team complete: ${successCount} success, ${errorCount} errors\n`);
return { successCount, errorCount };
}

async function main() {
console.log('πŸš€ Starting Firestore seeding...\n');
console.log('=' .repeat(70));

const startTime = Date.now();

try {
const registrationStats = await seedRegistrations();
const teamStats = await seedTeams();
const staffAdminStats = await seedStaffAndAdmin();
const testTeamStats = await seedTestTeam();

const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);

console.log('=' .repeat(70));
console.log('πŸ“Š SEEDING SUMMARY');
console.log('=' .repeat(70));
console.log(`Registrations: ${registrationStats.successCount} seeded, ${registrationStats.errorCount} errors`);
console.log(`Teams: ${teamStats.successCount} seeded, ${teamStats.errorCount} errors`);
console.log(`Staff/Admin Accounts: ${staffAdminStats.successCount} seeded, ${staffAdminStats.errorCount} errors`);
console.log(`Test Team: ${testTeamStats.successCount} seeded, ${testTeamStats.errorCount} errors`);
console.log(`Duration: ${duration}s`);
console.log('=' .repeat(70));

if (registrationStats.errorCount === 0 && teamStats.errorCount === 0) {
if (staffAdminStats.errorCount === 0 && testTeamStats.errorCount === 0) {
console.log('βœ… All data seeded successfully!');
} else {
console.log('⚠️ Seeding completed with some errors');
Expand Down
4 changes: 2 additions & 2 deletions serviceAccountKey.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"type": "service_account",
"project_id": "techsprint-gitam",
"private_key_id": "7fe570a3ea76baa41a18d1e29242c9f66527446c",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCURSj2oWsFXgg\n74iOhJ9dgdznsuc/Kb5wMhHsrDbpO09VAWqX4tv8H0/XQk2pmOAeAv0EmmwY5DbS\nQ2ZYGjs2AyJO8Buubq2GDaYdlzILn8BWR3FdrRKm4idsrn/PmuvfMYCo8KM2AOv7\n+uzq7IW0k0OSeBt3FDg8zUMXA2//rKjLzOl295oDTtGpmzIULAOI6SgGX6GJfpe/\nwkHwpN+ct08LJJgGYvFdqCKneqz2o7oTjcvW6ouRgAf8Jbx6X7A/nNr02muz4rtt\nG5WtEI3iOaLLnGVJJlWGzrwVeWmuC04v2lkLwciJFNbe78rZCfqHwXOQ8/URGLVq\nBCWE0E1NAgMBAAECggEATOnT/iFOPLWIxZyaVDMJc3UmD64AGz+2Kemfr6rg10OL\nHK4BV5pLkcmBDEapv+ILf8WWCb7n35hhXKuh9Gh5nGD0MQOYKVyUoZWAdYD1paU3\nd88yf640TksA6ONPIskC6ObKstQA/iyyO5xwL3KsX7PUkMKquGEP+30Ru6e4Kp4n\nQP3Tf0zBq92C/E3ff6E/zeNN7jaIU/cnV/OI9y6MDOcX+UjPDLZDZ+yNp5Lx40lp\nXAvZvQGQRHbt/l7J5/65vRg7zoSuzXeRfYPLCG3pCWTn5FatPfM43rQPIorkeOul\n4j0I1rNgpvIpJN6ghiFDahyytmNMPgTOJr4v8c4UDwKBgQDzBCHfxGgBcbH0knKt\nQc2z/IAl/j/DQe2b4QGDCKjZLaoS9Ym3blb2Xa8qRcsr7rIBMgbTruEZ4U/NEHmq\nCPPwuNyCfoTAf67WCvLW9+EvBpy4574NdBG1a51zCZsDAbu/Iz2cyVniyTh7OsIC\nOOxxs/F1LOiqlg4XvUViQhp1ZwKBgQDMstvtfxA3LEsloyremkHa2Wt405Vsncts\naYS4TopPcDMyLqx5lDMof3POPul+PGOAmtro+EYu+GkrEsi7qhiDTd5De9dsuTR5\nSQ9mzL4GAvSKTxi8UcwOs3K3d3ejQ5jrg/m2g3LVXnzfQqiB5048e+ueOBaxZ0xw\nUAoykLKjKwKBgFQmrCw2cOV/H2ZXiApi7P5Ug3OklSPiIouF4OYlC4MZAvnJuMSi\nGs75Jfz3aiFuaIltb1vCBQTXNrEF8Xtl2kMTYJh3gzS9gidwZyL1dy63lXGaHf++\nn5s5Bq6dNuZVpVPMujsepleX4k0ZzbDDUW0WKJiw0mivyXWC/xHFXjAlAoGAeuy9\n3bV8S2WyCvwddmg0O/Rs8bY9+WgZDRWguf2QWXwLgos80BYLUrqXFLf7B+/D4Ssd\nYuIVY4eRwGgbW3ceGVvdqbDpAUWHGX6iXR1+z6VerOAq/owwenOQ5FQ96DFj16r9\nfnkZsMB5RKmG/9ujw/a22+Da39YktR2bwhna7NkCgYAHYKt5D212pXxrL2IHYSqK\nEP3Z8CDy3b6rVm+A7ansYekaENcyfBu0zObvUMfysRzSYmczGfLS2HXfpK658b6q\nkM4XXEzkRZVAlCvG+X6ySUPVIWB5nsYep0dNce26eQTkw1xWgGejxE9/RCi/b1wI\nRC+Otyc9niLIxmVUwiPDBg==\n-----END PRIVATE KEY-----\n",
"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",
Expand Down
Loading