Skip to content

COolAlien35/PollRooms

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PollRooms

⚑ PollRooms β€” Real-Time & Distributed

Next.js TypeScript Socket.io Supabase Vercel Render Status License


The "Menti-Killer." Instant polling with <50ms latency, military-grade anti-abuse, and cyber-organic aesthetics.
No sign-up. No paywall. Just decisions β€” made together, in real time.


🟒 Live Demo

Launch App

🌐 Live at poll-rooms.vercel.app β€” Frontend on Vercel Edge Β· Socket Server on Render Β· Database on Supabase


πŸ“Έ The Experience

Home β€” Create a Poll
🏠 Home β€” Create a Poll
Glassmorphism form on a cyber-organic grid. Dynamic option inputs, smart scheduling presets, and a pulsing "LIVE VOTING" beacon.
Live Voting β€” Blind Mode
⚑ Live Voting β€” Blind Mode
Multi-select checkboxes with emerald accents, "Vote to reveal results" blind badge, live countdown timer, and QR + Share buttons.
Smart Scheduling β€” Custom Date & Time Picker
πŸ“… Smart Scheduling β€” Custom Date & Time
Dark-themed react-datepicker with emerald accents β€” full calendar + scrollable time slots. "Custom Date…" mode with Multi-Select & Blind Voting toggles.
Gold Mode β€” Victory State
πŸ† Gold Mode β€” Victory State
When the clock hits zero, the winner gets the royal treatment: Golden glowing border, trophy icons, confetti burst, and a "Winner" banner.

QR Code β€” Scan to Join
πŸ“± QR Code Modal β€” "Scan to Join"
One-tap QR generation for instant mobile participation. Point your camera β†’ join the poll.


πŸ—οΈ System Architecture

Split-Stack Design β€” The frontend lives on Vercel's edge network, the WebSocket server runs as an always-on process on Render, and Supabase provides managed PostgreSQL. They communicate via a clean /broadcast HTTP bridge.

graph LR
    subgraph Client ["πŸ–₯️ Browser"]
        A["React 19 + Socket.io Client<br/>FingerprintJS Β· Framer Motion"]
    end

    subgraph Vercel ["☁️ Vercel (Serverless)"]
        B["Next.js 16 API Routes<br/>/api/polls/create<br/>/api/polls/[slug]<br/>/api/polls/[slug]/vote"]
    end

    subgraph Render ["🎨 Render (Always-On)"]
        C["Node.js + Express<br/>Socket.io Server<br/>POST /broadcast<br/>GET /health"]
    end

    subgraph Supabase ["🐘 Supabase"]
        D["PostgreSQL<br/>polls Β· options Β· votes<br/>RPC: increment_vote_count<br/>Triggers Β· RLS"]
    end

    A -- "HTTPS Β· REST API" --> B
    A -- "WSS Β· WebSocket" --> C
    B -- "POST /broadcast<br/>{pollId, optionId, newCount}" --> C
    B -- "Supabase SDK<br/>Queries + RPC" --> D
    C -. "vote_update event<br/>β†’ all clients in room" .-> A

    style Client fill:#0a0a0a,stroke:#10b981,color:#f5f5f5
    style Vercel fill:#0a0a0a,stroke:#f5f5f5,color:#f5f5f5
    style Render fill:#0a0a0a,stroke:#46e3b7,color:#f5f5f5
    style Supabase fill:#0a0a0a,stroke:#3fcf8e,color:#f5f5f5
Loading

The Vote Flow (4 steps, <50ms end-to-end):

Step What Happens Where
1 User clicks an option β†’ Optimistic UI update (bar moves instantly) Client
2 POST /api/polls/[slug]/vote β€” validates poll, checks fingerprint, checks IP Vercel
3 INSERT INTO votes + RPC increment_vote_count (atomic) β†’ POST /broadcast Vercel β†’ Render
4 Socket.io emits vote_update to all clients in poll:{id} room Render β†’ All Clients

