# Project export: WINC

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: WINC is a smart outfit generator that uses your wardrobe, weather, and vibe to create daily looks with AI. Simply upload your clothes, and WINC suggests stylish outfits, saving you tons of time.
- Devpost: https://devpost.com/software/winc
- GitHub: https://github.com/hoethan231/calHacks24
- Team: 4 GitHub contributor(s) — Ethan (41 commits), Amy Okuma (24 commits), Richard Yip (15 commits), Karla Nguyen (8 commits)

## Devpost submission (written by the team)

### Inspiration

We were inspired by the daily challenge many people face when deciding what to wear. Whether it’s the desire to dress appropriately for the weather or an event, or simply a need for inspiration, people often waste time and effort in front of their closets. We wanted to make the process easier and more enjoyable by creating an app that not only considers the weather and vibe of the day but also maximizes the use of the clothes people already own.

### What it does

Our app, WINC (Walk-in Closet), generates outfit suggestions from a user’s own closet based on the user’s current weather and mood or vibe for the day. Users upload their clothing items into the app, and WINC curates complete outfits, giving them inspiration and maximizing the utility of their wardrobe. The app ensures that the outfits are not only fashionable but also practical, giving users more confidence in their daily dress choices. It also promotes sustainability by encouraging users to rediscover and restyle the clothing they already have.

### How we built it

Frontend: We used Next.js with Typescript and styled it with Tailwind CSS to create a fast, responsive, and highly interactive user interface. This allowed us to focus on a smooth user experience across devices. Backend: The core of our application is built in Python using Flask, handling user requests, clothing item categorization, and outfit generation logic. Data Storage: SingleStore was implemented for its powerful and easy-to-use capabilities to manage our data and, most importantly, perform efficient vector searches to match user-inputted vibe to the clothes in their wardrobe. Machine Learning: We used Google Gemini to perform image segmentation and image-to-text analysis, which singles out the clothing from the background, tags items based on categories, and provides intelligent outfit suggestions. Weather Integration: OpenWeather was used to integrate real-time weather data, allowing the app to suggest weather-appropriate outfits.

### Challenges we ran into

This was our first time using Next.js with TypeScript, so we faced a learning curve as we developed the project. As a team of four working on both the front end and back end, we encountered challenges in merging the two sides to create a fully functional app. On the backend, we had issues storing images as blobs in the cloud and running them through segmentation, as well as connecting our two databases. We also struggled with getting CORS to work properly on Mac, which added to the complexity of the project.

### Accomplishments we're proud of

We’re proud of building WINC from the ground up, especially the backend, where we tackled complex tasks like image storage, segmentation, and database integration. Also creating a clean and intuitive UI/UX design, ensuring the app is easy to use and visually appealing. Despite the challenges of learning Next.js and TypeScript for the first time, we worked through these obstacles as a team and delivered a fully functioning app.

### What we learned

It was our first time using Next.js along with TypeScript, so through this project, we learned how to navigate using these frameworks and languages, deepening our understanding of building responsive and performant web applications. For the backend, it was the first time using SingleStore and working with Google Gemini, so we became more comfortable working with databases and, more generally, adapting to new technologies. We also learned the importance of using version control effectively — such as making atomic commits, and git pushing and pulling often so there won’t be millions of conflicts.

### What's next

Future features of WINC will include the ability to add more items, like shoes, one-pieces, and accessories, to create complete outfits. Within the dashboard, it’ll allow users to create their own outfits and update their Outfit Log. This log will train the AI to suggest more personalized outfits and avoid duplicating looks within an appropriate short timeframe. Users will also be able to save and favorite their outfits, as well as edit tags on their clothing items. There will also be a mobile app that will enable users to easily snap pictures of their clothes directly from their phones.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 33 recognized source files, 74 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (52 of 52)

