# Project export: StudyBuddy

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: StudyBuddy is a platform designed to easily connect like-minded students in real time to provide a comfortable and productive environment!
- Devpost: https://devpost.com/software/studysync-gbo8fx
- GitHub: https://github.com/Yelloo5191/StudyBuddy
- Demo: https://study-buddy-phi-delta.vercel.app/
- Team: 3 GitHub contributor(s) — Hovhannes Muradyan (51 commits), Eli Whitaker (13 commits), Oliver Pearce (9 commits)

## Devpost submission (written by the team)

### Inspiration

Accountability during studying is hard when you do it alone, finding people to help you study with is such an important aspect of learning. If there was a way to connect with people anywhere who are learning what you are, studying could be easier than ever.

### What it does

Using OpenAi, matchmaking is computed for users anywhere around the world by class, major, and special interest. Join StudyBuddy through your email, enter the information you need to learn, and you'll be paired to a video chat with another student interested in studyng too.

### How we built it

We used next.js to code and build the website, OpenAi was used to queue matchmaking based on course material, and Agora was used to create two way video/audio communication.

### Challenges we ran into

Creating an AI matchmaking system using FetchAI was our original plan, however impelenting this idea proved more difficult than anticipated. We instead pivoted to using OpenAI to do the matchmaking instead. This was not desired, as we wanted to use our sponsor's products as much as possible, but we didn't find it detrimental to the end product. Additionally, we created a chatbox for users who cannot speak or communicate through the video, which was difficult to implement without putting a lot of strain on the server side but was eventually solved using Supabase's realtime database functions.

### Accomplishments we're proud of

We successfully implemented two way video communication, profile login with google, a chatbox, as well as an embedded music player for users to listen to while they study together. We also were able to make a complete website with home, about section, and profile section.

### What we learned

We learned a lot about Supabase's data base functions. One of our teammates had little to no experience coding in js, but was able to learn a substantial amount and completed a majority of front end development while the other members worked on implementing the matchmaking and video chat.

### What's next

We'd like to finish polishing a lot of the front end and finishing formatting, although the video and text chat is fully functional.

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 37 recognized source files, 50 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (44 of 44)

```
.eslintrc.json
.gitignore
LICENSE
middleware.ts
next.config.mjs
package.json
postcss.config.mjs
README.md
src/app/auth/callback/route.ts
src/app/auth/confirm/route.ts
src/app/channel/[channelName]/page.tsx
src/app/dashboard/actions.ts
src/app/dashboard/Form.tsx
src/app/dashboard/page.tsx
src/app/error/page.tsx
src/app/globals.css
src/app/layout.tsx
src/app/login/actions.ts
src/app/login/page.tsx
src/app/login/SignInButton.tsx
src/app/page.tsx
src/app/profile/actions.ts
src/app/profile/LogOut.tsx
src/app/profile/page.tsx
src/app/profile/SwitchAccount.tsx
src/app/waiting/page.tsx
src/components/AboutSection/About.tsx
src/components/AboutSection/AIUsage.tsx
src/components/AboutSection/InformationAboutProduct.tsx
src/components/AboutSection/ListenToMusic.tsx
src/components/AboutSection/Musics.tsx
src/components/Chat/Chat.tsx
src/components/Dashboard.tsx
src/components/DataStreamContext.js
src/components/Footer/Footer.tsx
src/components/Footer/Instagram.tsx
src/components/Header.tsx
src/components/Hero.tsx
src/components/Music/Music.tsx
src/utils/supabase/client.ts
src/utils/supabase/middleware.ts
src/utils/supabase/server.ts
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @fetchai/ai-engine-sdk@^0.1.0, @supabase/ssr@^0.5.1, @supabase/supabase-js@^2.45.5, @types/node@^20, @types/react@^18, @types/react-dom@^18, agora-chat-uikit@^1.1.0, agora-rtc-react@^2.3.0, agora-rtc-sdk-ng@^4.22.1, eslint@^8, eslint-config-next@14.2.15, fetchai-sdk@^1.11.0, next@14.2.15, openai@^4.68.1, postcss@^8, react@^18, react-dom@^18, react-icons@^5.3.0, react-use-audio-player@^2.2.0, tailwindcss@^3.4.1, typescript@^5, zod@^3.23.8

### Recent commits (newest first)

- fix build error
- fixed youtube image
- Final Website
- push
- final hopefully
- push
- login update
- Merge branch 'master' of https://github.com/Yelloo5191/StudySync
- hello
- videos working again
- fix build
- is video working
- sleepyyy
- names in chat
- merged
- well ig thats music
- youtube is p cool ngl
- Merge branch 'master' of github.com:Yelloo5191/StudySync
- input sanity checks, should be good YES
- fix profile

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

### package.json

```
{
  "name": "calhacks2023",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@fetchai/ai-engine-sdk": "^0.1.0",
    "@supabase/ssr": "^0.5.1",
    "@supabase/supabase-js": "^2.45.5",
    "agora-chat-uikit": "^1.1.0",
    "agora-rtc-react": "^2.3.0",
    "agora-rtc-sdk-ng": "^4.22.1",
    "fetchai-sdk": "^1.11.0",
    "next": "14.2.15",
    "openai": "^4.68.1",
    "react": "^18",
    "react-dom": "^18",
    "react-icons": "^5.3.0",
    "react-use-audio-player": "^2.2.0",
    "zod": "^3.23.8"
  },
  "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"
  }
}

