# Project export: NeighborNom

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Fast meals, faster friends: Regulating food waste and bringing neighbors together, one ingredient at a time.
- Devpost: https://devpost.com/software/neighbornom
- GitHub: https://github.com/skethini/foodhood
- Team: 3 GitHub contributor(s) — Ashmit Dutta (27 commits), Sumedha Kethini (17 commits), danifenster (10 commits)

## Devpost submission (written by the team)

### Inspiration

Inspired by our desire to be connected with people physically close to our through the power of food

### What it does

Provides a messaging system for users to request needed ingredients and offer extra ones. Offers a media platform for users to post their food and tag neighbors.

### How we built it

Split into front-end and back-end development, with one team member taking back-end and the rest on front-end.

### Challenges we ran into

Figuring out the databases and debugging.

### Accomplishments we're proud of

Building a chat feature and fully functional app with impressive UI/UX

### What we learned

The power and complexity of databases

### What's next

Adding an inventory feature that allows users to scan images using AI to inform of expiration dates.

## README (from the GitHub repository)

### Overview

This is our submission to Stanford's Treehacks 2024. Our product, NeighborNom is intended to help families connect through their love of food. By matching neighbors who live close by, the app serves as a way to reduce food waste and carbon emissions in a way that helps families grow their bond with their neighbors around the area. 


## Detected evidence (automated analysis)

Indexed codebase: 11 recognized source files, 29 KB.
- Firebase (technology) — detected in the code
- JavaScript (language) — detected in the code
- React (technology) — detected in the code
- CSS (language) — claimed on Devpost, not found in the code
- HTML (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (17 of 17)

```
.DS_Store
my-app/.gitignore
my-app/App.js
my-app/app.json
my-app/babel.config.js
my-app/contexts/AuthContext.js
my-app/firebaseConfig.js
my-app/package.json
my-app/requirements.txt
my-app/screens/chat.screen.jsx
my-app/screens/login.screen.jsx
my-app/screens/media.jsx
my-app/screens/navigation.js
my-app/screens/profile.jsx
my-app/screens/SignUp.jsx
package.json
README.md
```

### Dependencies

- my-app/package.json: @babel/core@^7.20.0, @react-native-google-signin/google-signin@^11.0.0, @react-native-picker/picker@^2.6.1, @react-navigation/native@^6.1.7, @react-navigation/native-stack@^6.9.12, expo@^49.0.23, expo-file-system@~16.0.6, expo-image-picker@~14.3.2, expo-status-bar@~1.4.4, firebase@^10.8.0, react@18.2.0, react-native@0.71.8
- package.json: firebase@^10.8.0

### Recent commits (newest first)

- Update README.md
- finalized for treehacks
- Update profile.jsx
- Merge branch 'main' of https://github.com/skethini/foodhood
- Update media.jsx
- Merge branch 'main' of https://github.com/skethini/foodhood
- Update profile.jsx
- make media page
- Update chat.screen.jsx
- lol
- fix navigation error
- Merge branch 'main' of https://github.com/skethini/foodhood
- Update chat.screen.jsx
- Merge branch 'main' of https://github.com/skethini/foodhood
- updated profile
- toProfile button
- Update chat.screen.jsx
- Update chat.screen.jsx
- Merge branch 'main' of https://github.com/skethini/foodhood
- update

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "dependencies": {
    "firebase": "^10.8.0"
  }
}

```

### my-app/package.json

```
{
  "name": "my-app",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-native-google-signin/google-signin": "^11.0.0",
    "@react-native-picker/picker": "^2.6.1",
    "@react-navigation/native": "^6.1.7",
    "@react-navigation/native-stack": "^6.9.12",
    "expo": "^49.0.23",
    "expo-file-system": "~16.0.6",
    "expo-image-picker": "~14.3.2",
    "expo-status-bar": "~1.4.4",
    "firebase": "^10.8.0",
    "react": "18.2.0",
    "react-native": "0.71.8"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0"
  },
  "private": true
}

```

### my-app/App.js

```javascript
import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { StyleSheet } from 'react-native';

// Screen Imports
import ProfileScreen from './screens/profile';
import LoginScreen from './screens/login.screen';
import SignUpScreen from './screens/SignUp';
import ChatScreen from './screens/chat.screen';
import MediaScreen from './screens/media'
import { auth } from './firebaseConfig';

const Stack = createNativeStackNavigator();

export default function App() {
  const [isSignedIn, setIsSignedIn] = useState(false);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, user => {
      setIsSignedIn(!!user);
    });
    return unsubscribe;
  }, []);

  return (
    <NavigationContainer>
      <Stack.Navigator>
        {isSignedIn ? (
          // Stack Navigator for Signed In Users
          <>
          <Stack.Screen name="Chat" component={ChatScreen} options={{ title: 'Chat' }} />
          <Stack.Screen name="Profile" component={ProfileScreen} options={{ title: 'Profile' }} />
          <Stack.Screen name="Media" component={MediaScreen} options={{ title: 'Media' }} />

          </>
        ) : (
          // Stack Navigator for Authentication Screens
          <>
            <Stack.Screen name="Login" component={LoginScreen} options={{ title: 'Login' }} />
            <Stack.Screen name="SignUp" component={SignUpScreen} options={{ title: 'Sign Up' }} />
            {/* <Stack.Screen name="Profile" component={ProfileScreen} options={{ title: 'Profile' }} /> */}

          </>
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

// Your StyleSheet definitions are correctly placed here
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  button: {
    backgroundColor: '#ccc',
    padding: 10,
    borderRadius: 15,
    margin: 10,
  },
});

```

### my-app/babel.config.js

```javascript
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
  };
};

