# Project export: TrashToTreasure - Sustainable Showcase

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: Cal Hacks 11.0
- Tagline: Improvements in Pepsi's Global Value chain end with consumers but so does waste. Imagine if customers were inspired by AI to make beautiful projects w/ trash, promoting brand value and sustainability.
- Devpost: https://devpost.com/software/trashtotreasure-sustainable-showcase
- GitHub: https://github.com/garvcodes/sustainableshowcase
- Result: winner (SCET/Pepsico: PEP+ Sustainability Prize)
- Team: 2 GitHub contributor(s) — garvcodes (9 commits), P11co (4 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# guineatwin


## Detected evidence (automated analysis)

Indexed codebase: 11 recognized source files, 21 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- JavaScript (language) — detected in the code
- MongoDB (technology) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (21 of 21)

```
.DS_Store
README.md
sustainableshowcase-backend/.env
sustainableshowcase-backend/package.json
sustainableshowcase-backend/server.js
sustainableshowcase-backend/uploads/pepsico.txt
sustainableshowcase-frontend/.eslintrc.json
sustainableshowcase-frontend/.gitignore
sustainableshowcase-frontend/next.config.mjs
sustainableshowcase-frontend/package.json
sustainableshowcase-frontend/postcss.config.mjs
sustainableshowcase-frontend/README.md
sustainableshowcase-frontend/src/app/getstarted/page.tsx
sustainableshowcase-frontend/src/app/globals.css
sustainableshowcase-frontend/src/app/layout.tsx
sustainableshowcase-frontend/src/app/leaderboard/page.tsx
sustainableshowcase-frontend/src/app/page.tsx
sustainableshowcase-frontend/src/components/BubblyButton.tsx
sustainableshowcase-frontend/src/components/FileInputButton.tsx
sustainableshowcase-frontend/tailwind.config.ts
sustainableshowcase-frontend/tsconfig.json
```

### Dependencies

- sustainableshowcase-backend/package.json: @google/generative-ai@^0.21.0, cors@^2.8.5, dotenv@^10.0.0, express@^4.17.1, mongoose@^8.7.2, multer@^1.4.3
- sustainableshowcase-frontend/package.json: @types/node@^20, @types/react@^18, @types/react-dom@^18, dotenv@^16.4.5, eslint@^8, eslint-config-next@14.2.15, express@^4.21.1, mongodb@^6.9.0, mongoose@^8.7.2, multer@^1.4.5-lts.1, next@14.2.15, postcss@^8, react@^18, react-bubbly-effect-button@^1.0.1, react-dom@^18, react-markdown@^9.0.1, tailwindcss@^3.4.1, typescript@^5

### Recent commits (newest first)

- Replaced placeholder prompt and added pepsi products list
- Markdown support for LM output
- Addressed CORS error (ai response not displaying)
- Added fancier bubbling buttons (fizzy)
- images working
- leaderboard sorta working
- onto the leaderboard
- fix gemini
- just need to fix api key
- done with mongo image upload
- backend
- rename
- landing page
- first
- first commit

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

### sustainableshowcase-backend/package.json

```
{
  "name": "sustainableshowcase-backend",
  "version": "1.0.0",
  "description": "Backend API for handling image uploads and leaderboard ranks",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@google/generative-ai": "^0.21.0",
    "cors": "^2.8.5",
    "dotenv": "^10.0.0",
    "express": "^4.17.1",
    "mongoose": "^8.7.2",
    "multer": "^1.4.3"
  }
}

```

### sustainableshowcase-frontend/package.json

```
{
  "name": "guineatwin",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.21.1",
    "mongodb": "^6.9.0",
    "mongoose": "^8.7.2",
    "multer": "^1.4.5-lts.1",
    "next": "14.2.15",
    "react": "^18",
    "react-bubbly-effect-button": "^1.0.1",
    "react-dom": "^18",
    "react-markdown": "^9.0.1"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### sustainableshowcase-backend/server.js

```javascript
const express = require("express");
const multer = require("multer");
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const fs = require("fs");
const path = require("path");
const cors = require('cors');
const app = express();
const { GoogleAIFileManager } = require("@google/generative-ai/server");
const { GoogleGenerativeAI } = require("@google/generative-ai");

dotenv.config();

// Enable CORS for all routes
app.use(cors());

// MongoDB connection
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log("Connected to MongoDB"))
  .catch((error) => console.log("MongoDB connection error:", error));