```

### src/app/page.tsx

```typescript
import About from "@/components/AboutSection/About";
import Header from "@/components/Header";
import Footer from "@/components/Footer/Footer";
import Hero from "@/components/Hero";

export default function Home() {
  return (
    <>
      <Header />
      <Hero />
      <About />
      <Footer />
    </>
  );
}

```

### src/app/layout.tsx

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

export const metadata: Metadata = {
  title: "StudySync",
  description: "Calhacks 2024 StudySync Project",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className="antialiased h-screen">{children}</body>
    </html>
  );
}

```

### src/app/error/page.tsx

```typescript
export default function ErrorPage() {
  return <p>Sorry, something went wrong</p>;
}

```

### src/app/login/page.tsx

```typescript
import SignInButton from "./SignInButton";

export default function LoginPage() {
  return (
    <div className="flex flex-col items-center justify-center bg-dark_light w-full h-screen gap-4 p-4">
      <SignInButton />
    </div>
  );
}

```

### src/app/dashboard/page.tsx

```typescript
import { redirect } from "next/navigation";
import { createClient } from "@/utils/supabase/server";
import FormJoin from "./Form";
import Header from "@/components/Header";
import Footer from "@/components/Footer/Footer";
export default async function page() {
  const supabase = createClient();

  const { data, error } = await supabase.auth.getUser();
  if (error || !data?.user) {
    redirect("/profile");
  }
  return (
    <div>
      <Header />

      <div className="w-screen h-screen bg-dark_light p-8">
        <FormJoin />
      </div>
      <Footer />
    </div>
  );
}

```

### src/utils/supabase/server.ts

```typescript
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";

export function createClient() {
  const cookieStore = cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    }
  );
}

```

### src/app/waiting/page.tsx

```typescript
"use client";
import { createClient } from "@/utils/supabase/client";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function Waiting() {
  const supabase = createClient();
  const router = useRouter();

  useEffect(() => {
    const channel = supabase.channel("schema-db-changes");

    channel
      .on(
        "postgres_changes",
        { event: "INSERT", schema: "public", table: "waiting_queue" },
        async (payload) => {
          console.log("Changes in queue:", payload);

          const { user_id: addedUserId } = payload.new as { user_id: string };
          const {
            data: { user: currentUser },
          } = await supabase.auth.getUser();
          console.log("added:", addedUserId);
          const currentUserId = currentUser?.id;

          if (addedUserId && currentUserId && addedUserId !== currentUserId) {
            const buddy1_formatted = addedUserId.replaceAll("-", "");
            const buddy2_formatted = currentUserId.replaceAll("-", "");
            const channelName = `${buddy2_formatted}${buddy1_formatted}`;
            router.push(`/channel/${channelName}`);
          } else {
            console.error(
              "User IDs are undefined. Cannot format for channel name."
            );
          }
        }
      )
      .subscribe();

    return () => {
      supabase.removeChannel(channel);
    };
  }, [router]);

  return (
    <div className="flex flex-col items-center pt-40">
      <h1>Searching for a buddy</h1>
      <div role="status">
        <svg
          aria-hidden="true"
          className="w-8 h-8 text-gray-200 animate-spin dark:text-taupe_gray-600 fill-redwood"
          viewBox="0 0 100 101"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
            fill="currentColor"
          />
          <path
            d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
            fill="currentFill"
          />
        </svg>
      </div>
    </div>
  );
}

```