```

### my-app/firebaseConfig.js

```javascript
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyDBZJseKOEnlO3wevP9lt8vFZ9btZ3O2NM",
  authDomain: "foodhood-9e04f.firebaseapp.com",
  projectId: "foodhood-9e04f",
  storageBucket: "foodhood-9e04f.appspot.com",
  messagingSenderId: "451560279977",
  appId: "1:451560279977:web:9c9344e2e3432d7d053eb8",
  measurementId: "G-T2VZ2B5BPS"
};


// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const analytics = getAnalytics(app);
const auth = getAuth(app);
export { app, auth, db };
```

### my-app/screens/navigation.js

```javascript
// Navigation.js

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import SignUpScreen from '../screens/SignUp.jsx';  // Adjust the actual path
import ProfileScreen from '../screens/profile.jsx';      // Adjust the actual path

const Stack = createStackNavigator();

function AppNavigation() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="SignUp" component={SignUpScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
        {/* Add more screens as needed */}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default AppNavigation;

```

### my-app/contexts/AuthContext.js

```javascript
// contexts/AuthContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';
import { auth } from '../firebaseConfig';

const AuthContext = createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState(null);
  const [loading, setLoading] = useState(true);

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password); 
  }

  function signOut() {
    return auth.signOut(); 
  }

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(user => { 
      setCurrentUser(user);
      setLoading(false);
    });
    return unsubscribe;
  }, []);

  const value = {
    currentUser,
    login,
    signOut
  };

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

```

### my-app/screens/login.screen.jsx

```javascript
import React, { useState } from 'react';
import { StyleSheet, View, TextInput, Button, Alert, Text } from 'react-native';
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from "firebase/auth";

export default function LoginScreen({ navigation }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const auth = getAuth();

  const handleSignUp = () => {
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        // Signed up
        navigation.navigate('Profile');
      })
      .catch((error) => {
        Alert.alert("Registration Error", error.message);
      });
  };

  const handleLogin = () => {
    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        // Signed in
        navigation.navigate('Profile');
      })
      .catch((error) => {
        Alert.alert("Login Error", error.message);
      });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome to <Text style={{fontWeight: 'bold'}}>NeighborNom</Text></Text>
      <TextInput
        style={styles.input}
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
      />
      <TextInput
        style={styles.input}
        placeholder="Password"
        secureTextEntry
        value={password}
        onChangeText={setPassword}
      />
      <View style={styles.buttonContainer}>
        <Button
          title="Login"
          onPress={handleLogin}
          color="#4CAF50"
        />
        <Button
          title="Sign Up"
          onPress={() => navigation.navigate('SignUp')}
          color="#4CAF50"
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F0F8F7', // Soft matcha tint background
    padding: 20,
  },
  title: {
    fontSize: 24,
    marginBottom: 20,
    textAlign: 'center',
    color: '#4CAF50', // Matcha green
  },
  input: {
    marginBottom: 10,
    paddingHorizontal: 15,
    height: 50,
    borderColor: '#ccc',
    borderWidth: 1,
    borderRadius: 5,
    width: '100%',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    width: '100%',
  },
});