// Multer setup for file storage
const storage = multer.memoryStorage();
const upload = multer({ storage });


// MongoDB schema for User and image
const userSchema = new mongoose.Schema({
  email: String,
  image: Buffer,
  contentType: String,
  geminiUri: String, // New field to store Gemini URI
});

const User = mongoose.model("User", userSchema);

// Google AI setup
const fileManager = new GoogleAIFileManager(process.env.API_KEY);
const genAI = new GoogleGenerativeAI(process.env.API_KEY);

// Route to handle file upload
app.post("/upload", upload.single("image"), async (req, res) => {
  try {

    const { email } = req.body;
    const image = req.file;

    if (!email || !image) {
      return res.status(400).send("Email and image are required");
    }

    // Create a new user with the uploaded image
    const newUser = new User({
      email,
      image: image.buffer,
      contentType: image.mimetype,
    });

    await newUser.save();

    // Create a temporary file path
    const tempFilePath = path.join(__dirname, "uploads", `${Date.now()}-${image.originalname}`);
    
    // Write the image buffer to the temporary file
    fs.writeFileSync(tempFilePath, image.buffer);

    // Upload to Gemini
    const uploadResult = await fileManager.uploadFile(tempFilePath, {
      mimeType: image.mimetype,
      displayName: `${email}-image`,
    });

    console.log(`Uploaded file ${uploadResult.file.displayName} as: ${uploadResult.file.uri} `);

    // Store Gemini URI in the user record
    newUser.geminiUri = uploadResult.file.uri;
    await newUser.save();

    // read file
    const pepsico_product_list = fs.readFileSync('uploads/pepsico.txt', 'utf-8')

    // Generate content using the uploaded image
    const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
    const result = await model.generateContent([
      'Here is a full list of Pepsico products:\n ${pepsico_product_list}. Is the object in the image a Pepsico product? If not, reply \'This is not a Pepsico product, please take a photo of a Pepsico product!\' If it is a pepsico product, describe how many and what brands they are. Then, give me an easy-to-follow instruction for upcycling projects with this object. the project has to be creative, environment-friendly, and fun to make. Give me only one but a different one each time',
      {
        fileData: {
          fileUri: uploadResult.file.uri,
          mimeType: uploadResult.file.mimeType,
        },
      },
    ]);

    console.log(JSON.stringify(result, null, 2))

    // Clean up the temporary file after upload
    fs.unlinkSync(tempFilePath); // Remove the temporary file

    res.status(200).json({
      message: "Image uploaded successfully!",
      geminiResponse: result.response.text(),
      geminiUri: uploadResult.file.uri,
    });
  } catch (error) {
    console.error("Error during image upload or generation:", error);
    res.status(500).send("Error uploading image");
  }
});


app.post("/upload-creation", upload.single("image"), async (req, res) => {
  try {
    const { email } = req.body;
    const image = req.file;

    if (!email || !image) {
      return res.status(400).send("Email and image are required");
    }

    // Create a new user with the uploaded image
    const newUser = new User({
      email,
      image: image.buffer, // Store the image buffer
      contentType: image.mimetype, // Store the MIME type
    });

    await newUser.save(); // Save the user with the image

    return res.status(201).send("User and image saved successfully");
  } catch (error) {
    console.error("Error saving user or image", error);
    return res.status(500).send("Internal server error");
  }
});


