About the Project
Inspiration
Every semester, students post in massive GroupMe chats and Discord servers looking for study partners — but those messages get buried in minutes. The ones who find groups do it through existing friend circles, leaving transfer students, introverts, and freshmen on the outside. We wanted to flip the script: what if you matched on the subject, not the person?
Town Circle was born from the idea that collaboration should be anonymous-first and low-friction. No social profiles, no follower counts — just a course tag, a confidence level, and a time to meet. We drew inspiration from the old-school college bulletin board: you pin a note, someone tears off a tab, and you connect.
What it does
Town Circle is a university-exclusive PWA where students post study sessions tied to a specific course and find classmates to work with. Users sign up with a verified .edu email — right now scoped to @illinois.edu — and land in a real-time feed of sessions from their school. Each post carries a course tag (e.g., CS 225, MATH 241), a confidence level (Low / Medium / High), a max group size, and optional details.
From the feed, you send a Link request. If the post owner accepts, you join a group chat with all linked members. Inside the chat, participants vote on proposed time slots and locations. When the group is ready, the post owner finalizes the session — locking in a confirmed time and place displayed in a green details bar. Twenty-four hours after the finalized time passes, the chat automatically archives itself.
Identity stays lightweight throughout: pixel-art avatars generated from usernames, no real names, no photos. You can tap any avatar to view a minimal profile showing past links and an optional email.
How we built it
We built Town Circle as a Next.js 14 app (App Router, TypeScript strict) with Firebase handling auth (email/password with verification) and Firestore as the real-time database. The UI is Material UI 7 with a custom dual-theme (light: white + blue primary, dark: dark surfaces + teal accents). Navigation uses a bottom tab bar with react-swipeable gesture support — swipe left/right to switch tabs.
The data model uses a school-domain isolation pattern: all posts, link requests, and chats live under circles/{schoolDomain}/... in Firestore, so each university is a self-contained shard. Security rules enforce this at the database level — every read and write checks that request.auth.token.email ends with the document's school domain.
We made it a full PWA — installable on iOS and Android with a service worker (network-first navigation caching).
State management is four React contexts stacked in provider order: Theme → Auth → Navigation → Notifications
The notification context runs real-time Firestore listeners that track unread messages (via a readBy map on each chat) and pending link requests, surfacing badge counts on the bottom nav without polling.
Validation is handled by Zod schemas shared between the form layer and the seed script, keeping the client and test data in sync.
Challenges we ran into
Firestore security rules vs. actual app code: We wrote comprehensive rules early, but as features evolved the field names drifted. A hand-audit late in the weekend found 10 mismatches — confidence enums were
['low','medium','high']in rules but['Low','Medium','High']in code,subjectvs.postSubject,finalizedTimevs.finalTime, arejectedstatus that should have beendeclined, and more. Each one caused silentpermission-deniederrors that were painful to trace.Mobile viewport: iOS Safari's dynamic toolbar meant
100vhoverflowed the screen. We switched every full-height container to100dvhbut had to chase down nested elements that still used the old unit.Chat read tracking at scale: Marking messages as read without triggering a write on every render required careful batching — we settled on updating the
readBymap only on chat open and on new incoming messages, usingonSnapshotdiffs.maxPeopleinput on mobile: MUI's numberTextFieldon iOS triggers a full numeric keyboard but stores the value as a string. We had to manage it as string state withonBlurparsing to avoid NaN flickers.
Accomplishments that we're proud of
- Zero-name social platform that still feels personal — pixel avatars and usernames create enough identity for collaboration without the pressure of a social profile.
- End-to-end post lifecycle — from creation → link request → group chat → voting → finalization → auto-archive, all real-time with no polling.
- Hardened Firestore rules — field-level type checks, timestamp drift validation ($|\text{now} - \text{timestamp}| < 5\text{ min}$), document size guards, immutable-field protection, and school-domain isolation.
- Full PWA experience — installable with offline-capable navigation and theme-aware branding.
- Notification system — real-time unread counts across tabs with per-chat indicators, all driven by Firestore listeners with no additional infrastructure.
What we learned
- Firestore security rules are a second codebase that must stay in sync with the client — treat them as code, not config.
- PWA support on iOS is years behind Android; every Apple-specific quirk (
standalonedetection,dvhunits) required manual workarounds. - Anonymous-first design is harder than it sounds — you still need just enough identity (avatars, usernames, confidence levels) for users to make trust decisions.
- Zod schemas are invaluable when shared between the UI validation layer and test seed scripts — one source of truth prevents drift.
- Real-time listeners are powerful but expensive if not scoped tightly; we learned to use composite indexes and narrow queries to keep Firestore reads manageable.
What's next for Town Circle
- Multi-university rollout — the data model already shards by school domain; we need an onboarding flow that detects the school from the email and loads the right course catalog.
- AI-powered matching — use embedding similarity on post descriptions to suggest relevant sessions beyond exact course matches.
- Push notifications — Firebase Cloud Messaging for new link requests and chat messages when the app is in the background.
- Calendar integration — export finalized sessions to Google Calendar / Apple Calendar with a single tap.
- Reputation system — lightweight karma based on session attendance (confirmed via mutual check-in) to build trust without breaking anonymity.
Built With
- cloud-firestore
- dicebear-api
- emotion
- firebase-authentication
- material-ui-(mui)
- next.js
- node.js
- progressive-web-app-(pwa)
- react
- react-swipeable
- service-workers
- sharp
- typescript
- zod
Log in or sign up for Devpost to join the conversation.