NextCourt: Priority-Based Pickleball Queue Management
About The Project
NextCourt is a Progressive Web App that solves a critical problem at pickleball facilities: players queue remotely but never show up, wasting court time and frustrating those actually present. The solution is a three-tier priority queue system that rewards physical presence.
Priority System:
- HIGH Priority: All 4 players checked in at facility (within 100m)
- MEDIUM Priority: 1-3 players checked in at facility
- LOW Priority: Remote queue with no check-ins
As players arrive and check in via geolocation, their game priority upgrades automatically and moves up the queue in real-time across all connected devices.
Inspiration
The idea emerged from observing recreational facilities struggle with no-shows. Traditional first-come-first-served queues don't account for physical presence—someone can join remotely and hold a spot while people at the facility wait. This creates inefficiency and frustration. The solution: a priority system that automatically rewards showing up.
Technology Stack
Frontend: React 18 with TypeScript, Vite, Tailwind CSS, Zustand state management, React Router v6
Backend: Firebase Firestore (real-time database), Firebase Authentication (email/Google OAuth), Firebase Hosting
Key APIs: Geolocation API for check-in verification, Service Workers for PWA capabilities
Architecture
The app follows a modular architecture with clear separation of concerns: src/ ├── components/ # Reusable UI components ├── screens/ # Full-page views ├── services/ # Business logic layer ├── store/ # Global state management ├── types/ # TypeScript interfaces └── utils/ # Pure utility functions
Key Algorithms
Priority Calculation
function calculatePriority(players: Player[]): Priority {
const checkedInCount = players.filter(p => p.checkedIn).length;
if (checkedInCount >= 4) return 'HIGH';
if (checkedInCount >= 1) return 'MEDIUM';
return 'LOW';
}
Queue Sorting
Games sort by priority first (HIGH > MEDIUM > LOW), then by timestamp (FIFO within same priority):
function sortGameQueues(queues: GameQueue[]): GameQueue[] {
return queues
.filter(game => game.status === 'waiting')
.sort((a, b) => {
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
if (priorityDiff !== 0) return priorityDiff;
return a.queuedAt.getTime() - b.queuedAt.getTime();
});
}
Geolocation Verification
Uses the Haversine formula to calculate great-circle distance between GPS coordinates: \subsection*{Geolocation Verification}
The application uses the Haversine formula to calculate the great-circle distance between two GPS coordinates
Implementation:
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371e3;
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
Players must be within 100 meters of the facility to check in successfully.
Wait Time Estimation
[ \text{Wait Time} = \left\lceil \frac{N_{\text{ahead}} \times T_{\text{avg}}}{C_{\text{available}}} \right\rceil ]
Where (N_{\text{ahead}}) is games ahead in queue, (T_{\text{avg}}) is average game duration (15 minutes), and (C_{\text{available}}) is available courts.
Real-Time Synchronization
Firestore's onSnapshot() listeners enable instant updates across all clients. When any user checks in, all connected devices receive the update within milliseconds and automatically re-sort the queue.
onSnapshot(collection(db, 'gameQueues'), (snapshot) => {
const games = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setGames(games);
updateQueueState(games);
});
What I Learned
Real-Time Systems Complexity: Building real-time queues taught me about eventual consistency and race conditions. I learned to use Firestore transactions for atomic updates and design data structures that minimize write conflicts.
Mobile-First Design: Most users access this on phones at facilities. I learned to design touch-friendly UI (48×48px minimum tap targets), test on actual devices, handle geolocation permissions across platforms, and optimize for slower networks.
TypeScript Benefits: Strict typing caught bugs before runtime. Union types like 'HIGH' | 'MEDIUM' | 'LOW' prevented typos, and interfaces made the codebase self-documenting and guided refactoring.
Security Rules: I learned to write comprehensive Firestore security rules to prevent malicious writes while allowing legitimate access patterns.
User Experience Details: Small touches matter—toast notifications, loading states, clear error messages, visual feedback, and optimistic updates create a polished experience.
Challenges Faced
Challenge 1: Geolocation Reliability
Browser geolocation is unreliable—iOS Safari sometimes takes 30+ seconds or fails entirely, and GPS accuracy varies wildly.
Solution: Increased check-in radius to 100m for GPS drift, added 10-second timeout, provided clear error messages and retry mechanism, and built a demo mode for testing without real location.
Challenge 2: Real-Time Race Conditions
When multiple users check in simultaneously, Firestore writes can conflict, causing lost updates or incorrect priority calculations.
Solution: Used Firestore transactions for atomic updates and designed the data model to minimize concurrent writes to the same document.
Challenge 3: State Management Complexity
Synchronizing Firestore (source of truth), Zustand store (app state), and React component state became complex.
Solution: Established unidirectional data flow (Firestore → Zustand → Components), used Zustand selectors to prevent unnecessary re-renders, and minimized component local state to UI concerns only.
Challenge 4: Testing Geolocation Features
Can't physically be at a facility while developing, and Chrome DevTools location spoofing is buggy.
Solution: Built comprehensive demo mode that simulates facilities, auto-generates games, simulates check-ins every 10 seconds, and allows full feature testing from anywhere.
Challenge 5: Firebase Free Tier Limits
Firestore free tier allows only 50K reads and 20K writes per day. Real-time listeners can consume this quickly.
Solution: Optimized queries with indexes, used query filters to reduce document reads, implemented client-side caching, and designed UI to minimize refetches. Result: supports 100-500 daily active users on free tier.
Challenge 6: Cross-Platform PWA Quirks
PWA installation and behavior differs drastically between iOS Safari, Android Chrome, and desktop browsers.
Solution: Tested on actual devices, configured manifest for each platform's requirements, added fallback for unsupported platforms, and ensured web version is fully functional without installation.
Core Features Implemented
- Three-tier priority queue system with automatic recalculation
- Real-time synchronization across all connected devices
- Geolocation-based check-in verification (100m radius)
- Progressive Web App installable on all platforms
- Email/password and Google OAuth authentication
- Court status board with occupancy tracking
- Wait time estimation based on queue position
- Demo mode for testing and demonstrations
- Comprehensive Firestore security rules
- Mobile-first responsive design
Technical Specifications
- Lines of Code: ~3,500+
- Components: 9 reusable components
- Screens: 4 full-page views
- Services: 4 business logic modules
- Build Time: <5 seconds with Vite
- Bundle Size: ~200KB gzipped
- Supported Platforms: iOS, Android, desktop (any modern browser)
Security Implementation
Firestore security rules ensure proper access control:
match /gameQueues/{gameId} {
allow read: if isSignedIn();
allow update: if isSignedIn() && (
request.auth.uid == resource.data.creatorId ||
request.auth.uid in resource.data.players[*].userId
);
}
Users can only update games they created or participate in, while maintaining queue transparency through read access.
Results
NextCourt is a production-ready application that solves a real problem for pickleball communities. The priority queue system creates fairness by rewarding players who show up, reducing no-shows and improving court utilization. It provides instant real-time updates, works on any device as a PWA, and scales to hundreds of users on Firebase free tier.
Conclusion
Building NextCourt demonstrated that elegant solutions often come from simple insights—physical presence should matter in a physical sport's queue system. The real challenge was execution: handling real-time synchronization, geolocation quirks, mobile responsiveness, security, and creating polished user experience.
The result is a practical tool that facilities can deploy today. It showcases how modern web technologies (PWA, real-time databases, geolocation) can solve tangible real-world problems while maintaining performance and user experience across all platforms.
Tech Stack: React, TypeScript, Firebase, Tailwind CSS, Vite
Architecture: Modular component-based with real-time synchronization
Deployment: Firebase Hosting with global CDN
Built With
- autoprefixer
- cli
- css
- date-fns
- eslint
- firebase
- firebase-authentication
- firebase-cloud-messaging
- firebase-hosting
- firestore
- geolocation-api
- git
- html5
- javascript
- lucide-react
- npm
- permissions-api
- postcss
- react
- react-dom
- react-hot-toast
- react-router
- service-workers
- tailwind-css
- typescript
- vite
- vite-pwa-plugin
- web-app-manifest
- web-storage-api
- zustand

Log in or sign up for Devpost to join the conversation.