{"id":51726509,"url":"https://github.com/pspanoudakis/pubtrail","last_synced_at":"2026-07-17T19:37:53.039Z","repository":{"id":369362784,"uuid":"1288334534","full_name":"pspanoudakis/pubtrail","owner":"pspanoudakis","description":"UX Prize 🏆 winner at KTH! A mobile 📱 app  for creating, joining, and tracking 🤳🔎 pub crawls 🍺 with real-time location 🗺️📍 features","archived":false,"fork":false,"pushed_at":"2026-07-04T23:12:05.000Z","size":3441,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T23:13:12.783Z","etag":null,"topics":["expo","firebase-auth","firestore","mapbox","react-native","redux-toolkit"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pspanoudakis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-03T13:54:15.000Z","updated_at":"2026-07-04T23:12:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pspanoudakis/pubtrail","commit_stats":null,"previous_names":["pspanoudakis/pubtrail"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/pspanoudakis/pubtrail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pspanoudakis%2Fpubtrail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pspanoudakis%2Fpubtrail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pspanoudakis%2Fpubtrail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pspanoudakis%2Fpubtrail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pspanoudakis","download_url":"https://codeload.github.com/pspanoudakis/pubtrail/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pspanoudakis%2Fpubtrail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35594052,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-17T02:00:06.162Z","response_time":116,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["expo","firebase-auth","firestore","mapbox","react-native","redux-toolkit"],"created_at":"2026-07-17T19:37:52.379Z","updated_at":"2026-07-17T19:37:53.022Z","avatar_url":"https://github.com/pspanoudakis.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PubTrail 🍺🗺️\n\n**PubTrail** is a mobile application for creating, joining, and tracking pub crawls with real-time location features. It is built with React Native and Expo. PubTrail lets users create routes between pubs, invite friends, add photos, document each stop with location information and notes, and discover nearby crawls.\n\n## UX Prize 🏆 winning project \n\nThis application was implemented as a project for the course **ID2216 Developing Mobile Applications at KTH**, winning the **UX Prize**, which is awarded to projects that stand out for their attention to the use case, usability and well-thought design, possibly creating a need that was previously unknown.\n\n### Team Members\n- Filip Todosov\n- Bogdan Stefanescu\n- Pavlos Spanoudakis\n\nAs an award-winning project, we also prepared a [short presentation](./docs/PubTrail_Project_Presentation.pdf) of our app to share our work and inspire our classmates.\n\n## Tech Stack\n\n- **React Native** + **Expo** - cross-platform UI (Android, iOS, Web)\n- **TypeScript** - strict type safety across the entire codebase\n- **Redux Toolkit** - centralized state management\n- **Mapbox** (`@rnmapbox/maps`) - interactive maps with user location tracking\n- **Firebase** - authentication (Google OAuth) and backend services\n- **Expo Router** - file-based navigation with auth-gated routing\n\n## App Features\n\n### Globally used features\n\n- **User Authentication** - email/password and Google OAuth via Firebase with automatic session handling and logout\n- **Auth Navigation** - unauthenticated users are redirected to the login screen; authenticated users land on the home screen\n- **Real-Time Location** - GPS permission handling and continuous user location tracking (10-second intervals)\n- **Interactive Map** - Mapbox integration with user location puck/marker, point markers, and route lines connecting stops (reused across different screens)\n- **Navigation** - Intuitive navigation using drawer menu \u0026 quick action buttons \n- **Reusable UI Components**\n- **Design System** - color palette, spacing scale, typography tokens, and shared styles\n- **Persistence** - connect all crawl, stop, and participant data to a Firestore database\n\n### Business logic features\n\n- **Add/Edit a crawl** - create/edit your own pub crawl\n- **Add/Edit a crawl stop** - create/edit stops in your pub crawl\n- **Pick stop location using suggestions** - suggest food \u0026 drink places near the user's location when the user tries to add a new stop, to avoid typing the pub information manually\n- **Share Crawl** - generate and share QR codes or invitation links for your crawl\n- **Join Crawl via QR Code or link** - scan a QR code or invitation link to join an existing crawl in real time\n- **Media Upload** - upload images taken using the camera to attach to a pub crawl\n- **Real Nearby Crawl Queries** - see live crawls that are currently nearby you, based on their latest stop\n- **Sobriety checker game** - A game that uses device's gyroscope to determine how drunk/sober you are\n\n### Demo screenshots\n\nMap-centric home screen UI, with quick action buttons \u0026 drawer menu:\n\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/home.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/drawer_menu.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\n\nAll the information of your active crawl in one place:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/active_pubs_readonly.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/edit_active_crawl.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\nAdd and edit crawl stops:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/add_stop.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/edit_stop.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\nCapture your favorite moments from your pub crawls:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/capture.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/submit_media.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\nCheck out nearby crawls, or your browse your previous ones:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/read_only_crawl.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/read_only_crawl2.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\nJoin others' crawls by scanning their QR code:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/share.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\nPlay a gyroscope-based minigame to check your sobriety level:\n\n\u003cdiv\u003e\n   \u003cimg src=\"./docs/images/sober.png\" alt=\"Home screen\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/sober2.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n   \u003cimg src=\"./docs/images/sober3.png\" alt=\"Drawer menu\" width=\"250vw\" /\u003e\n\u003c/div\u003e\n\n## Project File Structure\n\n```\n├── app.config.ts                    # Expo app configuration (name, icons, plugins, permissions)\n├── babel.confg.cjs                  # Babel config with expo preset and module-resolver aliases\n├── eas.json                         # EAS Build profiles (development, preview, production)\n├── firebase.json                    # Firebase Hosting + Firestore config\n├── metro.config.js                  # Metro bundler config (enables CSS support for web)\n├── package.json                     # Dependencies, scripts, and project metadata\n├── pubtrail-firebase-android.json   # Firebase Android config (used by app.config.ts)\n├── pubtrail-firebase-ios.plist      # Firebase iOS config (used by app.config.ts)\n├── tsconfig.json                    # TypeScript config\n│\n├── assets/\n│   └── images/                      # App icons and image assets\n│\n└── src/\n   ├── app/                         # Expo Router file-based routes\n   │   ├── _layout.tsx              # Root layout: Redux Provider, SafeArea, AuthGate (redirects)\n   │   ├── (auth)/\n   │   │   ├── _layout.tsx          # Auth stack layout\n   │   │   └── login.tsx            # Login screen\n   │   ├── (public)/\n   │   │   ├── _layout.tsx          # Drawer layout for the map home\n   │   │   └── index.tsx            # Map home + quick actions\n   │   └── (app)/\n   │       ├── _layout.tsx          # App stack header config\n   │       ├── activeCrawl/\n   │       │   ├── index.tsx            # Active crawl overview\n   │       │   ├── captureMedia.tsx     # Capture photo\n   │       │   ├── newStop.tsx          # Add a stop\n   │       │   └── stops/[stopUid].tsx  # Edit stop\n   │       ├── album/[crawlUid].tsx     # Crawl media album\n   │       ├── crawlHistory.tsx         # Past crawls\n   │       ├── join/[code].tsx          # External join link\n   │       ├── joinCrawl.tsx            # Join via QR / link\n   │       ├── nearbyCrawls.tsx         # Nearby public crawls\n   │       ├── newCrawl.tsx             # Create a crawl\n   │       ├── placeholder.tsx          # Placeholder screen\n   │       ├── shareCrawl.tsx           # Share link / QR\n   │       ├── soberCheck.tsx           # Sobriety mini-game\n   │       └── viewCrawl/\n   │           └── [crawlUid]/\n   │               ├── index.tsx        # Crawl details\n   │               └── stops/[stopUid].tsx # View stop details\n   │\n   ├── firebaseConfig.ts            # Firebase client and auth initialization\n   ├── firebaseEnv.ts               # Firebase env var validation\n   ├── mapboxConfig.ts              # Mapbox setup + nearby search helpers\n   ├── weatherService.ts            # Weather API helpers + crawl tips\n   │\n   ├── presenters/                  # Business logic layer (connects state to views)\n   │   ├── appLayoutPresenter.tsx\n   │   ├── captureMediaPresenter.tsx\n   │   ├── crawlDetailsPresenter.tsx\n   │   ├── crawlHistoryPresenter.tsx\n   │   ├── editCrawlPresenter.tsx\n   │   ├── editStopPresenter.tsx\n   │   ├── externalJoinPresenter.tsx\n   │   ├── joinCrawlPresenter.tsx\n   │   ├── loginPresenter.tsx\n   │   ├── mapHomePresenter.tsx\n   │   ├── mapPresenter.tsx\n   │   ├── mediaAlbumPresenter.tsx\n   │   ├── nearbyCrawlsPresenter.tsx\n   │   ├── newStopPresenter.tsx\n   │   ├── placeholderPresenter.tsx\n   │   ├── shareCrawlPresenter.tsx\n   │   ├── soberCheckPresenter.tsx\n   │   └── viewCrawlStopPresenter.tsx\n   │\n   ├── views/                       # Pure UI components (receive data via props)\n   │   ├── ExternalJoinView.tsx\n   │   ├── captureMediaView.tsx\n   │   ├── crawlDetailsView.tsx\n   │   ├── crawlHistoryView.tsx\n   │   ├── editCrawlView.tsx\n   │   ├── editStopView.tsx\n   │   ├── joinCrawlView.tsx\n   │   ├── loginView.tsx\n   │   ├── mapView.tsx\n   │   ├── mediaAlbumView.tsx\n   │   ├── nearbyCrawlsView.tsx\n   │   ├── newStopView.tsx\n   │   ├── placeholderView.tsx\n   │   ├── shareCrawlView.tsx\n   │   ├── soberCheckView.tsx\n   │   ├── viewCrawlStopView.tsx\n   │   ├── components/              # Map + UI building blocks\n   │   ├── navigation/              # Drawer + overlay UI\n   │   └── shared/                  # View helpers\n   │\n   ├── redux/                       # Redux state management\n   │   ├── actions/                 # Thunks and action creators\n   │   │   ├── activeCrawlActions.ts\n   │   │   ├── authActions.ts\n   │   │   ├── mapActions.ts\n   │   │   ├── nearbyCrawlsActions.ts\n   │   │   ├── newCrawlDraftActions.ts\n   │   │   ├── stopEditorActions.ts\n   │   │   └── weatherActions.ts\n   │   ├── reducers/                # Slice reducers and root reducer\n   │   │   ├── activeCrawlReducer.ts\n   │   │   ├── authReducer.ts\n   │   │   ├── mapReducer.ts\n   │   │   ├── nearbyCrawlsReducer.ts\n   │   │   ├── newCrawlDraftReducer.ts\n   │   │   ├── stopEditorReducer.ts\n   │   │   ├── weatherReducer.ts\n   │   │   └── index.ts\n   │   ├── selectors/               # Derived state selectors\n   │   │   ├── activeCrawlSelectors.ts\n   │   │   ├── authSelectors.ts\n   │   │   ├── nearbyCrawlsSelectors.ts\n   │   │   ├── newCrawlDraftSelectors.ts\n   │   │   └── stopEditorSelectors.ts\n   │   ├── gateways/                # Firestore and data access\n   │   │   ├── activeCrawlGateway.ts\n   │   │   ├── firestoreGatewayUtils.ts\n   │   │   └── userGateway.ts\n   │   ├── mappers/                 # Firestore data mappers\n   │   │   └── activeCrawlMappers.ts\n   │   └── store/                   # Store config and typed hooks\n   │       ├── store.ts\n   │       └── hooks.ts\n   │\n   ├── services/                    # External service helpers (Cloudinary)\n   ├── hooks/                       # App-specific hooks\n   ├── styles/                      # Design system\n   ├── types/                       # Shared types\n   └── utils/                       # Shared utilities\n```\n\n## Getting Started\n\n1. **Install dependencies**\n   ```bash\n   npm install\n   ```\n\n2. **Set up environment variables** - create a `.env` file:\n   ```\n   EXPO_PUBLIC_FIREBASE_API_KEY=\u003cyour-firebase-api-key\u003e\n   EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=\u003cyour-firebase-auth-domain\u003e\n   EXPO_PUBLIC_FIREBASE_PROJECT_ID=\u003cyour-firebase-project-id\u003e\n   EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=\u003cyour-firebase-storage-bucket\u003e\n   EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=\u003cyour-firebase-messaging-sender-id\u003e\n   EXPO_PUBLIC_FIREBASE_APP_ID=\u003cyour-firebase-app-id\u003e\n   EXPO_PUBLIC_GOOGLE_OAUTH_CLIENT_ID=\u003cyour-google-oauth-client-id\u003e\n   EXPO_PUBLIC_MAPBOX_TOKEN=\u003cyour-mapbox-access-token\u003e\n   EXPO_PUBLIC_OPENWEATHER_API_KEY=\u003cyour-openweather-api-key\u003e\n   ```\n\n3. **Configure native Firebase** (for Android/iOS builds)\n   - Download `pubtrail-firebase-android.json` from Firebase Console → place at the project root\n   - Download `pubtrail-firebase-ios.plist` from Firebase Console → place at the project root\n   - Ensure your package name (`com.anonymous.pubtrail`) matches your Firebase project settings\n\n4. **Run the development server**\n   ```bash\n   npm run dev\n   ```\n\n5. **Run on Android device/emulator**\n   ```bash\n   npm run android:dev\n   ```\n\n\n6. **Build an Android APK locally**\n   ```bash\n   npm run android:build_apk\n   ```\n\n7. **Publish web update**\n   ```bash\n   npm run web:publish\n   ```\n\n## 3rd-Party UI Components\n\nWe have used these user visible components from external libraries in our project:\n\n| Library | Which component | Where |\n|---------|-----------|---------|\n| `@rnmapbox/maps` | `MapView`, `Camera`, `MarkerView`, route `ShapeSource`/`LineLayer` | `src/views/components/FullScreenMap.native.tsx`, `src/views/components/FullScreenMap.web.tsx`, `src/views/components/mapOverlay.native.tsx`, `src/views/components/mapOverlay.web.tsx`, `src/views/components/MapUserLocationMarker.tsx` |\n| `@react-native-community/datetimepicker` | `DateTimePicker` | `src/views/editStopView.tsx` |\n| `react-native-qrcode-svg` | `QRCode` | `src/views/shareCrawlView.tsx` |\n| `expo-camera` | `CameraView` | `src/views/captureMediaView.tsx`, `src/views/joinCrawlView.tsx` |\n| `expo-sensors` | `Gyroscope` | `src/presenters/soberCheckPresenter.tsx` |\n| `@react-navigation/drawer` | `DrawerContentScrollView` | `src/views/navigation/AppDrawerContent.tsx` |\n| `react-native-safe-area-context` | `useSafeAreaInsets` | `src/views/navigation/IndexQuickActionsOverlay.tsx`, `src/views/navigation/AppDrawerContent.tsx` |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpspanoudakis%2Fpubtrail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpspanoudakis%2Fpubtrail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpspanoudakis%2Fpubtrail/lists"}