app.get("/leaderboard", async (req, res) => {
  try {
    // Fetch all users with their images sorted by the most recent upload
    const users = await User.aggregate([
      {
        $project: {
          email: 1,
          image: 1,
          contentType: 1,
          geminiUri: 1,
        },
      },
    ]);

    // Convert binary images to base64 and include them in the response
    const usersWithImages = users.map((user) => ({
      email: user.email,
      contentType: user.contentType,
      image: user.image ? user.image.toString('base64') : null, // Convert binary image to base64
      geminiUri: user.geminiUri,
    }));

    res.json(usersWithImages);
  } catch (error) {
    console.error("Error fetching leaderboard", error);
    res.status(500).send("Error fetching leaderboard");
  }
});




// Start server
const port = process.env.PORT || 5050;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

```

### sustainableshowcase-frontend/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### sustainableshowcase-frontend/src/app/page.tsx

```typescript
'use client';
import Image from "next/image";
import Link from 'next/link';
import BubblyButton from '../components/BubblyButton';


export default function Home() {
  const handleGetStarted = () => {
    // Navigate to the getstarted page
    window.location.href = '/getstarted';
  };

  return (
    <div className="bg-gradient-to-br from-green-700 to-black"> 
      <div className="text-gray-300 container mx-auto p-8 overflow-hidden md:rounded-lg md:p-10 lg:p-12">
        <div className="flex justify-between">
          <h1 className="font-serif text-3xl font-medium">PepsiCo Sustainability Challenge</h1>
        </div>

        <div className="h-32 md:h-40"></div>

        <div className="flex items-center bg-transparent">
          <p className="font-sans text-4xl font-bold text-gray-200 max-w-5xl lg:text-7xl lg:pr-24 md:text-6xl">
            Show us what you can create using America's favorite drink!
          </p>
          <Image
            src="/pepsi-png-42984.png"
            width={400} // Adjust width as needed
            height={100} // Adjust height as needed
            alt="Pepsi planter"
            className="rounded-lg ml-4" // Add margin left for spacing
          />
        </div>

        <div className="h-10"></div>
        <p className="max-w-2xl font-serif text-xl text-gray-400 md:text-2xl">
          PepsiCo has been fueling American creativity for years, we're asking you to give us a little of that 
          creativity in the form of Sustainable fun!
        </p>

        <div className="h-32 md:h-40"></div>

        <div className="grid gap-8 md:grid-cols-2">
          <div className="flex flex-col justify-center">
            <p className="self-start inline font-sans text-xl font-medium text-transparent bg-clip-text bg-gradient-to-br from-green-400 to-green-600">
              How it Works
            </p>
            <h2 className="text-4xl font-bold">It starts with a picture...</h2>
            <div className="h-6"></div>
            <p className="font-serif text-xl text-gray-400 md:pr-10">
              After uploading a picture of your PepsiCo product, it will undergo analysis using the Google Gemini API. 
              Gemini will then issue you a challenge... 
              A challenge to make something new out of something old! Using the prompt, Gemini will create its own rendition as well, which 
              you can use as inspiration in your creation!
            </p>
            <div className="h-8"></div>
            <div className="grid grid-cols-2 gap-4 pt-8 border-t border-green-700">

              {/* Section for Gemini picture examples */}
              {/* Picture 1 */}
              <div>
                <p className="font-semibold text-gray-400">Gemini's Vision</p>
                <div className="h-4">
                  <Image
                    src="/pepsiplanter.png"
                    width={500}
                    height={500}
                    alt="Picture of the author"
                    className="rounded-xl mt-8"
                  />
                  <h1 className="text-xl mt-4">Gemini thought of a sustainable planter solution!</h1>
                </div>
                <p className="font-serif text-gray-400"></p>
              </div>

              {/* Picture 2 */}
              <div>
                <p className="font-semibold text-gray-400">Your Creation!</p>
                <div className="h-4"></div>
                <p className="font-serif text-gray-400"></p>
              </div>

            </div>
          </div>
          <div>
            <div className="-mr-24 rounded-lg md:rounded-l-full bg-gradient-to-br from-red-400 to-blue-700 h-96"></div>
          </div>
        </div>

        <div className="mt-20">
            <BubblyButton 
              text="Get Started" 
              onClick={handleGetStarted} 
              color="#ffffff" 
              bgColor="#22c55e"  // This is a green color similar to bg-green-500
            />
          </div>

        <div className="flex justify-center pt-12 pb-8 text-gray-400">
          © 2024 All rights reserved
        </div>
      </div>
    </div>
  );
}

