Skip to content

Repository files navigation

Community Event Board

A cross-platform mobile app where a neighbourhood posts local events to a shared, live-updating board. Sign up, post an event, and it appears on every other signed-in device instantly — no refresh, no pull-to-reload.

Built with React Native (Expo), TypeScript, and Firebase (Auth + Cloud Firestore).

Expo SDK 57 React Native 0.81 TypeScript Firebase Expo Router


Demo

Sign in screen Live event board Add event form Edit event modal
Sign in
Email and password, with the session remembered
The live board
Your own posts invert to black; live counts on the filter chips
Post an event
Validated before it ever reaches Firestore
Edit your own
Pre-filled modal, owner-only
📱  More screens  —  sign up and empty states  (click to expand)
Sign up screen Empty board state
Create an account
Writes an Auth user and a profile document together
Empty board
Distinguished from a board that failed to load

What it does

  • Email & password accounts — sign up, sign in, sign out, with the session tracked app-wide so a signed-in user never sees the auth screens again.
  • A genuinely live board — the event list is a Firestore onSnapshot subscription, so adds, edits and deletes from any device land on every device within a second.
  • Full CRUD, scoped to the owner — anyone can read the whole board, but only the person who posted an event can edit or delete it.
  • Ownership is visible, not just enforced — your own posts render inverted (black card, gold accents) so you can pick them out of the feed instantly.
  • Filter without a second queryAll events / My posts chips with live counts, derived from the data already in memory.
  • Two-tab navigation — Event Board and Add Event, in a floating pill bar, with sign-out in the header.
  • Failures are visible, not silent — a rejected read reports itself on screen with the reason, instead of leaving a board that looks merely empty. Validation and auth errors surface the same way rather than being logged to a console nobody is watching.

Design decisions

Ownership lives on the document. Every event stores the UID of whoever wrote it, and one comparison against the signed-in user drives the whole model — the card's colour, the accent, and whether the edit and delete controls exist at all. No roles table, no permissions lookup, no second read.

The board never re-fetches. A single Firestore real-time listener is opened when the screen mounts and torn down when it unmounts. After a write, nothing manually patches local state — Firestore pushes the new snapshot back and the list re-renders itself. That is why saving an edit and deleting an event have no "and now update the array" step: the subscription is the single source of truth, and the same code path handles a change made on someone else's device.

Filtering is derived, not destructive. The array from Firestore is never mutated. The All events / My posts toggle computes a new view of it on each render, so switching back always has the complete list to return to, and the counts on the chips stay honest.

"Empty" and "broken" are different states. The first version logged listener failures to the console and rendered the normal empty state, so a permissions error looked exactly like a board nobody had posted to yet — and the empty state cheerfully suggested posting an event, which was the one thing that would also fail. Now a rejected read stores its reason and shows it above the list, the empty-state card is suppressed so the app never claims a board is empty when it simply could not read it, and the next successful snapshot clears the message on its own. Distinguishing no data from no access costs a few lines and turns a silent dead end into something a user can actually report.

One design system, no UI library. CustomStyle.ts exports the design tokens — colour, spacing, radius, elevation, type scale — and a shared stylesheet composed from them. No screen hardcodes a colour or a corner radius, so the entire app restyles from one file. The look is a soft, neumorphic UI: warm off-white ground, raised light surfaces, near-black ink for emphasis, and a single gold accent.

Auth state is a hook with three states. Firebase's auth listener is wrapped so screens can distinguish still checking from checked, nobody is signed in. Collapsing those two into one would flash the sign-in screen for a moment on every cold start before the session resolves.


Tech stack

Layer Choice
Framework React Native 0.81.5 via Expo SDK 57
Language TypeScript 5.9 (strict)
Navigation Expo Router v6 — file-based, with route groups
Auth Firebase Authentication (email/password)
Database Cloud Firestore (real-time listeners)
Icons @expo/vector-icons (Material Icons)
Styling Custom token-based design system, no UI library

Project structure

app/                          # every file here is a route (Expo Router)
├── (auth)/
│   ├── signIn.tsx            # email + password sign in
│   └── signUp.tsx            # account creation + Firestore profile write
└── (welcome)/
    └── (tabs)/
        ├── _layout.tsx       # tab navigator + sign-out button
        ├── EventBoard.tsx    # live feed, edit modal, delete
        └── AddEvent.tsx      # post a new event

CustomStyle.ts                # design tokens + shared stylesheet
firebaseConfig.ts             # Firebase init (gitignored — see setup)
firebaseConfig.example.ts     # template to copy
userAuthentication.ts         # onAuthStateChanged hook
types/Event.ts                # the Event shape

Route groups — the folders in (parentheses) — organise files without adding URL segments, which keeps the auth screens and the signed-in screens cleanly separated.

Data model

Class diagram: one UserProfile owns many Events, linked by uid, with the access rule for each operation

EventDB/{autoId}          title, date, location, description, uid, postedBy
userProfile/{authUid}     name, email

One profile owns many events, joined by the uid the event carries. postedBy is denormalised onto each event so the board renders a display name without a second read per card, and the profile document is keyed by the Auth UID, so a profile is always reachable from a UID with no query.

The operations on the diagram are the actual Firestore security rules, not just intent: reads require a signed-in user, creating an event requires its uid to match the caller, and updates and deletes are restricted to the owner. Ownership is enforced at the database, so hiding the edit and delete buttons on someone else's card is a convenience rather than the protection itself.


Running it locally

Prerequisites: Node.js 18+, and either the Expo Go app on your phone or an Android/iOS emulator.

git clone <your-repo-url>
cd EventBoard_sakib_shariq_raihan
npm install

Add your Firebase credentials. firebaseConfig.ts is gitignored, so copy the template and fill it in:

cp firebaseConfig.example.ts firebaseConfig.ts

Then in the Firebase console: create a project → add a Web app (</>) → paste the config values into firebaseConfig.ts → enable Authentication → Email/Password → create a Cloud Firestore database.

npx expo start

Press a for Android, i for iOS, or scan the QR code with Expo Go.


What I ran into

The tab bar wouldn't move. The floating pill kept stretching edge to edge and the icons sat above centre — the active circle actually overflowed the top of the bar. Styling it through the navigator's own style prop did nothing. The cause turned out to be that the tab bar pins itself to both screen edges using layout properties that take precedence over the ones I was setting, so my values were being silently discarded. It was also adding bottom padding for the home indicator, which pushed the icons up.

The fix was to stop fighting the positioning and inset the bar with margins instead, since margins shrink the box no matter how it is pinned, then zero out the padding to re-centre the icons. Reading the navigator's own layout source was faster than guessing at style overrides.


Notes

Firebase web API keys are not secrets — they identify the project, they don't grant access — but firebaseConfig.ts is kept out of version control anyway so each person runs against their own Firebase project. Access is controlled by Firestore security rules, which should require an authenticated user before this is exposed publicly.

About

Eventboard for event creation with auth.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages