# Project export: StudySlug

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: CruzHacks 2025
- Tagline: StudySlug is for UCSC students to find study partners. A live campus map shows availability, making meetups easy. With a Gemini AI Tutor, users can get help and peer support in one space.
- Devpost: https://devpost.com/software/studyslug
- GitHub: https://github.com/kritibhargava1/StudySlug
- Video: https://www.youtube.com/embed/KOPQEPNatqg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — lakshana321 (12 commits)

## Devpost submission (written by the team)

### Inspiration

As students at UCSC, we often found it difficult to coordinate study groups, meet classmates in the same classes, or get timely academic help. We wanted to create a centralized tool that encourages collaboration, makes scheduling easier, and supports academic growth, all tailored to the student experience.

### What it does

StudySlug helps UCSC students connect with peers in their classes, form study groups, and meet in real-time based on availability. Students can: Check in to campus locations to show when and where they're available. View a live map showing others who are free to study nearby. Create a study profile with classes and weekly availability. Chat with the Gemini AI Tutor for instant academic assistance.

### How we built it

We used: React for the frontend interface Express + Node.js for the backend API MongoDB for user data and check-in storage Leaflet to display a live, interactive campus map Firebase Auth for secure login Gemini API for AI-powered tutoring and Q&A

### Challenges we ran into

Implementing a smooth and responsive AI chat experience with Gemini and integrating real time map markers with user check-in data.

### Accomplishments we're proud of

Seamless signup and matching flow based on class and schedule, integration with Gemini AI for tutoring support, clean and accessible UI tailored to UCSC students.

### What we learned

How to work collaboratively across frontend, backend, and AI components.

### What's next

Add group chat features and calendar syncing, enable push notifications when nearby students check in, launch a mobile-friendly version for faster on-the-go meetups, open it up for students at other UC campuses.

## README (from the GitHub repository)

# 🐌 StudySlug

A web app for UCSC students to connect with study buddies, join study groups, use AI tutors, and check real-time study locations on a live campus map.

---

## 🚀 Setup Instructions

### 1. Clone the Repository

```bash
git clone https://github.com/kritibhargava1/StudySlug.git
cd StudySlug
```

### 2. Install Dependencies

Make sure you're in the root directory (where `package.json` is located), then run:

```bash
npm install
```

This will install both backend (server) and frontend (React) dependencies.

---

## 🔧 Running the App

### Option 1: Development mode (recommended)

Runs both the server and client at the same time:

```bash
npm run dev
```

- Server: runs with `nodemon` on port **9000**
- Client (React app): runs on **http://localhost:3000**

### Option 2: Run only the backend

```bash
node server.js
```

This starts your backend API on:

```
http://localhost:9000
```

Make sure MongoDB is running locally or update your `.env` file to connect to an external DB.

---

## 🌐 Access the App

Once the server and client are running, open:

```
http://localhost:3000
```

Use the login/signup page to get started.

---

## 📁 Project Structure

```
StudySlug/
├── server.js               # Backend entry point
├── client/ or src/         # React frontend
│   ├── components/
│   ├── pages/
│   ├── styles/
│   └── App.js
├── .env                    # Environment variables
├── package.json            # Project config & dependencies
└── README.md               # Setup instructions
```

---

## 🛠️ Environment Variables (`.env`)

Create a `.env` file in the root directory with:

```env
MONGO_URI=your_mongodb_connection_string
PORT=9000
```

Make sure to restart your server after creating this file.

---

## ✅ Technologies Used

- React
- Express
- MongoDB + Mongoose
- Firebase Auth
- Leaflet.js
- Google Generative AI API
- Node.js

---

Happy Studying 🧠✨

Made with 💛 by the StudySlug Team



## Detected evidence (automated analysis)

Indexed codebase: 39 recognized source files, 70 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- Firebase (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- MongoDB (technology) — detected in the code
- React (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (43 of 43)

```
.gitignore
eslint.config.js
index.html
package.json
README.md
server/.env
server/models/Checkin.js
server/package.json
server/routes/checkin.js
server/server.js
src/App.css
src/App.jsx
src/components/ChatPopup.jsx
src/components/Layout.jsx
src/components/Navbar.jsx
src/components/ProfileDropdown.jsx
src/firebase.js
src/index.css
src/main.jsx
src/MainLayout.jsx
src/pages/CheckInPage.jsx
src/pages/Contact.jsx
src/pages/Home.jsx
src/pages/HowItWorks.jsx
src/pages/Login.jsx
src/pages/MatchesPage.jsx
src/pages/MeetNow.jsx
src/pages/SignUp.jsx
src/pages/StudyGroups.jsx
src/pages/Tutors.jsx
src/styles/CheckInPage.css
src/styles/Contact.css
src/styles/Home.css
src/styles/HowItWorks.css
src/styles/Login.css
src/styles/MatchesPage.css
src/styles/MeetNow.css
src/styles/Navbar.css
src/styles/ProfileDropdown.css
src/styles/SignUp.css
src/styles/StudyGroups.css
src/styles/Tutors.css
vite.config.js
```

### Dependencies

- package.json: @eslint/js@^9.21.0, @types/react@^19.0.10, @types/react-dom@^19.0.4, @vitejs/plugin-react@^4.3.4, axios@^1.8.4, cors@^2.8.5, dotenv@^16.5.0, eslint@^9.21.0, eslint-plugin-react-hooks@^5.1.0, eslint-plugin-react-refresh@^0.4.19, express@^5.1.0, firebase@^11.6.0, globals@^15.15.0, leaflet@^1.9.4, mongodb@^6.15.0, mongoose@^8.13.2, nodemon@^3.1.9, react@^19.0.0, react-dom@^19.0.0, react-leaflet@^5.0.0, react-router-dom@^7.5.0, vite@^6.2.0
- server/package.json: @google/generative-ai@^0.24.0, axios@^1.6.0, concurrently@^8.2.0, cors@^2.8.5, dotenv@^16.5.0, express@^5.1.0, firebase@^10.7.0, leaflet@^1.9.4, mongoose@^8.13.2, nodemon@^3.0.1, react@^18.2.0, react-dom@^18.2.0, react-leaflet@^4.2.1, react-router-dom@^6.18.0, react-scripts@5.0.1

### Recent commits (newest first)

- readme and requirements to run project
- Update navbar layout and signup styles
- Merge branch 'main' of https://github.com/kritibhargava1/StudySlug
- Working on MeetNow page and Navbar updates
- changes to signup page
- changes to navbar
- edits
- changed homepage
- changes to signup(profile)  page
- changes to contact page
- navbar changes
- ui/ux for homepage and matchespage
- changes to checkinpage and tutors
- changes to checkinpage
- login page design edits/logo
- changes to tutors page
- added profile dropdown and removed navagation bar from login
- fixed map location
- added location on map
- allow dev port

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

### package.json

```
{
  "name": "studyslug",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.8.4",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0",
    "express": "^5.1.0",
    "firebase": "^11.6.0",
    "leaflet": "^1.9.4",
    "mongodb": "^6.15.0",
    "mongoose": "^8.13.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-leaflet": "^5.0.0",
    "react-router-dom": "^7.5.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.21.0",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.21.0",
    "eslint-plugin-react-hooks": "^5.1.0",
    "eslint-plugin-react-refresh": "^0.4.19",
    "globals": "^15.15.0",
    "nodemon": "^3.1.9",
    "vite": "^6.2.0"
  },
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
  "main": "eslint.config.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kritibhargava1/StudySlug.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/kritibhargava1/StudySlug/issues"
  },
  "homepage": "https://github.com/kritibhargava1/StudySlug#readme"
}

```

### server/package.json

```
{
  "name": "studyslug",
  "version": "1.0.0",
  "description": "StudySlug: UCSC study buddy and AI tutor platform",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js",
    "server": "nodemon server.js",
    "client": "react-scripts start",
    "dev": "concurrently \"npm run server\" \"npm run client\"",
    "build": "react-scripts build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@google/generative-ai": "^0.24.0",
    "axios": "^1.6.0",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0",
    "express": "^5.1.0",
    "firebase": "^10.7.0",
    "leaflet": "^1.9.4",
    "mongoose": "^8.13.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-leaflet": "^4.2.1",
    "react-router-dom": "^6.18.0"
  },
  "devDependencies": {
    "concurrently": "^8.2.0",
    "nodemon": "^3.0.1",
    "react-scripts": "5.0.1"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### src/App.jsx

```javascript
import { BrowserRouter as Router } from 'react-router-dom';
import MainLayout from './MainLayout';

function App() {
  return (
    <Router>
      <MainLayout />
    </Router>
  );
}

export default App;

```

### src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### server/server.js

```javascript
import express from 'express';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import cors from 'cors';



// Load environment variables from .env
dotenv.config();

const app = express();
const PORT = process.env.PORT || 4000;

// Middleware
const allowedOrigins = ['http://localhost:5173', 'http://localhost:5174'];

app.use(cors({
  origin: function (origin, callback) {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));
app.use(express.json());

//After app is initialized, add your routes
import checkinRoutes from './routes/checkin.js';
app.use('/api/checkin', checkinRoutes);

// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI)
  .then(() => console.log("✅ Connected to MongoDB"))
  .catch((err) => console.error("❌ MongoDB connection error:", err));

// Schema and Model
const userProfileSchema = new mongoose.Schema({
  uid: { type: String, required: true },
  email: { type: String, required: true },
  classes: [
    {
      course: String,
      year: String,
      quarter: String,
    },
  ],
  availability: {
    type: Map,
    of: [
      {
        from: String,
        to: String,
      },
    ],
  },
});

const UserProfile = mongoose.model('UserProfile', userProfileSchema);

// Route to save user profile
app.post('/api/signup', async (req, res) => {
  const { uid, email, classes, availability } = req.body;

  if (!uid || !email) {
    return res.status(400).json({ error: 'Missing UID or email' });
  }

  try {
    const result = await UserProfile.create({ uid, email, classes, availability });
    res.status(200).json({ message: 'Signup stored!', user: result });
  } catch (err) {
    console.error('MongoDB insert error:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Update existing profile
app.put('/api/update-profile/:uid', async (req, res) => {
  const { uid } = req.params;
  const { classes, availability } = req.body;

  if (!uid) {
    return res.status(400).json({ error: 'Missing UID' });
  }

  try {
    const updated = await UserProfile.findOneAndUpdate(
      { uid },
      { $set: { classes, availability } },
      { new: true }
    );

    if (!updated) {
      return res.status(404).json({ error: 'Profile not found' });
    }

    res.status(200).json({ message: 'Profile updated!', user: updated });
  } catch (err) {
    console.error('Update error:', err);
    res.status(500).json({ error: 'Internal server error' });
  }
});

// ✅ Route to fetch all students
app.get('/api/all-students', async (req, res) => {
  try {
    const students = await UserProfile.find({});
    res.status(200).json(students);
  } catch (err) {
    console.error('Error fetching students:', err);
    res.status(500).json({ error: 'Failed to fetch student data' });
  }
});

// Check if user already has a profile
app.get('/api/profile-exists/:uid', async (req, res) => {
  const { uid } = req.params;
  try {
    const user = await UserProfile.findOne({ uid });
    res.json({ exists: !!user });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Failed to check user profile' });
  }
});

/* ============================
   Gemini SDK Configuration
   ============================ */

// Import and initialize the Gemini SDK
import { GoogleGenerativeAI } from '@google/generative-ai';

// Create an instance of the Gemini client with your API key
const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);

// Get the generative model you wish to use (e.g., "gemini-1.5-flash")
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });

/* ============================
   Gemini Chat Endpoint
   ============================ */

/**
 * POST /api/gemini-chat
 * This endpoint receives JSON with:
 *   - chat: string (the user’s message)
 *   - history: Array (optional conversation history)
 *
 * It sends the user's message to the Gemini API and returns the generated response.
 */
app.post('/api/gemini-chat', async (req, res) => {
  try {
    // Extract the user's message and history.
    // We expect the frontend to send the key "chat" (adjust if necessary).
    const chatHistory = req.body.history || [];
    const msg = req.body.chat || req.body.message;

    // Start a new chat session with any conversation history.
    const chat = model.startChat({ history: chatHistory });
    
    // Send the user's message to Gemini.
    const result = await chat.sendMessage(msg);
    
    // Await and extract the complete response.
    const response = await result.response;
    const text = response.text();
    
    // Return the generated response as JSON.
    res.json({ text: text });
  } catch (error) {
    console.error("Error processing Gemini chat request:", error);
    res.status(500).json({ text: "An error occurred while processing your message." });
  }
});

// Start the server
app.listen(PORT, () => {
  console.log(`🚀 Server running at http://localhost:${PORT}`);
});

```

### vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
]

```

### src/App.css

```css
/* 🔧 Global Reset */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  overflow-x: hidden; /* ✅ Prevent horizontal scroll */
  font-family: sans-serif;
}

/* Your existing styles below */
#root {
  width: 100vw;
  min-height: 100vh;
  margin: 0;
  padding: 0;
  text-align: center;
  overflow-x: hidden;
}

.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
  filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: no-preference) {
  a:nth-of-type(2) .logo {
    animation: logo-spin infinite 20s linear;
  }
}

.card {
  padding: 2em;
}

.read-the-docs {
  color: #888;
}

```

### src/firebase.js

```javascript
import { initializeApp } from "firebase/app";
import {
  getAuth,
  GoogleAuthProvider,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signInWithPopup
} from "firebase/auth";
import {
  getFirestore,
  setDoc,
  doc
} from "firebase/firestore";

const firebaseConfig = {
  apiKey: "AIzaSyDA-QJ-Pwa6zAXz24IwxOUGR4XUEfOkOCc",
  authDomain: "login-form-b9a7c.firebaseapp.com",
  projectId: "login-form-b9a7c",
  storageBucket: "login-form-b9a7c.appspot.com",
  messagingSenderId: "60291513749",
  appId: "1:60291513749:web:8005897cfd66a19e45ca9c",
  measurementId: "G-LELMF53W43"
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const provider = new GoogleAuthProvider();

export {
  auth,
  db,
  provider,
  signInWithPopup,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  setDoc,
  doc
};

```

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