```

### sustainableshowcase-frontend/src/app/leaderboard/page.tsx

```typescript
'use client';
import { useEffect, useState } from "react";
import Image from "next/image";

export default function Leaderboard() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchLeaderboard = async () => {
      try {
        const response = await fetch("http://localhost:5050/leaderboard");
        const data = await response.json();
        setUsers(data);
      } catch (error) {
        console.error("Error fetching leaderboard", error);
      }
    };

    fetchLeaderboard();
  }, []);

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
  {users && users.length > 0 ? (
    users.map((user) => (
      <div key={user._id} className="bg-green-700 p-4 rounded-lg shadow-lg">
        <h2 className="text-2xl font-bold mb-4">{user.email}</h2>
        <div className="flex gap-4">
          {user.image && (
            <Image
              src={`data:${user.contentType};base64,${user.image}`} // Use base64-encoded image
              alt={`User ${user.email}'s image`}
              width={150}
              height={150}
            />
          )}
        </div>
      </div>
    ))
  ) : (
    <p className="text-white text-2xl">No users found.</p>
  )}
</div>
  );
}

```

### sustainableshowcase-frontend/src/app/getstarted/page.tsx

```typescript
'use client';
import Image from "next/image";
import { useState } from "react";
import BubblyButton from '../../components/BubblyButton';
import FileInputButton from '../../components/FileInputButton';
import ReactMarkdown from 'react-markdown';

