Sugar is a hyper-local online marketplace for buying and selling food. This project consists of a backend API built with Node.js and Express, a MongoDB database hosted on MongoDB Atlas, and a frontend developed using React Native with Expo.
- Frontend: React Native, Expo, TypeScript, React Navigation
- Backend: Node.js, Express
- Database: MongoDB Atlas
- UI Library: React Native Paper (Material Design 3)
- State Management: React Context API
- API Client: Axios
- Forms: Formik, Yup (for validation)
- Image Handling: expo-image-picker
- Authentication: JSON Web Tokens (JWT)
- Deployment: Heroku (Backend), Expo Application Services (EAS) potentially for Frontend builds
- CI/CD: GitHub Actions
- Linting/Formatting: ESLint, Prettier
- Node.js (v14 or later recommended)
- npm (usually comes with Node.js)
- MongoDB Atlas account (free tier recommended for development)
- Expo Go app installed on your mobile device (for testing)
- Git
git clone https://github.com/yourusername/sugar.git # Replace with your repo URL
cd sugar
a) Install Dependencies:
cd backend
npm install
b) Configure Environment Variables:
Create a .env
file in the /backend
directory with the following content. Ensure this file is added to .gitignore
.
PORT=3000
JWT_SECRET=YOUR_STRONG_RANDOM_SECRET_HERE # Replace with a strong, unique secret
NODE_ENV=development
MONGODB_URI=mongodb+srv://dev-user:sugardevuser1@sugarcluster.mssvz.mongodb.net/sugar_marketplace?retryWrites=true&w=majority&appName=SugarCluster # Replace with your MongoDB Atlas connection string
JWT_EXPIRATION=1h # Or your preferred token expiration
- Replace
MONGODB_URI
with your actual connection string from MongoDB Atlas. - Replace
JWT_SECRET
with a secure, randomly generated string.
a) Install Dependencies:
Navigate back to the project root directory if you are in /backend
.
cd ..
npm install
- Troubleshooting Tip: If you encounter peer dependency conflicts, especially after adding new libraries, try running:
npm install --legacy-peer-deps
b) Configure Environment Variables:
Create a .env
file in the project root directory (/
) with the following content. Ensure this file is added to .gitignore
.
# Base URL for the backend API
API_BASE_URL=http://localhost:3000 # Use your machine's local IP if testing on a physical device: http://[YOUR_LOCAL_IP]:3000
# Optional: Specify device type if needed for specific logic (not currently used)
# DEVICE_TYPE=mac # or 'android'
- Replace
localhost
with your computer's local network IP address if you are running the app on a physical device using Expo Go. You can usually find this viaipconfig
(Windows) orifconfig
(macOS/Linux).
You'll need two separate terminal windows.
Terminal 1: Start the Backend Server
cd backend
node server.js
# Or use nodemon if installed: nodemon server.js
The backend should start, typically on port 3000.
Terminal 2: Start the Frontend Development Server
# Ensure you are in the project root directory
npx expo start
This will start the Metro bundler. Scan the QR code shown in the terminal using the Expo Go app on your device, or press i
for iOS simulator / a
for Android emulator if configured.
This section details the primary user-facing features implemented in the Sugar application.
Purpose: Provides a central view for users to browse food items listed by others in the community. It separates items into "Free" and "Other Items" categories.
Location: src/screens/HomeScreen.tsx
Data Flow:
- Fetching: On screen focus (
useFocusEffect
), fetches all available listings via aGET
request to${API_BASE_URL}/api/marketplace
. - Sorting: Fetched items are sorted by creation date (
createdAt
) in descending order (newest first). - Filtering: Items are categorized based on price:
- Free Items: Listings where the
price
is exactly "$0.00" or "0.00". - Other Items: All other priced listings.
- Free Items: Listings where the
- Display: Uses two separate horizontal
FlatList
components to display cards for "Free Items" and "Other Items". If no items are available in a category or overall, appropriate messages are displayed.
UI Components & Interaction:
- Item Card (
Card
): Displays a preview of each listing (Image, Title, Producer, Formatted Price). Usesreact-native-paper
. - Modal (
Modal
,Portal
): Triggered by pressing an item card (handleCardPress
). Displays detailed information about the selected item (selectedItem
).- Modal Content: Shows Title, Image, Producer, Price (formatted), Description, Origin, Certifications, Expiry Date, Location (reverse geocoded name if
shareLocation
is true). - Modal Dismiss: Closes when tapped outside or via
handleModalDismiss
.
- Modal Content: Shows Title, Image, Producer, Price (formatted), Description, Origin, Certifications, Expiry Date, Location (reverse geocoded name if
- Headers: "Free Items" and "Other Items" headers use
Text
withvariant="headlineSmall"
andcolor={theme.colors.primary}
for consistency.
Key Data Attributes (MarketplaceItem
Interface):
This interface (src/screens/HomeScreen.tsx
) defines the structure of items fetched from the marketplace. Key attributes relevant for checkout functionality include:
_id
: (string) Unique identifier for the listing. Crucial for identifying the item in the cart/checkout.title
: (string) Name of the item.price
: (string) The listed price (e.g., "$10.00", "Free"). Needs parsing for calculations. Use theparsePrice
helper inHomeScreen
as a reference.unitType
: (string, optional: 'unit' | 'size') Indicates if the price is per unit or based on size/weight.sizeMeasurement
: (string, optional) Specific size/weight details (e.g., "lb", "kg", "oz") ifunitType
is 'size'.quantity
: (number) Note: This field is defined in the backend model but might not be directly fetched/displayed on theHomeScreen
card/modal currently. It's essential for managing stock during checkout and should be fetched/available.userId
: (string) The ID of the user who listed the item. Needed for messaging and potentially for seller information during checkout.
Integration Points & Building Upon:
- Checkout:
- The "Add to Cart" button (
handleAddToCart
) uses theuseCart
context hook (src/contexts/CartContext.tsx
) to add theselectedItem
to the cart state. - The cart context likely needs enhancement to handle quantity selection and potentially fetching full item details (including available
quantity
) when an item is added. - The checkout process will need to retrieve items from the
CartContext
, use their_id
,price
,quantity
, etc., and interact with a backend checkout/order endpoint.
- The "Add to Cart" button (
- Messaging:
- The "Message Seller" button (
handleMessageSeller
) in the modal currently logs theselectedItem.userId
. - To build the messaging feature: This handler should be modified to navigate to a new
ChatScreen
or similar component. - It must pass the
selectedItem.userId
(the seller's ID) and potentially theselectedItem._id
(listing ID) as navigation parameters to the chat screen to initiate or resume a conversation with the correct seller about the specific item. The authenticated user's ID can be retrieved fromAuthContext
.
- The "Message Seller" button (
Purpose: Allows authenticated users to create new food listings to be displayed on the marketplace.
Location: src/screens/PostScreen.tsx
Workflow:
- Authentication: Screen access might be restricted to logged-in users (handled via navigation setup).
- Form: Uses
Formik
for form state management andYup
for validation. - Image Selection: Users can optionally select an image via the "Select Image" button (
handleChooseImageSource
), which presents anAlert
to choose between taking a photo (takePhoto
) or selecting from the library (selectImage
). Usesexpo-image-picker
. - Image Upload: If a new image is selected:
- It's uploaded first using
FormData
via aPOST
request to${API_BASE_URL}/api/fooditems/upload-image
. This requires the auth token. - The backend responds with the relative path of the stored image (e.g.,
uploads/image-123.jpg
).
- It's uploaded first using
- Form Submission (
handleSubmit
):- Form data (including the image path received from the upload step, if any) is compiled.
- Location coordinates are fetched using
expo-location
ifshareLocation
is true. - Data is sent via a
POST
request to${API_BASE_URL}/api/fooditems
, including the auth token in the headers. - On success, navigates back or to
MyListingsScreen
. Errors are displayed.
Form Fields & Validation (validationSchema
):
title
: (string, required)producer
: (string, required)price
: (string, required) Validated using regex/^(\$?(0(\.\d{1,2})?|([1-9]\d*(\.\d{1,2})?))|Free)$/i
to match formats like$10.00
,10.00
,0.50
,$0.50
,0
,$0
, orFree
(case-insensitive). The$
prefix is added visually usingTextInput.Affix
and is not part of the stored/validated value unless typed by the user (validation allows optional $).keyboardType="decimal-pad"
.unitType
: (enum: 'unit' | 'size', required) Radio buttons control conditional rendering ofsizeMeasurement
.quantity
: (string representing number, required) Must be a positive integer.keyboardType="numeric"
.sizeMeasurement
: (string, required ifunitType
is 'size')description
: (string, optional)origin
: (string, optional)certifications
: (array of strings, optional) Checkbox group.contactMethod
: (enum: 'Email' | 'Phone' | 'Direct Message', required) Dropdown.expiryDate
: (Date | null, optional) Date picker.shareLocation
: (boolean) Checkbox.location
: (object { latitude, longitude }, required ifshareLocation
is true) Map interaction planned.imageUri
: (string | null) Stores local URI before upload or backend path after upload.
Building Upon:
- Add more complex validation rules in the
Yup
schema. - Introduce new fields to the form, ensuring they are added to
initialValues
,Formik
's JSX, the validation schema, and the backend model/route (backend/models/FoodItem.js
,backend/routes/foodItems.js
). - Modify image handling (e.g., allow multiple images, add cropping).
- Integrate a map view component for selecting location visually.
Purpose: Allows users to view, manage, and edit the listings they have previously created.
Location:
- View/Manage:
src/screens/MyListingsScreen.tsx
- Edit:
src/screens/EditListingScreen.tsx
Data Flow (MyListingsScreen
):
- Fetching: On screen focus (
useFocusEffect
), fetches the user's specific listings via aGET
request to${API_BASE_URL}/api/fooditems/my-listings
. Requires the auth token. - Display: Shows the fetched listings in a vertical
FlatList
. Each item includes Title, Price, Image, and action buttons.
Actions (MyListingsScreen
):
- Add New: A button navigates the user to
PostScreen
. - Edit: An "Edit" button (
handleEdit
) on each listing card navigates the user toEditListingScreen
, passing theitem._id
as a route parameter. - Delete: A "Delete" button (
handleDelete
) triggers anAlert
confirmation. If confirmed, sends aDELETE
request to${API_BASE_URL}/api/fooditems/:itemId
(using the item's_id
). Requires the auth token. Refreshes the list on successful deletion.
Workflow (EditListingScreen
):
- Fetch Data: Retrieves the
itemId
from route parameters. Fetches the specific listing data via aGET
request to${API_BASE_URL}/api/fooditems/:itemId
. Requires the auth token. - Pre-populate Form: Uses the fetched data to set the
initialValues
for theFormik
form, allowing the user to see and modify existing data. - Image Handling: Similar to
PostScreen
. If the user selects a new image, it's uploaded first viaPOST
to${API_BASE_URL}/api/fooditems/upload-image
. The existing image URI is used otherwise. - Form Submission: On submit (
handleSubmit
), sends the updated form data (including new image path, if applicable) via aPUT
request to${API_BASE_URL}/api/fooditems/:itemId
. Requires the auth token. Navigates back on success.
Form Fields & Validation (EditListingScreen
):
- Mirrors the fields and validation schema of
PostScreen
, adapted for editing. Ensures consistency in data requirements.
Building Upon:
- Add functionality to mark items as "Sold" or "Unavailable" instead of just deleting.
- Implement filtering or sorting options for the user's listings.
- Enhance the UI for managing listings (e.g., show listing status).
- Refine error handling and user feedback during edit/delete operations.
Location: src/screens/ComponentPlaygroundScreen.tsx
Purpose: The Component Playground is a development screen designed to:
- Rapidly prototype and test UI components in isolation.
- Showcase different variations and states of components.
- Facilitate design reviews and ensure consistency.
- Aid in debugging component-specific issues.
Usage During Development:
- The playground provides buttons to navigate directly to all major application screens, speeding up the development workflow.
- It serves as a living style guide where developers can see examples of core components (Buttons, Inputs, Cards, etc.) using the application's theme.
Adding Components to the Playground:
- Open
src/screens/ComponentPlaygroundScreen.tsx
. - Import the component you want to showcase.
- Add a new
View
section within theScrollView
. - Include a
Text
component as a title for the section. - Render different variations of your component within this section.
// Example: Adding NewComponent to ComponentPlaygroundScreen.tsx
import NewComponent from '../components/NewComponent';
// ... other imports
const ComponentPlaygroundScreen: React.FC = () => {
const navigation = useNavigation<StackNavigationProp<any>>();
const theme = useTheme();
return (
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
{/* ... other component sections ... */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>New Component Variants</Text>
<NewComponent variant="primary" label="Primary Action" />
<NewComponent variant="secondary" label="Secondary Action" disabled />
{/* Add more variants as needed */}
</View>
{/* ... navigation buttons ... */}
</ScrollView>
);
};
Location: src/contexts/
Approach:
We utilize React's built-in Context API along with useReducer
and useContext
hooks for managing global application state. This provides a lightweight, type-safe solution without external dependencies.
Global State Structure (AppContext.tsx
):
The main context combines several domain-specific states:
- Authentication State (
AuthState
): Manages user login status, user details (user
), JWT token (token
), and authentication errors (error
). Handled byAuthReducer
. Located insrc/contexts/AuthContext.tsx
(or integrated withinAppContext.tsx
). - Theme State (
ThemeState
): Manages UI theme preferences (e.g.,mode: 'light' | 'dark'
, potentially custom colors). Handled byThemeReducer
. Integrated withinAppContext.tsx
or a separateThemeContext.tsx
. - Location State (
LocationState
): Stores user's geographic information (latitude
,longitude
,address
). Handled byLocationReducer
. Integrated withinAppContext.tsx
or a separateLocationContext.tsx
. - Cart State (
CartContext.tsx
): Manages the user's shopping cart (items
). Uses its own provider (CartProvider
) and hook (useCart
).
Using the Context:
// Example: Accessing Auth state in a component
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useAppContext } from '../contexts/AppContext'; // Assuming combined context
const ProfileScreen: React.FC = () => {
const { state, dispatch } = useAppContext();
const handleLogout = () => {
dispatch({ type: 'LOGOUT' });
// Potentially call an AuthService logout function here too
};
return (
<View>
{state.auth.isAuthenticated ? (
<>
<Text>Welcome, {state.auth.user?.username || 'User'}</Text>
<Button title="Logout" onPress={handleLogout} />
</>
) : (
<Text>Please log in.</Text>
)}
</View>
);
};
export default ProfileScreen;
Key Principles:
- Immutability: Reducers must return new state objects instead of mutating the existing state.
- Type Safety: TypeScript interfaces (
AppState
,AuthAction
, etc.) define the structure of state and actions. - Separation of Concerns: Contexts are often split by domain (Auth, Theme, Cart) to keep management focused.
Library: React Native Paper
Custom Theme: src/theme/SugarTheme.ts
Philosophy: We exclusively use React Native Paper components for all UI elements to ensure a consistent, modern look and feel based on Material Design 3. This approach promotes:
- Consistency: Uniform styling across the app.
- Maintainability: Centralized theme management.
- Accessibility: Built-in accessibility features.
- Responsiveness: Components adapt to different screen sizes.
Key Guidelines:
- Always Import
useTheme
: Access theme colors, fonts, and spacing via theuseTheme
hook fromreact-native-paper
. - Use Paper Components: Replace standard React Native components (
View
,Text
,Button
,TextInput
) with their Paper counterparts (Surface
,Text
,Button
,TextInput
). - Leverage Theme: Apply styling primarily through theme properties (e.g.,
color={theme.colors.primary}
,style={theme.fonts.headlineSmall}
). Avoid inline styles for theme-related properties. - Custom Theme (
SugarTheme.ts
): Defines the application's specific color palette, fonts, and component variants based on the default Material Design 3 theme.
Example Usage:
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Text, Button, useTheme } from 'react-native-paper';
const MyComponent: React.FC = () => {
const theme = useTheme(); // Access the theme
return (
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
<Text variant="headlineMedium" style={{ color: theme.colors.primary }}>
Welcome!
</Text>
<Button
mode="contained" // Paper button style
onPress={() => console.log('Pressed')}
style={{ marginTop: 16 }}
// Button automatically uses theme's primary color for contained mode
>
Get Started
</Button>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
alignItems: 'center',
justifyContent: 'center',
},
});
export default MyComponent;
- Use feature branches based off the main development branch (e.g.,
main
ordevelop
). - Branch naming convention:
feature/your-feature-name
orfix/bug-description
.git checkout main git pull git checkout -b feature/new-checkout-flow
- Goal: Implement unit and integration tests using Jest and React Native Testing Library.
- When implemented, run tests via:
npm test
- Develop the feature/fix on your branch.
- Commit changes with clear, concise messages.
- Push your branch to the remote repository:
git push origin feature/your-feature-name
- Create a Pull Request (PR) on GitHub against the main development branch.
- Provide a detailed description of changes in the PR.
- Request code reviews from team members.
- Once the PR is approved and passes any CI checks, merge it into the main development branch.
- Tools: ESLint and Prettier are configured for code quality and consistent style.
- Check: Run
npm run lint
to identify linting errors. - Format: Run
npm run format
to automatically format code according to Prettier rules. - Recommendation: Configure your code editor to format on save using ESLint and Prettier plugins.
- Platform: GitHub Actions.
- Workflow: A basic CI/CD pipeline is likely configured (check
.github/workflows
) for automated testing (when tests exist) and potentially deployment on pushes/merges to the main branch. - Deployment: Currently targets Heroku for the backend. Frontend deployment might involve Expo Application Services (EAS) builds later.
- Current State: Basic logging is implemented.
- Future Considerations:
- Integrate performance monitoring tools (e.g., Sentry, Datadog).
- Optimize database queries using indexing (MongoDB Atlas provides tools for this).
- Implement caching strategies (backend API responses, frontend data).
- Input Validation: Validate and sanitize all user inputs on both frontend (Yup) and backend.
- Authentication: Use JWT securely (HTTPS, httpOnly cookies if applicable for web, secure storage in mobile).
- Authorization: Ensure backend endpoints verify user permissions before performing actions (e.g., checking if the user owns the listing they are trying to edit/delete).
- Secrets Management: Never commit sensitive information (API keys, JWT secrets, database credentials) directly to the codebase. Use
.env
files and ensure they are in.gitignore
. - Dependencies: Regularly update dependencies to patch security vulnerabilities.
- Dependency Issues: If
npm install
fails or causes unexpected errors, try deletingnode_modules
andpackage-lock.json
(oryarn.lock
) and runningnpm install --legacy-peer-deps
again. - API Connection Errors (Expo Go): If the app can't connect to the local backend:
- Ensure the backend server is running.
- Verify the
API_BASE_URL
in the frontend's.env
file is correct. If using a physical device, it must be your computer's local network IP (e.g.,http://192.168.1.10:3000
), notlocalhost
. - Check firewall settings aren't blocking the connection on the required port (3000).
- Database Connection Issues: Ensure your
MONGODB_URI
in the backend.env
file is correct and that your current IP address is whitelisted in MongoDB Atlas network access settings. - Image Upload/Display Issues: Verify backend file permissions and that the
${API_BASE_URL}
used for constructing image URLs in the frontend is correct.
For any questions or issues, please contact the project maintainer at [rodi1364@colorado.edu].