```
.DS_Store
backend/.Dockerfile
backend/.DS_Store
backend/.gitignore
backend/cors_config.json
backend/cors-config.json
backend/describe.py
backend/helper.py
backend/image_segmentation.py
backend/models/selfie_multiclass_256x256.tflite
backend/requirements.txt
backend/server.py
Docker-compose.yml
frontend/.Dockerfile
frontend/.gitignore
frontend/app/Components/aspectRatio.tsx
frontend/app/Components/background-gradient-animation.tsx
frontend/app/Components/button.tsx
frontend/app/Components/calendar.tsx
frontend/app/Components/card.tsx
frontend/app/Components/generatedOutfit.tsx
frontend/app/Components/imageUpload.tsx
frontend/app/Components/input.tsx
frontend/app/Components/label.tsx
frontend/app/Components/loginForm.tsx
frontend/app/Components/miniWardrobe.tsx
frontend/app/Components/sidebar.tsx
frontend/app/Components/sidebarContainer.tsx
frontend/app/Components/signupForm.tsx
frontend/app/Components/tabs.tsx
frontend/app/Components/typewriter-effect.tsx
frontend/app/Components/weather.tsx
frontend/app/dashboard/page.tsx
frontend/app/fonts/GeneralSans-Bold.otf
frontend/app/fonts/GeneralSans-Light.otf
frontend/app/fonts/GeneralSans-Medium.otf
frontend/app/fonts/GeneralSans-Regular.otf
frontend/app/fonts/GeneralSans-Semibold.otf
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/lib/utils.ts
frontend/app/myWardrobe/page.tsx
frontend/app/outfitLog/page.tsx
frontend/app/page.tsx
frontend/app/savedOutfits/page.tsx
frontend/app/signup/page.tsx
frontend/next.config.mjs
frontend/package.json
frontend/postcss.config.mjs
frontend/README.md
frontend/tailwind.config.ts
frontend/tsconfig.json
```

### Dependencies

- backend/requirements.txt: flask, flask-cors, google-cloud-storage, google-generativeai, mediapipe, opencv-python, pillow, python-dotenv, singlestoredb
- frontend/package.json: @daypilot/daypilot-lite-react@^3.27.0, @radix-ui/react-aspect-ratio@^1.1.0, @radix-ui/react-label@^2.1.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-tabs@^1.1.1, @tabler/icons-react@^3.19.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, @types/react-modal@^3.16.3, axios@^1.7.7, class-variance-authority@^0.7.0, clsx@^2.1.1, framer-motion@^11.11.9, next@14.2.15, postcss@^8, react@^18, react-big-calendar@^1.15.0, react-dom@^18, react-dropzone@^14.2.9, react-modal@^3.16.1, react-router-dom@^6.27.0, tailwind-merge@^2.5.4, tailwindcss@^3.4.1, typescript@^5

### Recent commits (newest first)

- containerized application
- Merge pull request #15 from hoethan231/implement-favoriting
- Merge branch 'main' into implement-favoriting
- Style outfit generation
- Implement favoriting and logging
- blah
- generate styling
- Merge pull request #14 from hoethan231/implement-outfits
- Merge branch 'main' into implement-outfits
- cycle clothes
- smal
- modal kinda work
- delete random
- Merge pull request #13 from hoethan231/test-generation
- outfit made
- showing doubles
- implemented combinations
- implement comboinations
- Write routes for outfits table
- update requirements.txt

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

### backend/requirements.txt

```
singlestoredb
python-dotenv
google-cloud-storage
google-generativeai
flask-cors
flask
pillow
opencv-python
mediapipe
```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@daypilot/daypilot-lite-react": "^3.27.0",
    "@radix-ui/react-aspect-ratio": "^1.1.0",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-tabs": "^1.1.1",
    "@tabler/icons-react": "^3.19.0",
    "axios": "^1.7.7",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "framer-motion": "^11.11.9",
    "next": "14.2.15",
    "react": "^18",
    "react-big-calendar": "^1.15.0",
    "react-dom": "^18",
    "react-dropzone": "^14.2.9",
    "react-modal": "^3.16.1",
    "react-router-dom": "^6.27.0",
    "tailwind-merge": "^2.5.4"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@types/react-modal": "^3.16.3",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### backend/server.py

