FoodBridge connects food donors (restaurants, individuals) with receivers (NGOs, individuals). Donors post surplus food; receivers request it; both coordinate pickup via in‑app chat and maps.
- ✅ Role‑based Auth (Donor / Receiver)
- 🗺️ Nearby Listings with distance and expiry indicators
- 📨 Request Flow with accept/reject and cleanup actions
- 💬 In‑app Chat per listing
- ⭐ Ratings for trust and safety
- 📸 Image Uploads to Firebase Storage
- 📍 GPS & Maps via Expo Location
- App: Expo (React Native, TypeScript)
- State: Local screen state + Context for auth
- Backend: Firebase (Auth, Firestore, Storage)
- Realtime: Firestore
onSnapshot(requests, messages)
React Native (Expo)
├── Screens (Map, ListingDetail, IncomingRequests, Chat)
├── Components (FoodCard, ...)
└── Contexts (Auth)
│
▼
Firebase Services
├── Auth
├── Firestore (users, listings, requests, messages, ratings)
└── Storage (images)
users:{ id, name, role, rating, createdAt }listings:{ id, donorId, title, description, foodType, quantity, expiryTime, pickupLocation, images, status, requestedBy[], createdAt, updatedAt }requests:{ id, listingId, requesterId, requesterName, donorId, donorName, status, createdAt }messages:{ id, listingId, senderId, text, timestamp }
- Node.js 18+
- Expo CLI:
npm i -g @expo/cli - Firebase project (Firestore + Auth + Storage enabled)
git clone <your-repo-url>
cd foodbridge
npm install
npm run start
# or
expo startOpen on device with Expo Go, or run npm run android / npm run ios.
Update src/services/firebase.ts with your Firebase config. Firestore is initialized with long polling for React Native reliability.
EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key
EXPO_PUBLIC_FIREBASE_API_KEY=your_firebase_api_keyrules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /listings/{listingId} {
allow read: if request.auth != null;
allow create: if request.auth != null && request.resource.data.donorId == request.auth.uid;
allow update: if request.auth != null && request.resource.data.donorId == request.auth.uid;
}
match /requests/{requestId} {
allow read, create: if request.auth != null;
allow update, delete: if request.auth != null;
}
match /messages/{messageId} {
allow read, write: if request.auth != null;
}
match /ratings/{ratingId} {
allow read, create: if request.auth != null;
}
}
}npm start– Expo dev servernpm run android/npm run ios– Run on emulator/devicenpm run web– Run in webnpm run build– Build bundle
- Realtime not updating: Ensure each request document includes
donorIdanddonorName. The app listens withwhere("donorId", "==", donorId). - Firestore transport error: We enable long polling via
initializeFirestore(..., { experimentalAutoDetectLongPolling: true })for React Native. - Date errors: Firestore Timestamps are normalized to JS
Dateobjects in the API service.
- Fork & branch:
feat/your-feature - Code with type safety and clear naming
- PR with description and screenshots (if UI)
MIT
Made with ❤️ to reduce food waste and help communities.