# Project export: Basket

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: Fresh Groceries, Delivered Together
- Devpost: https://devpost.com/software/basket-8iq7m9
- GitHub: https://github.com/will-hou/basket/
- Demo: https://basket-nu.vercel.app/
- Team: 3 GitHub contributor(s) — Gregory Shklovski (25 commits), William Hou (13 commits), Yasper De Jong (8 commits)

## Devpost submission (written by the team)

### Inspiration

Supermarkets are filled from floor to ceiling with unhealthy foods, made cheap by unsafe pesticides, unsustainable agricultural practices, and unsanitary factory production, all while the grocery stores pushing these products take a massive cut of the profits. In America, people feel that organic foods and a healthy lifestyle are out of reach. Farmer's markets typically happen once a week for only half the day, consumers don't know what food will be there, and they charge farmers upwards of $200 to have a booth. Farm-to-table services are convenient for consumers, but logistical problems like order tracking and last mile distribution can be too much responsibility for farmers to tackle, often causing them to outsource logistics and introduce yet another middle-man, with more fees.

### What it does

Basket provides a solution, with the consumer convenience of farm-to-table services, and the simplicity that farmer's markets provide for farmers. Basket does this through the power of shared deliveries, a business model that has taken over Chinese e-commerce, but has yet to make its way to the west. When you open the app, you can scroll through food sold by farms near you and buy what you want. Then, the app combines your order with the orders of people near you, and sends one bulk order to be fulfilled by the farm. The farm then ships the order to one of the customers who volunteers as a pickup point. This address is then sent to the other customers who can pick up their order. This provides consumers with the convenience of online shopping, knowing exactly what food they're getting and how much they're paying, while providing farmer's the simplicity of farmer's markets, with only one delivery to make. The high costs of delivery services and farmer's markets fees are eliminated, and the delivery fees needed by the farmer are split among customers for the bulk order.

### How we built it

The backend consists of a PostgreSQL database holding products, orders, and all other data. A Python server powered by FastAPI provides the algorithms to combine orders and determine prices. The frontend is written using React + Vite, using Tailwind for styling and Auth0 for authentication.

### Challenges we ran into

Managing deployment for both backend and frontend were difficult, as well as the SQL queries to manage interactions between orders.

### Accomplishments we're proud of

We're proud of coming up with a sound business model, and using our different specializations to build an e-commerce store in a limited time.

### What we learned

We learned about the costs incurred by farmers in the agricultural process from top to bottom, and how money gets distributed through the grocery store system.

### What's next