```python
import os

import helper as db
from flask import Flask, jsonify, request
from flask_cors import CORS
from image_segmentation import crop_image

app = Flask(__name__)
CORS(app)


@app.route("/upload_file", methods=["POST"])
def upload_file_route():
    try:
        file = request.files.get("file")
        if not file:
            return jsonify({"error": "Invalid file"}), 400
        file_name, file_extension = os.path.splitext(file.filename)
        file = crop_image(file)
        db.upload_file(file, file_name)
        return "success", 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route("/upload_outfit", methods=["POST"])
def upload_outfit_route():
    data = request.json
    if not data:
        return jsonify({"error": "Invalid input"}), 400
    # Check if outfit exists
    outfit_id = db.get_outfit_id(
        data["userID"], data["top_part_url"], data["bottom_part_url"]
    )
    if outfit_id:
        print(outfit_id)
        return jsonify({"error": "Outfit already exists"}), 400
    db.add_outfit(
        data["userID"],
        data["top_part_url"],
        data["bottom_part_url"],
        data["date_added"],
        data["saved"],
    )
    return "success", 200


@app.route("/bookmark_outfit", methods=["POST"])
def upload_outfit_route():
    data = request.json
    if not data:
        return jsonify({"error": "Invalid input"}), 400
    outfit_id = db.get_outfit_id(
        data["userID"], data["top_part_url"], data["bottom_part_url"]
    )
    if not outfit_id:
        return jsonify({"error": "Outfit ID does not exist"}), 400
    db.update_outfit_saved(data["userID"], outfit_id, data["saved"])
    return "success", 200


@app.route("/login", methods=["POST"])
def login():
    data = request.json
    if not data:
        return jsonify({"error": "Invalid input"}), 400
    userID = db.get_userID(data["email"], data["password"])
    if userID:
        return {"userID": userID[0][0]}, 200
    return jsonify({"error": "Authentication failed"}), 401


@app.route("/register", methods=["POST"])
def register():
    data = request.json
    if not data:
        return jsonify({"error": "Invalid input"}), 400
    db.create_user(
        data["first"], data["last"], data["username"], data["password"], data["email"]
    )


@app.route("/get_images", methods=["GET"])
def get_clothes():
    clothes = db.get_clothes(2251799813685250)
    return jsonify(clothes), 200


@app.route("/get_combinations", methods=["POST"])
def get_combinations():
    data = request.json
    if not data:
        return jsonify({"error": "Error fetching"}), 400
    n_tops = list(
        map((lambda x: x[0]), db.rag_top_items(data["vibe"], data["limit"], "top"))
    )
    n_bottoms = list(
        map((lambda x: x[0]), db.rag_top_items(data["vibe"], data["limit"], "bottom"))
    )
    combinations = zip(n_tops, n_bottoms)
    return list(combinations)


if __name__ == "__main__":
    app.run(debug=True)

```

### frontend/app/layout.tsx

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


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>
        {children}
      </body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
import React from "react";
import { BackgroundGradientAnimation } from "./Components/background-gradient-animation";
import { Button } from "./Components/button";
import Link from "next/link";
import { TypewriterEffectSmooth } from "./Components/typewriter-effect";

export default function BackgroundGradientAnimationDemo() {
  const words = [
    {
      text: "Welcome",
      className: "text-[#ffffff] text-[100px] cursor-default drop-shadow-lg bg-gradient-to-b from-white/80 to-white/20 text-transparent bg-clip-text",
    },
    {
      text: "to",
      className: "text-[#ffffff] text-[100px] cursor-default drop-shadow-lg bg-gradient-to-b from-white/80 to-white/20 text-transparent bg-clip-text",
    },
    {
      text: "Winc",
      className: "text-[#cbddf7] text-[100px] cursor-default drop-shadow-lg underline decoration-4 underline-offset-8",
    },
    {
      text: ";)",
      className: "text-[#ffffff] text-[100px] cursor-default drop-shadow-lg bg-gradient-to-b from-white/80 to-white/20 text-transparent bg-clip-text",
    },
  ];
 
  return (
    <BackgroundGradientAnimation>
      <div className="absolute z-50 inset-0 flex flex-col items-center justify-center text-white font-bold px-4 text-center space-y-0">
        <TypewriterEffectSmooth words={words} />
        <p className="bg-clip-text text-transparent drop-shadow-2xl bg-gradient-to-b from-white/80 to-white/20 text-lg md:text-lg lg:text-xl font-medium px-64 mt-10">
        Come Build Your OOTD! Winc lets you upload your favorite pieces of clothing, and then it generates an outfit just for you! 
        It’s a super easy way to look great without the hassle—just pick your clothes, and we’ll help you style them perfectly for any occasion!
        </p>
        <div className="pointer-events-auto md:space-x-4">
          <Link href="/signup" passHref>
            <Button className=" py-2 px-8 text-2xl text-white border border-white rounded-[100px] cursor-pointer bg-gradient-to-b from-blue/80 to-white/20 shadow-lg hover:bg-white hover:text-[#39516E] mt-7">
              Get Started
            </Button>
          </Link>
          <Button className=" py-2 px-8 text-2xl text-white border border-white rounded-[100px] cursor-pointer bg-gradient-to-b from-blue/80 to-white/20 shadow-lg hover:bg-white hover:text-[#39516E] mt-7">
              Learn More
            </Button>
        </div>
      </div>
    </BackgroundGradientAnimation>
  );
}