✨ 11/10 Features

⚑ The "Pulse" Engine

Custom Socket.io server separated from Next.js for true stateful WebSocket connections. Vercel's serverless functions are stateless β€” they can't hold open sockets. Our dedicated Node.js process on Render manages persistent room-based pub/sub, handles reconnections, and exposes a /health endpoint for uptime monitoring.

πŸ›‘οΈ The Fortress (Anti-Abuse)

Dual-layer protection that creates serious friction for manipulation:

Layer Technology What It Prevents How It Works
1. Device Fingerprinting @fingerprintjs/fingerprintjs Same device voting twice Generates a stable hash from 50+ browser signals (canvas, WebGL, screen, timezone). Stored per-poll in PostgreSQL with UNIQUE(poll_id, voter_fingerprint). Falls back to SHA-256 of manual browser properties if FingerprintJS fails.
2. IP Rate Limiting Sliding window (10 min) Bot attacks, VPN hopping Checks votes table for any vote from the same IP within the last 10 minutes. Returns 429 Too Many Requests with Retry-After header. Uses server-side UTC timestamps β€” never trusts the client clock.

πŸ™ˆ Blind Voting

Toggle results_hidden on poll creation to hide all vote counts and bars until the poll expires. This eliminates the "Bandwagon Effect" β€” voters commit to their genuine preference without being swayed by the crowd. When the deadline hits, all results are revealed simultaneously.

πŸ“… Time Travel (Smart Scheduling)

Precise deadline control with custom-styled dark emerald calendar:

  • Presets: 10 min Β· 1 hour Β· 24 hours Β· No Limit
  • Custom: Full react-datepicker with date + time selection, past-date rejection, and emerald-themed dark mode styling
  • Enforcement: Server-side expiration check on every vote β€” new Date() on the server, never the client

πŸ† Gold Mode