```

### my-app/screens/profile.jsx

```javascript
import React, { useState, useEffect } from 'react';
import { View, Text, Image, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import { doc, getDoc } from "firebase/firestore";
import { db } from '../firebaseConfig'; 
import { getAuth, signOut } from 'firebase/auth';

const Profile = ({ navigation }) => {
  const [profile, setProfile] = useState(null);
  const auth = getAuth();
  const userId = auth.currentUser?.uid; 

  useEffect(() => {
    const fetchUserProfile = async () => {
      try {
        const docRef = doc(db, "users", userId);
        const docSnap = await getDoc(docRef);
  
        if (docSnap.exists()) {
          setProfile(docSnap.data());
        } else {
          console.log("No such document!");
        }
      } catch (error) {
        console.error("Error fetching profile:", error);
        Alert.alert("Profile Error", "Failed to fetch profile data. Please try again later.");
      }
    };
  
    if (userId) {
      fetchUserProfile();
    }
  }, [userId]);
  

  const handleLogout = async () => {
    try {
      await signOut(auth);
      navigation.replace('Login'); 
    } catch (error) {
      console.error("Logout error:", error);
      Alert.alert("Logout Error", error.message);
    }
  };

  if (!profile) {
    return (
      <View style={styles.container}>
        <Text>Loading profile...</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <View style={styles.profileContainer}>
        <Image source={{ uri: profile.imageUrl }} style={styles.profileImage} />
        <Text style={styles.name}>{profile.name}</Text>
        <Text style={styles.bio}>{profile.bio}</Text>
      </View>
      <View style={styles.buttonContainer}>
        <TouchableOpacity onPress={handleLogout} style={[styles.button, styles.logoutButton]}>
          <Text style={styles.buttonText}>Logout</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => navigation.navigate('Chat')} style={[styles.button, styles.chatButton]}>
          <Text style={styles.buttonText}>Go to Chat</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => navigation.navigate('Media')} style={[styles.button, styles.mediaButton]}>
          <Text style={styles.buttonText}>Go to Media Page</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => navigation.navigate('Settings')} style={[styles.button, styles.settingsButton]}>
          <Text style={styles.buttonText}>Settings</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5', // Light grey background
    padding: 20,
  },
  profileContainer: {
    alignItems: 'center',
    marginBottom: 30,
  },
  profileImage: {
    width: 150,
    height: 150,
    borderRadius: 75, // Circular image
    marginBottom: 20,
  },
  name: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 4,
  },
  bio: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 20,
  },
  buttonContainer: {
    alignItems: 'center',
  },
  button: {
    backgroundColor: '#28a745', 
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 25,
    marginVertical: 10, 
    minWidth: 150, 
    alignItems: 'center',
  },
  buttonText: {
    color: '#ffffff', 
    fontSize: 18,
  },
  logoutButton: {
    backgroundColor: '#dc3545', 
  },
  chatButton: {
    backgroundColor: '#007bff', 
  },
  mediaButton: {
    backgroundColor: '#ffc107', 
  },
  settingsButton: {
    backgroundColor: '#17a2b8', 
  },
  notificationsButton: {
    backgroundColor: '#6c757d', 
  },
});

export default Profile;

```

### my-app/screens/media.jsx

```javascript
import React, { useState, useEffect } from 'react';
import { View, Text, Image, StyleSheet, FlatList, TouchableOpacity, Alert } from 'react-native';
import { getAuth } from 'firebase/auth';
import { doc, getDoc, collection, getDocs, addDoc } from 'firebase/firestore';
import { getStorage, ref, uploadBytes, getDownloadURL, listAll } from 'firebase/storage';
import * as ImagePicker from 'expo-image-picker';
import { db } from '../firebaseConfig';



const SocialMediaPage = () => {
  const auth = getAuth();
  const storage = getStorage();
  const [user, setUser] = useState(null);
  const [photos, setPhotos] = useState([]);
  const [imageUri, setImageUri] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const userDocRef = doc(collection(db, 'users'), auth.currentUser.uid);
      const userDocSnap = await getDoc(userDocRef);

      if (userDocSnap.exists()) {
        setUser(userDocSnap.data());
      } else {
        console.log('No such document for user!');
      }
    };

    fetchUser();
    fetchPhotos(); 
  }, []);


  const fetchPhotos = async () => {
    try {
      const storage = getStorage();
      const imagesFolderRef = ref(storage, 'images');
      const items = await listAll(imagesFolderRef);
      const photosData = [];
      for (const item of items.items) {
        try {
          const downloadURL = await getDownloadURL(item);
          photosData.push({ id: item.name, imageUrl: downloadURL });
        } catch (error) {
          console.error('Error getting download URL:', error);
        }
      }
      setPhotos(photosData);
  
    } catch (error) {
      console.error('Error fetching photos:', error);
    }
  };
  
  
  const pickImage = async () => {
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();

    if (status !== 'granted') {
      Alert.alert('Permission denied', 'Sorry, we need camera roll permissions to make this work!');
      return;
    }

    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.cancelled) {
      setImageUri(result.uri);
    }
  };

  const uploadImage = async () => {
    try {
      if (imageUri) {
        const imageRef = ref(storage, `images/${auth.currentUser.uid}/${Date.now()}.jpg`);
        const snapshot = await uploadBytes(imageRef, await fetch(imageUri));
        const downloadURL = await getDownloadURL(snapshot.ref);

        const photosCollection = collection(db, 'photos');
        await addDoc(photosCollection, { userId: auth.currentUser.uid, imageUrl: downloadURL });

        setImageUri(null);

        fetchPhotos();
      }
    } catch (error) {
      console.error('Error uploading image:', error);
    }
  };

  return (
    <View style={styles.container}>
      <Text>Welcome, {user?.displayName || 'User'}!</Text>
      <TouchableOpacity onPress={pickImage}>
        <Text>Choose a photo</Text>
      </TouchableOpacity>
      {imageUri && <Image source={{ uri: imageUri }} style={styles.previewImage} />}
      <TouchableOpacity onPress={uploadImage}>
        <Text>Upload</Text>
      </TouchableOpacity>

      <FlatList
        data={photos}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={styles.photoContainer}>
            <Image source={{ uri: item.imageUrl }} style={styles.photoImage} />
          </View>
        )}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  previewImage: {
    width: 200,
    height: 200,
    marginVertical: 10,
  },
  photoContainer: {
    marginVertical: 10,
  },
  photoImage: {
    width: 200,
    height: 200,
  },
});

export default SocialMediaPage;

```

[2 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]