In the future, Basket could partner with local grocers to provide delivery points. This will help with privacy concerns for the pickup point volunteers, and build a positive relationship between Basket and grocers, who will receive traffic from Basket customers.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 24 recognized source files, 33 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- PostgreSQL (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code

## Codebase structure (from repository index)

### Files (35 of 35)

```
.DS_Store
backend/.gitignore
backend/database.py
backend/helpers.py
backend/main.py
backend/requirements.txt
frontend/.eslintrc.cjs
frontend/.gitignore
frontend/bun.lockb
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/public/fonts/Comfortaa/OFL.txt
frontend/public/fonts/Comfortaa/README.txt
frontend/public/fonts/Open_Sans/OFL.txt
frontend/public/fonts/Open_Sans/README.txt
frontend/README.md
frontend/src/.config.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/auth.jsx
frontend/src/components/basket.jsx
frontend/src/components/cart.jsx
frontend/src/components/CheckOutCard.jsx
frontend/src/components/ItemCard.jsx
frontend/src/components/ProgressBar.jsx
frontend/src/contexts/CartContext.js
frontend/src/index.css
frontend/src/main.jsx
frontend/src/views/CheckoutView.jsx
frontend/src/views/ConfirmationView.jsx
frontend/src/views/ItemDetailView.jsx
frontend/src/views/ItemList.jsx
frontend/tailwind.config.js
frontend/vite.config.js
```

### Dependencies

- backend/requirements.txt: annotated-types@==0.6.0, anyio@==4.2.0, click@==8.1.7, fastapi@==0.109.2, h11@==0.14.0, httptools@==0.6.1, idna@==3.6, psycopg2@==2.9.9, pydantic@==2.6.1, pydantic_core@==2.16.2, python-dotenv@==1.0.1, PyYAML@==6.0.1, sniffio@==1.3.0, starlette@==0.36.3, typing_extensions@==4.9.0, uvicorn@==0.27.1, uvloop@==0.19.0, watchfiles@==0.21.0, websockets@==12.0
- frontend/package.json: @react-oauth/google@latest, @types/react@^18.2.55, @types/react-dom@^18.2.19, @vitejs/plugin-react-swc@^3.5.0, autoprefixer@^10.4.17, eslint@^8.56.0, eslint-plugin-react@^7.33.2, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.5, framer-motion@^11.0.5, postcss@^8.4.35, prop-types@^15.8.1, react@^18.2.0, react-dom@^18.2.0, react-router-dom@^6.22.1, tailwindcss@^3.4.1, vite@^5.1.0

### Recent commits (newest first)

- Update tagline
- Merge pull request #12 from will-hou:greg-dev
- greg improvements
- Fix loading bar
- Merge pull request #10 from will-hou:greg-dev
- Live update price (#11)
- improved ui
- improved ui
- better ui for checkout
- Update col names (#9)
- Check out card updated
- tracking all files
- check out card
- fixes and chekcout page started
- Update col names (#8)
- Connect items list with API (#7)
- Basket(cart) context added
- Merge branch 'main' of https://github.com/will-hou/basket
- address input for registration pretty
- Fixed createuser

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

### backend/requirements.txt

```
annotated-types==0.6.0
anyio==4.2.0
click==8.1.7
fastapi==0.109.2
h11==0.14.0
httptools==0.6.1
idna==3.6
psycopg2==2.9.9
pydantic==2.6.1
pydantic_core==2.16.2
python-dotenv==1.0.1
PyYAML==6.0.1
sniffio==1.3.0
starlette==0.36.3
typing_extensions==4.9.0
uvicorn==0.27.1
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@react-oauth/google": "latest",
    "framer-motion": "^11.0.5",
    "prop-types": "^15.8.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.1"
  },
  "devDependencies": {
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "autoprefixer": "^10.4.17",
    "eslint": "^8.56.0",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "postcss": "^8.4.35",
    "tailwindcss": "^3.4.1",
    "vite": "^5.1.0"
  }
}

```

### backend/main.py

```python
from fastapi import FastAPI, Query, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from psycopg2 import DatabaseError

from dotenv import load_dotenv
from database import connect, create_tables_if_needed, deletefulldb

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class User(BaseModel):
    email: str
    addr: str
    role: str # does this work?

@app.get('/getprogress')
def get_progress():
    conn = connect()
    try:
        with conn.cursor() as cur:
            cur.execute("select count(*) as num_orders from orders")
            return {'cur': cur.fetchone()[0], 'threshold': 5}
    except (DatabaseError, Exception) as err:
        print("Failed with get progress")
        print(err)
        raise HTTPException(status_code=500, detail="Failed with get progress")
    finally:
        conn.close()


@app.post('/deletefulldb')
def deletefulldb():
    deletefulldb()

@app.post('/createuser')
def createuser(user: User):
    conn = connect()
    try:
        with conn.cursor() as cur:
            cur.execute("INSERT INTO users (email, addr, role) VALUES (%s, %s, %s);",
                        (user.email, user.addr, user.role))
            conn.commit()
        return "Success"
    except (DatabaseError, Exception) as err:
        print("Error adding user to databse")
        print(err)
        raise HTTPException(status_code=500, detail="Failed to add user to db")
    finally:
        conn.close()

@app.get('/addtoorder')
def addtoorder(email: str, itemid: int):
    conn = connect()
    try:
        with conn.cursor as cur:
            cur.execute("SELECT ARRAY_LENGTH(orders) FROM users WHERE email = %s", (email,))
            if cur.fetchone()[0] > 0:
                cur.execute("UPDATE order o JOIN users u ON u.orders[1] = o.id SET o.items = ARRAY_APPEND(o.items, %s) WHERE u.email = %s;", (itemid, email))
            else:
                pass
    finally:
        conn.close()

@app.get('/getuser')
def getuser(email: str = Query(...)):
    conn = connect()
    try:
        with conn.cursor() as cur:
            cur.execute("SELECT id, email, addr, role FROM users WHERE email = %s", (email,))
            result = {}
            item = cur.fetchone()
            if item:
                return {
                    "id": item[0],
                    "email": item[1],
                    "addr": item[2],
                    "role": item[3]
                }
            else:
                return {}
    finally:
        conn.close()

@app.get('/getorder')
def getorder(email: str = Query(...)):
    conn = connect()

    with conn.cursor() as cur:
        cur.execute("""
        select * from items where id in (
        select unnest(o.items)
        from users u JOIN orders o ON u.id = o.owner
        WHERE u.email = %s);
        """, (email,))
        result = cur.fetchall()
    conn.close()
    return result

@app.get('/listitems')
def listitems():
    conn = connect()
    with conn.cursor() as cur:
        cur.execute('SELECT i.id as item_id, i.farm_id as farm_id, i.name as item_name, i.price as item_price, i.image as item_image, f.name as farm_name, f.image as farm_image, f.owner_id as seller FROM items i LEFT JOIN farms f ON i.farm_id = f.id;')
        resultlist = []
        for item in cur:
            result = {}
            for key, val in zip(('itemId', 'farmId', 'itemName', 'itemPrice', 'itemImage', 'farmName', 'farmImage', 'seller'), item):
                result[key] = val
            resultlist.append(result)
        conn.close()
        return resultlist

class Item(BaseModel):
    name: str
    farm_id: int | None = None
    price: float
    image: str | None = None

@app.post('/createitem')
def additem(item: Item):
    conn = connect()
    try:
        with conn.cursor() as cur:
            cur.execute(
                'INSERT INTO items (name, farm_id, price) VALUES (%s, %s, %s);',
                (item.name, item.farm_id, item.price))
            conn.commit()
        return "Success"
    except (DatabaseError, Exception) as err:
        print("Error adding item to db")
        print(err)
        raise HTTPException(status_code=500, detail="Failed to add item to db")
    finally:
        conn.close()


@app.get('/testpoint')
def root():
    return {"message": "Hello World"}

@app.get('/')
def rootindex():
    return FileResponse('../frontend/dist/index.html')

# app.mount("/", StaticFiles(directory="../frontend/dist"), name="site")

if __name__ == 'main':
    load_dotenv()
    connection = connect()
    print("Going to create tables")
    create_tables_if_needed(connection)
    connection.close()
    print("Closed sql conneciton")


```

### frontend/src/App.jsx

```javascript
import "./App.css";

import vercel_logo from "./assets/vercel.png";

import Auth from "./components/auth.jsx";
import Basket from "./components/basket.jsx";

function App() {
  return (
    <div className="h-screen overflow-y-hidden">
      <Basket size="10" />
      <h1 className="text-center text-6xl font-display text-secondary font-bold mt-2">
        Basket
      </h1>
      <p className="text-center mt-2 mb-8 text-base font-body font-semibold text-black/40">
        {/* <img className="inline h-4 opacity-50" src={vercel_logo} /> for farmers */}
        Fresh groceries, delivered together.
      </p>
      <div className="text-center text-2xl font-body mx-10 p-4 border bg-white/70 rounded-2xl drop-shadow-md">
        <Auth />
      </div>
    </div>
  );
}

export default App;

```

### frontend/src/main.jsx

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { GoogleOAuthProvider } from "@react-oauth/google";
import { CLIENT_ID } from "./.config.js";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import ItemList from "./views/ItemList.jsx";
import ItemDetailView from "./views/ItemDetailView.jsx";
import CartContext from "./contexts/CartContext.js";
import CheckoutView from "./views/CheckoutView.jsx";
import ConfirmationView from "./views/ConfirmationView.jsx";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
  },
  { path: "/items", element: <ItemList /> },
  { path: "/item_description", element: <ItemDetailView /> },
  { path: "/checkout", element: <CheckoutView /> },
  { path: "/confirmation", element: <ConfirmationView /> },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <GoogleOAuthProvider clientId={CLIENT_ID}>
      <CartContext.Provider value={{ cart: new Set() }}>
        <RouterProvider router={router} />
      </CartContext.Provider>
    </GoogleOAuthProvider>
  </React.StrictMode>
);

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/vite.config.js

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

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

```

### frontend/index.html

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

```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: {
        primary: "#344e41",
        secondary: "#588157",
        tertiary: "#a3B18a",
        warning: "#BC4749",
        background: "#fefae0",
      },
      fontFamily: {
        display: ["Comfortaa"],
        body: ["Open_sans"],
      },
    },
  },
  plugins: [],
};

```

### backend/helpers.py

```python
from configparser import ConfigParser

def load_config(filename, section):

    if filename is None or section is None:
        raise Exception('Wrong parameters')
    
    parser = ConfigParser()
    parser.read(filename)

    config={}
    if parser.has_section(section):
        params = parser.items(section)
        for param in params:
            config[param[0]] = param[1]
    else:
        raise Exception('Section {0} not found in the {1} file'.format(section, filename))

    return config

```

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