```

### frontend/app/outfitLog/page.tsx

```typescript
"use client"
import { useState } from "react";
import {SidebarContainer} from "../Components/sidebarContainer"
import Calendar from "../Components/calendar";

export default function outfitLog() {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  
    return (
      <div className="flex">
        <SidebarContainer open={sidebarOpen} setOpen={setSidebarOpen} />
        <div className="flex-grow">
          <div className="flex justify-between items-center m-10">
            <h1 className="text-5xl font-semibold text-[#39516E]">Outfit Log</h1>
          </div>
          <div className="h-1 bg-[#39516E] mx-10 -my-3" />
          <div className="mx-14 mt-6">
            <Calendar />
          </div>
        </div>
      </div>
    );
  }
   
```

### frontend/app/dashboard/page.tsx

```typescript
"use client"
import { SidebarContainer } from "../Components/sidebarContainer";
import Weather from "../Components/weather";
import { GeneratedOutfit } from "../Components/generatedOutfit";
import { MiniWardrobe } from "../Components/miniWardrobe";
import { useEffect, useState } from "react";
import axios from "axios";

export default function Dashboard() {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [vibe, setVibe] = useState("");
  const [combinations, setCombinations] = useState([]);

  const handleVibeChange = (newVibe: string) => {
    setVibe(newVibe);
  };

  useEffect(() => {
    if(vibe) {
      axios.post("http://localhost:3000/get_combinations", { "vibe":vibe, "limit":5 })
      .then((response) => {
        setCombinations(response.data);
      })
      .catch((error) => {
        console.log("error:" + error);
      })
    }
  },[vibe]);

  return (
    <div className="flex">
      <SidebarContainer open={sidebarOpen} setOpen={setSidebarOpen} />
      <div>
        <div className="flex justify-between items-center m-10">
          <h1 className="text-5xl font-semibold text-[#39516E]">Dashboard</h1>
        </div>
        <div className="h-1 bg-[#39516E] mx-10 -my-3" />
        <div className="flex">
          <GeneratedOutfit sidebarOpen={sidebarOpen}/>
          <div>
            <Weather />
            <MiniWardrobe />
          </div>
        </div>
      </div>
    </div>
  );
}

```

### frontend/app/savedOutfits/page.tsx

```typescript
"use client"
import { useState, useEffect } from "react";
import axios from "axios";
import { Card, CardContent, CardTitle } from "../Components/card";
import {SidebarContainer} from "../Components/sidebarContainer"

interface Outfit {
  top_part_url: string;
  bottom_part_url: string;
}