### src/app/profile/page.tsx

```typescript
import { createClient } from "@/utils/supabase/server";
import Header from "@/components/Header";
import Footer from "@/components/Footer/Footer";
import LogOut from "./LogOut";
import Image from "next/image";
import { redirect } from "next/navigation";
import SwitchAccounts from "./SwitchAccount";

export default async function ProfilePage() {
  const supabase = createClient();

  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    redirect("/login");
  }

  const { data } = await supabase
    .from("profiles")
    .select("*")
    .eq("id", user.id);

  if (!data) {
    redirect("/login");
  }

  const quotes = [
    "One day, all your hard work will pay off.",
    "If we wait until we’re ready, we’ll be waiting for the rest of our lives.",
    "It’s never too late to be what you might have been.",
    "You don’t have to be great to start. But you have to start to be great.",
    "Nobody can go back and start a new beginning, but anyone can start today and make a new ending.",
  ];

  function getRandomQuote() {
    const randomIndex = Math.floor(Math.random() * quotes.length);
    return quotes[randomIndex];
  }

  // console.log(data);

  const randomQuote = getRandomQuote();

  return (
    <>
      <Header />
      <div className="flex flex-col bg-dark_light w-full h-screen gap-4 p-4">
        <div className="w-full h-16 flex flex-row justify-center items-center bg-light_gray rounded-lg">
          <h1 className="text-lg text-white">Profile</h1>
        </div>
        <div className="flex flex-col md:flex-row w-full gap-4 h-full">
          <div className="w-full md:w-1/2 h-full md:h-2/3 flex flex-col gap-8 p-8 bg-light_gray rounded-lg justify-center items-center">
            <Image
              src={
                data[0].avatar_url ||
                "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Default_pfp.svg/2048px-Default_pfp.svg.png"
              }
              alt="avatar"
              width={200}
              height={200}
              className="rounded-full hover:animate-spin"
              unoptimized
            />
          </div>
          <div className="w-full h-full md:h-2/3 flex flex-col gap-8 p-8 bg-light_gray rounded-lg relative">
            <div className="flex flex-col md:flex-row gap-10">
              <div className="flex flex-col gap-6">
                <h1 className="text-3xl text-white">
                  Welcome,
                  <br />
                  <b>{data[0].full_name || "No full name available"}</b>
                </h1>
                <h1 className="text-lg text-white">
                  {user.email || "No email available"}
                </h1>
                <h1 className="text-lg text-white">
                  {user.id || "No id available"}
                </h1>

                <div className="flex flex-row focus-center">
                  <LogOut />
                  <SwitchAccounts />
                </div>
              </div>
              <div className="w-full flex justify-center items-center mb-20 md:mb-0">
                <h1 className="text-2xl text-white text-center italic">
                  {randomQuote}
                </h1>
              </div>
            </div>
          </div>
        </div>
      </div>
      <Footer />
    </>
  );
}

```

### src/app/channel/[channelName]/page.tsx

```typescript
// import Call from "@/components/Call";
import { createClient } from "@/utils/supabase/server";
import dynamic from "next/dynamic";

export default async function Page({
  params,
}: {
  params: { channelName: string };
}) {
  const Dashboard = dynamic(() => import("@/components/Dashboard"), {
    ssr: false,
  });
  const supabase = createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();

  return (
    <main className="flex w-full h-full flex-col">
      <Dashboard
        appId={process.env.PUBLIC_AGORA_APP_ID!}
        channelName={params.channelName}
        userId={user?.id ?? ""}
      ></Dashboard>
    </main>
  );
}

```

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