export default function Page() {
  const [selectedImage, setSelectedImage] = useState(null);
  const [email, setEmail] = useState("garv5114@gmail.com"); // State to hold the email
  const [aiChallenge, setAiChallenge] = useState(""); // State to hold the AI challenge text
  const [isLoading, setIsLoading] = useState(false); // State to show loading while waiting for response
  const [userCreation, setUserCreation] = useState(null); // State to hold user's creation

  const handleImageUpload = async (event) => {
    const file = event.target.files[0];
    if (file) {
      setSelectedImage(URL.createObjectURL(file));
      setIsLoading(true); // Set loading to true while waiting for the AI challenge

      // Create a form data object to send the file and email
      const formData = new FormData();
      formData.append("email", email); // Use the email from the state
      formData.append("image", file);

      try {
        const response = await fetch("http://localhost:5050/upload", {
          method: "POST",
          body: formData,
        });

        if (response.ok) {
          const responseData = await response.json();
          setAiChallenge(responseData.geminiResponse); // Set the AI challenge text
          console.log("Received AI response:", responseData.geminiResponse);
          setIsLoading(false); // Set loading to false after receiving the response
        } else {
          console.error("Image upload failed");
          setIsLoading(false); // Stop loading in case of error
        }
      } catch (error) {
        console.error("Error uploading image", error);
        setIsLoading(false); // Stop loading in case of error
      }
    }
  };

  const handleCreationUpload = async (event) => {
    const file = event.target.files[0];
    if (file) {
      setUserCreation(URL.createObjectURL(file)); // Set the user's creation for preview

      // Create a form data object to send the user's creation and email
      const formData = new FormData();
      formData.append("email", email); // Use the email from the state
      formData.append("image", file); // Add the creation file

      try {
        const response = await fetch("http://localhost:5050/upload-creation", {
          method: "POST",
          body: formData,
        });

        if (response.ok) {
          console.log("Creation uploaded successfully");
        } else {
          console.error("Creation upload failed");
        }
      } catch (error) {
        console.error("Error uploading creation", error);
      }
    }
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-green-800 to-green-500">
      <div className="text-white container mx-auto p-8 overflow-hidden md:rounded-lg md:p-10 lg:p-12">
        <div className="flex justify-between">
          <h1 className="font-serif text-4xl font-medium">PepsiCo Sustainable Showcase</h1>
        </div>

        <div className="h-10"></div>
        <h2 className="font-sans text-2xl font-bold text-gray-200">Join the Movement</h2>
        <p className="font-serif text-4xl text-white font-semibold italic mt-4">
          Follow the steps below to upload your image and participate in the challenge:
        </p>

        <ol className="list-decimal list-inside font-serif text-white text-2xl mt-6 mb-6">
          <li>Enter your email, this will be used to display your creation on our global leaderboard!</li>
        </ol>

        <div className="h-10"></div>

        <div className="flex flex-col items-center">
          <input
            type="email"
            placeholder="Enter your email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className="text-black mb-4 p-2 border rounded"
            required
          />

          <div className="list-decimal list-inside font-serif text-white text-2xl mt-6 text-left">
            2. Upload an image of your PepsiCo product and let AI give you a challenge!
          </div>

          <FileInputButton
            onChange={handleImageUpload}
            accept="image/*"
            required
            text="Upload Image"
            color="#fff"
            bgColor="#4CAF50"
          />
          {selectedImage && (
            <div className="mt-6">
              <Image src={selectedImage} alt="Uploaded Image" width={500} height={500} />
            </div>
          )}
        </div>

        <div className="list-decimal list-inside font-serif text-white text-2xl mt-6 text-left">
          3. Your AI Challenge will appear below, read it, dream it, and create it! Don't be afraid to let your personal creativity show!
        </div>

        {/* Display loading state or AI challenge */}
        <div className="text-white text-xl mt-4">
          {isLoading ? (
            <p>Loading AI Challenge...</p>
          ) : (
            aiChallenge && (
              <div className="bg-gray-800 p-4 rounded-md shadow-md mt-4 text-white">
                <ReactMarkdown>{aiChallenge}</ReactMarkdown>
              </div>
            )
          )}
        </div>

        {aiChallenge && (
          <>
            <div className="list-decimal list-inside font-serif text-white text-2xl mt-6 text-left">
              4. Now that you have your challenge... it's all you! Use Gemini as inspiration and create something awesome.
              Afterwards, upload it for the world to see! Gemini will assign your submission a score based on its rubric, and the world can show love as well!
            </div>

            <div className="flex flex-col items-center">
              <p className="text-white text-xl mb-4">Upload your creation:</p>
              <FileInputButton
                onChange={handleCreationUpload}
          
[truncated — 559 more characters]
```

### sustainableshowcase-frontend/tailwind.config.ts

```typescript
import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        background: "var(--background)",
        foreground: "var(--foreground)",
      },
    },
  },
  plugins: [],
};
export default config;

```

### sustainableshowcase-frontend/src/app/globals.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  color: var(--foreground);
  background: var(--background);
  font-family: Arial, Helvetica, sans-serif;
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }
}

```

### sustainableshowcase-frontend/src/components/BubblyButton.tsx

```typescript
import React from 'react';
import ReactBubblyEffectButton from "react-bubbly-effect-button";

interface BubblyButtonProps {
  text: string;
  onClick: () => void;
  color?: string;
  bgColor?: string;
}

const BubblyButton: React.FC<BubblyButtonProps> = ({ 
  text, 
  onClick, 
  color = '#fff', 
  bgColor = '#ff0081' 
}) => {
  return (
    <ReactBubblyEffectButton 
      text={text} 
      color={color} 
      bgColor={bgColor} 
      onClick={onClick} 
    />
  );
};

export default BubblyButton;
```

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