When the clock hits zero, the winning option gets the full ceremony:

  • canvas-confetti burst πŸŽ‰ (triggered once via useEffect on expiry detection)
  • Gold gradient vote bar (linear-gradient(90deg, #eab308, #fbbf24, #fde68a))
  • πŸ† Trophy icon + ambient glow effect
  • Haptic click sound via use-sound

πŸ“± Native Bridge

  • QR Code Modal β€” react-qr-code renders the poll URL for instant mobile scanning
  • Native Share Sheet β€” uses navigator.share() on supported devices
  • Sound Feedback β€” subtle click/vote SFX with a mute toggle
  • Clipboard Fallback β€” 3-tier copy strategy (Clipboard API β†’ Share API β†’ <textarea> + execCommand)

πŸ› Edge Cases Solved β€” The "Senior" Section

These aren't hypothetical. Every one was encountered, debugged, and patched.

🧟 The Zombie Socket

Problem: Laptop sleeps β†’ wakes up β†’ Socket.io auto-reconnects, but the UI shows stale vote counts from hours ago. Fix: On reconnect, the client fires a full re-fetch of /api/polls/[slug] to hydrate the latest authoritative data before resuming real-time updates.

🏎️ The Race Condition

Problem: 50 users vote in the same second. NaΓ―ve read count β†’ write count+1 loses votes due to interleaved reads. Fix: supabase.rpc('increment_vote_count') runs UPDATE SET vote_count = vote_count + 1 RETURNING vote_count β€” a single atomic Postgres statement. No read-modify-write. No drift.

πŸ“‹ The Clipboard Crash

Problem: navigator.clipboard.writeText() throws in non-HTTPS contexts (localhost, embedded iframes, older Android WebViews). Fix: Three-tier fallback:

  1. navigator.clipboard.writeText() β€” modern browsers on HTTPS
  2. navigator.share() β€” mobile native share sheet
  3. Temporary <textarea> + document.execCommand('copy') β€” the "1999 approach" that still works everywhere

⏳ The Timezone Trap

Problem: User with a misconfigured system clock bypasses poll expiration, or gets blocked from a still-open poll. Fix: Server-authoritative timestamps only. The vote API uses new Date() on the server (UTC) to compare against poll.expires_at. Client-side countdown timers are display-only β€” cosmetic, never authoritative.

πŸ›‘οΈ The Array Attack

Problem: Malicious client sends optionIds: "string" instead of ["array"], crashing .map() downstream. Fix: Input normalization before any processing:

const normalizedIds = Array.isArray(rawIds)
  ? rawIds.filter(id => typeof id === 'string' && id.length > 0)
  : typeof rawIds === 'string' ? [rawIds] : [];

πŸ”’ Database Guardrails (Defense in Depth)

Even if the app logic has a bug, Postgres triggers are the last line of defense:

  • vote_check_active β€” rejects votes on inactive polls at the DB level
  • vote_validate_option β€” rejects votes where option_id βˆ‰ poll_id
  • UNIQUE(poll_id, voter_fingerprint) β€” constraint-level duplicate prevention

πŸ› οΈ Tech Stack

Layer Technology Role
Frontend Next.js 16 (App Router) + React 19 SSR, routing, serverless API
Language TypeScript End-to-end type safety
Styling Tailwind CSS 4 + Custom Design System "Cyber-Organic" dark theme with emerald accents
Real-Time Node.js + Express + Socket.io Dedicated WebSocket server (Railway)
Database PostgreSQL via Supabase Persistent storage, RPC, RLS, triggers
Anti-Abuse FingerprintJS + IP Sliding Window Dual-layer vote integrity
Animation Framer Motion Page transitions, micro-interactions
Notifications Sonner Toast notifications
Icons Lucide React Consistent iconography
Engagement canvas-confetti Β· use-sound Β· react-qr-code Confetti, haptic sounds, QR sharing
Scheduling react-datepicker Custom deadline calendar
Deployment Vercel (app) + Render (socket) + Supabase (db) Split-stack, independently scalable

πŸš€ Getting Started

Prerequisites

Node.js 18+  Β·  npm 9+  Β·  Git  Β·  Supabase Account (free tier)

Quick Start

# 1. Clone
git clone https://github.com/pulkitpandey/poll-rooms.git && cd poll-rooms

# 2. Install dependencies (both apps)
npm install && cd socket-server && npm install && cd ..

# 3. Configure environment
cp .env.example .env.local          # Fill in Supabase credentials
cp socket-server/.env.example socket-server/.env

# 4. Setup database
#    β†’ Go to supabase.com β†’ SQL Editor β†’ Paste database/schema.sql β†’ Run

# 5. Launch (two terminals)
npm run dev                          # Terminal 1 β†’ http://localhost:3000
cd socket-server && node index.js    # Terminal 2 β†’ ws://localhost:3001

Env Var Checklist:

Variable File Required Description
NEXT_PUBLIC_BASE_URL .env.local βœ… App origin (e.g. http://localhost:3000)
NEXT_PUBLIC_SOCKET_URL .env.local βœ… Public WebSocket URL (client connects here)
SOCKET_SERVER_URL .env.local βœ… Internal broadcast URL (API β†’ Socket)
NEXT_PUBLIC_SUPABASE_URL .env.local βœ… Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY .env.local βœ… Supabase anonymous key
PORT socket-server/.env ⬜ Socket server port (default: 3001)
FRONTEND_URL socket-server/.env βœ… CORS origin whitelist

πŸ“„ License

MIT β€” use it, fork it, ship it.


Built with obsessive attention to detail, defensive engineering, and way too much emerald green. πŸ’š

About

Real time polling engine with <50ms latency. Built with Next.js 14, Socket.io, & Supabase. Features dual-layer anti-abuse, optimistic UI, and "Cyber-Organic" design.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Contributors