export default function savedOutfits() {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [favoriteOutfits, setFavoriteOutfits] = useState<Outfit[]>([]);
  const [outfit, setOutfit] = useState<Outfit[]>([]);

  useEffect(() => {
    const fetchFavoriteOutfits = async () => {
      await axios.post("http://localhost:5000/upload_outfit").then((response) => {
        setFavoriteOutfits(response.data);
      });
    };
    fetchFavoriteOutfits();
  },[]);
  
    return (
      <div className="flex">
      <SidebarContainer open={sidebarOpen} setOpen={setSidebarOpen} />
      <div className="flex-grow">
        <div className="flex justify-between items-center m-10">
          <h1 className="text-5xl font-semibold text-[#39516E]">Saved Outfits</h1>
        </div>
        <div className="h-1 bg-[#39516E] mx-10 -my-3" />
        <div className="flex flex-col items-center">
          <ul>
            {favoriteOutfits.map((outfit, index) => (
              <li key={index}>
                <Card>
                  <CardContent>
                    <CardTitle>{outfit.top_part_url} {outfit.bottom_part_url}</CardTitle>
                  </CardContent>
                </Card>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
    );
  }
   
```

### frontend/app/signup/page.tsx

```typescript
import { SignupForm } from "../Components/signupForm";
import { LoginForm } from "../Components/loginForm";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../Components/tabs";
import { BackgroundGradientAnimation } from "../Components/background-gradient-animation";
import Image from "next/image";
import winc from "../assets/winc-white.png";

interface SignupPageProps {
    setUserID: (id: string) => void;
}

const SignupPage: React.FC<SignupPageProps> = ({ setUserID }) => {
    return (
        <BackgroundGradientAnimation>
            <div className="flex min-h-screen relative z-10">
                <div className="w-1/2 flex justify-center items-center space-y-0">
                    <Tabs defaultValue="signUp" className="w-[400px]">
                        <TabsList>
                            <TabsTrigger value="signUp" className="-mt-3">Sign Up</TabsTrigger>
                            <TabsTrigger value="logIn" className="-mt-3">Log In</TabsTrigger>
                        </TabsList>
                        <TabsContent value="signUp"><SignupForm /></TabsContent>
                        <TabsContent value="logIn"><LoginForm /></TabsContent>
                    </Tabs>
                </div>
                <div className="w-1/2 bg-blurBackground bg-cover bg-center flex justify-center items-center text-white text-6xl">
                    <div className="text-right">
                        Thank you for 
                        <br /> 
                        <span className="text-white text-6xl">choosing <span className=" text-8xl font-bold text-[#cbddf7] drop-shadow-xl">winc!</span></span>
                        <span className="block text-xl font-light text-[#ffffff] drop-shadow-xl">Please enter your details to get started.</span>
                    </div>
                </div>
                <div className="absolute top-20 right-20">
                    <Image src={winc} width={180} height={80} alt="Winc logo" />
                </div>
                <div className="absolute inset-0 z-[-1]">
                    <BackgroundGradientAnimation />
                </div>
            </div>
        </BackgroundGradientAnimation>
    );
};

export default SignupPage;
```

### frontend/app/myWardrobe/page.tsx

```typescript
"use client"
import Image from "next/image"
import { AspectRatio } from "../Components/aspectRatio"
import { SidebarContainer } from "../Components/sidebarContainer"
import { ImageUpload } from "../Components/imageUpload";
import { useState, useEffect } from "react";
import axios from "axios";
import Modal from "react-modal";

export default function myWardrobe() {
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [images, setImages] = useState<{ imgURL: string }[]>([]);
  const [modalIsOpen, setModalIsOpen] = useState(false);
  const [selectedImage, setSelectedImage] = useState<{ imgURL: string; category?: string; description?: string } | null>(null);

  useEffect(() => {
    const fetchImages = async () => {
      await axios.get("http://localhost:5000/get_images").then((response) => {
        console.log(response.data);
        setImages(response.data);
      });
    };
    fetchImages();
  },[]);

  const handleImageClick = (image: { imgURL: string }) => {
    setSelectedImage(image);
    setModalIsOpen(true);
  };

  const handleModalClose = () => {
    setModalIsOpen(false);
  };

    return (
      <div className="flex">
        <SidebarContainer open={sidebarOpen} setOpen={setSidebarOpen}  />
        <div className="flex-grow">
            <div className="flex justify-between items-center m-10">
                <h1 className="text-5xl font-semibold text-[#39516E]">My Wardrobe</h1>
                <div className="ml-auto mt-4 mr-10">
                    <ImageUpload />
                </div>
            </div>
            <div className="h-1 bg-[#39516E] mx-10 -my-3" />
            <div className="grid grid-cols-5 gap-20 mx-32 my-16">
              {images && images.map((src, index) => (
                <div key={index}>
                  <AspectRatio ratio={1 / 1}>
                    <Image src={src.imgURL} alt={`Wardrobe Item`} width={450} height={450} className="rounded-md object-cover" onClick={() => handleImageClick(src)} />
                  </AspectRatio>
                </div>
              ))}
            </div>
            <div>
              <Modal
                isOpen={modalIsOpen}
                onRequestClose={handleModalClose}
                className="w-[700px] h-[500px] bg-white fixed top-[150px] left-[450px] rounded-[20px]"
                overlayClassName="overlay"
              >
                {selectedImage && (
                  <div className="flex justify-between items-center mt-20">
                    <img src={selectedImage.imgURL} alt={selectedImage.category} className="w-60 h-auto mr-11"/>
                    <div className="mr-8">
                      <h2 className="font-normal"><span className="font-bold">Category: </span>{selectedImage.category}</h2>
                      <br/>
                      <p className="font-normal"><span className="font-bold">Description: </span>{selectedImage.description}</p>
                    </div>
                  </div>
                )}
              </Modal>
            </div>
        </div>
      </div>
    );
}
   
```

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