├── Images
├── Image
├── GSSoC-Ext.png
├── hacktober.png
├── hacktoberfest.png
├── 212284100-561aa473-3905-4a80-b561-0d28506553ee.gif
└── 329829127-e79eb6de-81b1-4ffb-b6ed-f018bb977e88.png
├── client
├── src
│ ├── pages
│ │ ├── Home
│ │ │ ├── Home.css
│ │ │ └── HomePage.tsx
│ │ ├── Friends
│ │ │ └── FriendsPage.tsx
│ │ ├── Profile
│ │ │ ├── profile.css
│ │ │ └── AvatarSelectionModal.tsx
│ │ ├── auth
│ │ │ └── firebase.ts
│ │ ├── Onboard
│ │ │ └── (components)
│ │ │ │ └── UsernameAndPictures.tsx
│ │ └── List
│ │ │ └── MovieSearch.tsx
│ ├── components
│ │ ├── custom
│ │ │ ├── MovieCard
│ │ │ │ ├── MovieCard.css
│ │ │ │ └── MovieCardData.ts
│ │ │ ├── ProfileIcon
│ │ │ │ └── ProfileIcon.tsx
│ │ │ ├── MovieCarousel
│ │ │ │ ├── CarouselCard.css
│ │ │ │ └── CarouselCard.tsx
│ │ │ ├── AddToList
│ │ │ │ └── AddToListBtn.tsx
│ │ │ ├── Groups
│ │ │ │ ├── Groups.tsx
│ │ │ │ └── Group.tsx
│ │ │ ├── LazyLoadImage
│ │ │ │ └── LazyImage.tsx
│ │ │ ├── Navbar
│ │ │ │ ├── ThemeController.tsx
│ │ │ │ ├── TopNavbar.tsx
│ │ │ │ ├── Navbar.tsx
│ │ │ │ ├── BottomBar.tsx
│ │ │ │ └── TestNavbar.tsx
│ │ │ ├── Share
│ │ │ │ └── Share.tsx
│ │ │ └── ProfileBento
│ │ │ │ └── ProfileBento.tsx
│ │ ├── ui
│ │ │ ├── Loader.tsx
│ │ │ ├── loading.tsx
│ │ │ ├── label.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── input.tsx
│ │ │ ├── stars.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── tooltip.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── bento-grid.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── accordion.tsx
│ │ │ ├── card.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── pagination.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── dialog.tsx
│ │ │ └── form.tsx
│ │ ├── charts
│ │ │ └── BarChart.tsx
│ │ ├── friend-requests.tsx
│ │ ├── magicui
│ │ │ └── dock.tsx
│ │ └── friends-request.tsx
│ ├── vite-env.d.ts
│ ├── Types
│ │ ├── Director.ts
│ │ ├── Group.ts
│ │ ├── User.ts
│ │ ├── validationSchema.ts
│ │ └── Movie.ts
│ ├── assets
│ │ ├── logo_dark.png
│ │ ├── logo_light.png
│ │ ├── temp_logo.png
│ │ ├── avatars.ts
│ │ └── react.svg
│ ├── lib
│ │ ├── utils.ts
│ │ ├── firebaseConfig.ts
│ │ ├── firebase.ts
│ │ └── notify.ts
│ ├── layouts
│ │ ├── depraceted
│ │ │ ├── unauth-layout.tsx
│ │ │ ├── layout-wrapper.tsx
│ │ │ └── protected-route.tsx
│ │ ├── layout.css
│ │ ├── authenticated-layout.tsx
│ │ └── root-layout.tsx
│ ├── hooks
│ │ ├── useAuth.ts
│ │ └── useTheme.ts
│ ├── services
│ │ ├── tmdbServices.ts
│ │ ├── directorService.ts
│ │ └── journalService.ts
│ ├── main.tsx
│ ├── data
│ │ ├── Groups.ts
│ │ └── Movies.ts
│ ├── router.tsx
│ ├── index.css
│ └── context
│ │ └── AuthContext.tsx
├── bun.lockb
├── vercel.json
├── public
│ ├── favicon.ico
│ ├── logo_dark.png
│ ├── logo_light.png
│ ├── favicon-32x32.png
│ ├── noise.svg
│ ├── vite.svg
│ └── google.svg
├── postcss.config.js
├── .env.example
├── tsconfig.node.json
├── vite.config.ts
├── .gitignore
├── index.html
├── components.json
├── tsconfig.json
├── package.json
└── tailwind.config.js
├── index.ts
├── server
├── .env.example
├── bun.lockb
├── routes
│ ├── statRoutes.ts
│ ├── groupRoutes.ts
│ ├── journalRoutes.ts
│ ├── listRoutes.ts
│ ├── friendRoutes.ts
│ └── userRoutes.ts
├── types
│ └── express
│ │ └── index.d.ts
├── config
│ └── index.ts
├── Dockerfile
├── connections
│ └── connectToDB.ts
├── models
│ ├── movie.ts
│ ├── Stat.ts
│ ├── Director.ts
│ ├── Group.ts
│ ├── Person.ts
│ ├── List.ts
│ └── User.ts
├── controllers
│ ├── groupControllers
│ │ └── getAllGroups.ts
│ └── movieController.ts
├── tsconfig.json
├── middleware
│ └── verifyToken.ts
├── package.json
├── utils
│ └── keepAlive.ts
└── .gitignore
├── bun.lockb
├── prettier.config.mjs
├── tsconfig.json
├── .github
├── workflows
│ ├── greetings.yml
│ ├── auto-label.yml
│ ├── post-pr-thankyou.yml
│ ├── auto_labeler_on_pr.yml
│ ├── check-merge-conflicts.yml
│ ├── restrict_issues.yml
│ └── ci-cd.yaml
└── ISSUE_TEMPLATE
│ └── documentation.yml
├── Dockerfile
├── LICENSE
├── package.json
├── .gitignore
└── eslint.config.mjs
/Images/Image:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/pages/Home/Home.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | console.log("Hello via Bun!");
--------------------------------------------------------------------------------
/client/src/components/custom/MovieCard/MovieCard.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/.env.example:
--------------------------------------------------------------------------------
1 | MONGO_URL=
2 | NODE_ENV=development
--------------------------------------------------------------------------------
/client/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/bun.lockb
--------------------------------------------------------------------------------
/client/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/bun.lockb
--------------------------------------------------------------------------------
/server/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/server/bun.lockb
--------------------------------------------------------------------------------
/Images/GSSoC-Ext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/Images/GSSoC-Ext.png
--------------------------------------------------------------------------------
/Images/hacktober.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/Images/hacktober.png
--------------------------------------------------------------------------------
/client/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
3 | }
--------------------------------------------------------------------------------
/Images/hacktoberfest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/Images/hacktoberfest.png
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/src/Types/Director.ts:
--------------------------------------------------------------------------------
1 | export interface Director {
2 | id: string;
3 | name: string;
4 | }
--------------------------------------------------------------------------------
/client/public/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/public/logo_dark.png
--------------------------------------------------------------------------------
/client/public/logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/public/logo_light.png
--------------------------------------------------------------------------------
/client/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/public/favicon-32x32.png
--------------------------------------------------------------------------------
/client/src/assets/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/src/assets/logo_dark.png
--------------------------------------------------------------------------------
/client/src/assets/logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/src/assets/logo_light.png
--------------------------------------------------------------------------------
/client/src/assets/temp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/client/src/assets/temp_logo.png
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/server/routes/statRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 |
3 | const router = Router();
4 | export {router as statRoutes};
--------------------------------------------------------------------------------
/Images/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/Images/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif
--------------------------------------------------------------------------------
/Images/329829127-e79eb6de-81b1-4ffb-b6ed-f018bb977e88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daccotta-org/daccotta/HEAD/Images/329829127-e79eb6de-81b1-4ffb-b6ed-f018bb977e88.png
--------------------------------------------------------------------------------
/client/.env.example:
--------------------------------------------------------------------------------
1 | VITE_ACCESS_KEY= "your tmdb key"
2 | VITE_API_KEY=
3 | VITE_AUTH_DOMAIN=
4 | VITE_PROJECT_ID=
5 | VITE_STORAGE_BUCKET=
6 | VITE_MESSAGING_SENDER_ID=
7 | VITE_APP_ID=
8 | VITE_API_BASE_URL=
--------------------------------------------------------------------------------
/client/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/client/src/pages/Friends/FriendsPage.tsx:
--------------------------------------------------------------------------------
1 | import { FriendsRequest } from "@/components/friends-request"
2 |
3 | const FriendsPage = () => {
4 | return
5 | }
6 |
7 | export default FriendsPage
8 |
--------------------------------------------------------------------------------
/server/types/express/index.d.ts:
--------------------------------------------------------------------------------
1 | import { DecodedIdToken } from 'firebase-admin/auth';
2 |
3 | declare global {
4 | namespace Express {
5 | interface Request {
6 | user?: DecodedIdToken;
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/server/config/index.ts:
--------------------------------------------------------------------------------
1 | import { config } from "dotenv"
2 | config()
3 |
4 | const PORT = process.env.PORT || 8080
5 | const password = process.env.MONGO_PASSWORD
6 | const MONGO_URL = process.env.MONGO_URL
7 | export { PORT, MONGO_URL }
8 |
--------------------------------------------------------------------------------
/server/routes/groupRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express";
2 | import { getAllGroups } from "../controllers/groupControllers/getAllGroups";
3 |
4 | const router = Router();
5 |
6 | router.get("/",getAllGroups);
7 |
8 | export {router as groupRoutes};
9 |
--------------------------------------------------------------------------------
/client/src/Types/Group.ts:
--------------------------------------------------------------------------------
1 |
2 | import { IconType } from "react-icons";
3 | import { IUser } from "./User";
4 |
5 |
6 | export type IGroup=
7 | {
8 | id:string,
9 | description?:string,
10 | icon:IconType,
11 | name?:string,
12 | members?:IUser[],
13 | }
--------------------------------------------------------------------------------
/client/src/pages/Profile/profile.css:
--------------------------------------------------------------------------------
1 |
2 | .bg-main{
3 | background: linear-gradient(to bottom, rgb(30,32,30) 1%, rgba(0,0,0,0.55) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;
4 | background-blend-mode: multiply,multiply;
5 | }
--------------------------------------------------------------------------------
/client/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/client/src/components/custom/ProfileIcon/ProfileIcon.tsx:
--------------------------------------------------------------------------------
1 | import SimpleDialogDemo from "@/components/ui/dialog_mui"
2 |
3 | const ProfileIcon = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default ProfileIcon
12 |
--------------------------------------------------------------------------------
/client/src/components/custom/MovieCarousel/CarouselCard.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
2 |
3 | .roboto-regular {
4 | font-family: "Roboto", system-ui;
5 | font-weight: 400;
6 | font-style: normal;
7 | }
--------------------------------------------------------------------------------
/client/src/components/ui/Loader.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const Loader: React.FC<{ size?: number }> = ({ size = 24 }) => (
4 |
8 | )
9 |
10 | export default Loader
11 |
--------------------------------------------------------------------------------
/client/public/noise.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/client/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import path from "path"
3 | import react from '@vitejs/plugin-react'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 |
9 | resolve: {
10 | alias: {
11 | "@": path.resolve(__dirname, "./src"),
12 | },
13 | },
14 | })
15 |
16 |
--------------------------------------------------------------------------------
/client/src/lib/firebaseConfig.ts:
--------------------------------------------------------------------------------
1 | export const firebaseConfig = {
2 | apiKey: import.meta.env.VITE_API_KEY,
3 | authDomain: import.meta.env.VITE_AUTH_DOMAIN,
4 | projectId: import.meta.env.VITE_PROJECT_ID,
5 | storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
6 | messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID,
7 | appId: import.meta.env.VITE_APP_ID,
8 | }
9 |
--------------------------------------------------------------------------------
/client/src/layouts/depraceted/unauth-layout.tsx:
--------------------------------------------------------------------------------
1 | // UnauthenticatedLayout.tsx
2 | import React from 'react';
3 | import { Outlet } from 'react-router-dom';
4 |
5 | const UnauthenticatedLayout: React.FC = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | );
12 | };
13 |
14 | export default UnauthenticatedLayout;
15 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | .env
11 | .env.local
12 |
13 | node_modules
14 |
15 | dist-ssr
16 | *.local
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .idea
22 | .DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 |
29 | dist
30 |
31 |
--------------------------------------------------------------------------------
/client/src/components/custom/AddToList/AddToListBtn.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | movie_id: string // movie id
3 | release_date: string
4 | title: string
5 | overview: string
6 | poster_path: string
7 | backdrop_path: string
8 | }
9 | //api/user/uid/list/list_id/movie_id
10 | //
11 | const AddToListBtn = (props: Props) => {
12 | return {props.movie_id}
13 | }
14 | export default AddToListBtn
15 |
--------------------------------------------------------------------------------
/prettier.config.mjs:
--------------------------------------------------------------------------------
1 | // prettier.config.js, .prettierrc.js, prettier.config.mjs, or .prettierrc.mjs
2 |
3 | /**
4 | * @see https://prettier.io/docs/en/configuration.html
5 | * @type {import("prettier").Config}
6 | */
7 | const config = {
8 | trailingComma: "es5",
9 | tabWidth: 4,
10 | semi: false,
11 | doubleQuote : true,
12 | singleQuote: false,
13 | };
14 |
15 | export default config;
16 |
17 |
--------------------------------------------------------------------------------
/client/src/hooks/useAuth.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { AuthContext } from '../context/AuthContext'; // Adjust this path as needed
3 |
4 | export function useAuth() {
5 | const context = useContext(AuthContext);
6 | if (context === undefined) {
7 | throw new Error('useAuth must be used within an AuthProvider');
8 | }
9 | return {
10 | ...context,
11 | isSignedIn: !!context.user
12 | };
13 | }
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | daccotta
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/src/Types/User.ts:
--------------------------------------------------------------------------------
1 | import { Director } from "../pages/Onboard/(components)/TopDirectors";
2 | import { IGroup } from "./Group";
3 |
4 | export type IUser=
5 | {
6 | id:string,
7 | username:string,
8 | profile_image: string,
9 | email:string,
10 | age:number,
11 | onboarded?:boolean,
12 | badges?: string[];
13 | groups?: IGroup[];
14 | lists?:string[],
15 | directors?:Director[],
16 | actors?:string[],
17 |
18 |
19 | }
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM oven/bun:1
2 |
3 | # Set the working directory in the container
4 | WORKDIR /app
5 |
6 | # Copy package.json and package-lock.json (if available)
7 | COPY package*.json ./
8 | COPY bun.lockb ./
9 |
10 | # Install dependencies
11 | RUN bun install --production
12 |
13 | # Copy the rest of your app's source code
14 | COPY . .
15 |
16 | # Expose the port your app runs on
17 | EXPOSE 8080
18 |
19 | # Start the application
20 | CMD ["bun", "main.ts"]
--------------------------------------------------------------------------------
/client/src/components/ui/loading.tsx:
--------------------------------------------------------------------------------
1 | // import * as React from 'react';
2 | // import CircularProgress from '@mui/material/CircularProgress';
3 | // import Box from '@mui/material/Box';
4 |
5 | export default function CircularIndeterminate() {
6 | return (
7 | //
8 | //
9 | //
10 |
11 | );
12 | }
--------------------------------------------------------------------------------
/server/connections/connectToDB.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import { MONGO_URL } from "../config";
3 |
4 | export default async function connectDatabase() {
5 | try {
6 | if (!MONGO_URL) {
7 | throw new Error("mongo url not defined");
8 | }
9 | await mongoose.connect(MONGO_URL);
10 | console.log("MongoDB connected successfully");
11 | } catch (error) {
12 | console.log("Error occured while connecting to MongoDB" + error);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/client/src/lib/firebase.ts:
--------------------------------------------------------------------------------
1 | import { initializeApp } from "firebase/app"
2 | import { getAuth } from "firebase/auth"
3 | import { firebaseConfig } from "./firebaseConfig"
4 | // TODO: Add SDKs for Firebase products that you want to use
5 | // https://firebase.google.com/docs/web/setup#available-libraries
6 |
7 | // Your web app's Firebase configuration
8 |
9 | // Initialize Firebase`
10 | const app = initializeApp(firebaseConfig)
11 | export const auth = getAuth()
12 |
13 | export default app
14 |
--------------------------------------------------------------------------------
/client/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/index.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "magicui": "@/components/magicui"
18 | }
19 | }
--------------------------------------------------------------------------------
/client/src/lib/notify.ts:
--------------------------------------------------------------------------------
1 | // import { toast } from "sonner";
2 |
3 | // const Notify = (type: string, message: string) => {
4 | // switch (type) {
5 | // case "success":
6 | // return toast.success(message);
7 | // case "warning":
8 | // return toast.warning(message);
9 | // case "error":
10 | // return toast.error(message);
11 | // case "info":
12 | // return toast.info(message);
13 | // default:
14 | // return toast(message);
15 | // }
16 | // };
17 |
18 | // export default Notify;
19 |
--------------------------------------------------------------------------------
/server/models/movie.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "mongoose"
2 |
3 | export interface MovieInList {
4 | movie_id: string
5 | title: string
6 | poster_path: string
7 | release_date?: string
8 | genre_ids?: number[]
9 | }
10 |
11 | export const movieInListSchema = new Schema({
12 | movie_id: { type: String, required: true },
13 | title: { type: String, required: true },
14 | poster_path: { type: String, required: true },
15 | release_date: { type: String },
16 | genre_ids: [{ type: Number }],
17 | })
18 |
--------------------------------------------------------------------------------
/client/src/pages/auth/firebase.ts:
--------------------------------------------------------------------------------
1 | import { initializeApp } from "firebase/app"
2 | import { getAuth } from "firebase/auth"
3 | import { getFirestore } from "firebase/firestore"
4 | import { firebaseConfig } from "../../lib/firebaseConfig"
5 | // TODO: Add SDKs for Firebase products that you want to use
6 | // https://firebase.google.com/docs/web/setup#available-libraries
7 |
8 | // Your web app's Firebase configuration
9 |
10 | // Initialize Firebase`
11 | const app = initializeApp(firebaseConfig)
12 | export const auth = getAuth()
13 | export const db = getFirestore(app)
14 | export default app
15 |
--------------------------------------------------------------------------------
/server/models/Stat.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, Document } from 'mongoose';
2 |
3 |
4 | interface Stats extends Document {
5 |
6 | role: string;
7 | user?: Schema.Types.ObjectId,
8 | group?: Schema.Types.ObjectId,
9 |
10 |
11 | }
12 |
13 | const statSchema = new Schema({
14 |
15 | role: {
16 | type: String,
17 | enum:["user","group"]
18 |
19 | },
20 |
21 | user: { type: Schema.Types.ObjectId, ref: 'User' },
22 | group: { type: Schema.Types.ObjectId, ref: 'Group' },
23 |
24 | });
25 |
26 | const Stat = model('Stat', statSchema);
27 |
28 | export default Stat;
--------------------------------------------------------------------------------
/client/src/components/custom/Groups/Groups.tsx:
--------------------------------------------------------------------------------
1 | import { IGroup } from "../../../Types/Group"
2 | import Group from "./Group"
3 | type GroupListProps = {
4 | groups: IGroup[]
5 | }
6 |
7 | const Groups = ({ groups }: GroupListProps) => {
8 | return (
9 |
10 | {groups.map(({ id, name, icon }) => (
11 |
12 | ))}
13 |
14 | )
15 | }
16 |
17 | export default Groups
18 |
--------------------------------------------------------------------------------
/client/src/components/custom/LazyLoadImage/LazyImage.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react"
2 | import { LazyLoadImage } from "react-lazy-load-image-component"
3 | import "react-lazy-load-image-component/src/effects/blur.css"
4 |
5 | interface ImageProps {
6 | src: string
7 | className: string
8 | alt: string
9 | }
10 | const LazyImage: FC = ({ src, className, alt = "image" }) => {
11 | return (
12 |
18 | )
19 | }
20 |
21 | export default LazyImage
22 |
--------------------------------------------------------------------------------
/client/src/layouts/layout.css:
--------------------------------------------------------------------------------
1 | /* .bg-main{
2 |
3 | background: linear-gradient(to bottom, rgb(0, 0, 0) 75%, rgba(0, 0, 0, 0.55) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;
4 | background-blend-mode: multiply,multiply;
5 | }
6 | .bg-bar{
7 |
8 | background: linear-gradient(to bottom, rgb(30,32,30) 1%, rgba(0,0,0,0.55) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;
9 | background-blend-mode: multiply,multiply;
10 | opacity: 0.8;
11 | } */
12 |
13 | .bg-background-light{
14 |
15 | background-color: rgb(14, 14, 14);
16 | }
--------------------------------------------------------------------------------
/server/models/Director.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose"
2 | import { Schema, model, Document } from "mongoose"
3 | import Person, { personSchema } from "./Person"
4 |
5 | export interface Directors extends Document {
6 | directors_id: string
7 | names: Person[]
8 | }
9 |
10 | export const directorSchema = new Schema({
11 | directors_id: {
12 | type: String,
13 | required: true,
14 | unique: true,
15 | default: () => new mongoose.Types.ObjectId().toString(),
16 | },
17 | names: [personSchema],
18 | })
19 |
20 | const Director = model("Director", directorSchema)
21 |
22 | export default Director
23 |
--------------------------------------------------------------------------------
/client/src/components/custom/Groups/Group.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { IGroup } from "../../../Types/Group"
3 | import { motion } from "framer-motion"
4 | import { Link } from "react-router-dom"
5 | const Group: React.FC = ({ id, icon, name }) => {
6 | const Icon = icon
7 | {
8 | return (
9 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 | }
20 |
21 | export default Group
22 |
--------------------------------------------------------------------------------
/server/models/Group.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { Schema, model, Document } from 'mongoose';
2 |
3 | interface Groups {
4 | name: string;
5 | members: Schema.Types.ObjectId[];
6 | lists: Schema.Types.ObjectId[];
7 | group_icon: string;
8 | stats: Schema.Types.ObjectId[];
9 | }
10 |
11 | const groupSchema = new Schema({
12 |
13 | name: { type: String, required: true },
14 | members: [{ type: Schema.Types.ObjectId, ref: 'User' }],
15 | lists: [{ type: Schema.Types.ObjectId, ref: 'List' }],
16 | group_icon: { type: String },
17 | stats: [{ type: Schema.Types.ObjectId, ref: 'Stat' }]
18 | });
19 |
20 | const Group = model('Group', groupSchema);
21 |
22 | export default Group;
--------------------------------------------------------------------------------
/server/controllers/groupControllers/getAllGroups.ts:
--------------------------------------------------------------------------------
1 | // controllers/groups/getAllGroups.ts
2 | import { type Request, type Response } from 'express';
3 | import User from '../../models/User';
4 |
5 | export const getAllGroups = async (req: Request, res: Response) => {
6 | try {
7 | const userId = req.body._id; // Assuming you have user authentication middleware
8 | const user = await User.findById(userId).populate('groups');
9 |
10 | if (!user) {
11 | return res.status(404).json({ message: 'User not found' });
12 | }
13 |
14 | res.status(200).json(user.groups);
15 | } catch (error) {
16 | res.status(500).json({ message: 'Error fetching groups', error });
17 | }
18 | };
--------------------------------------------------------------------------------
/client/src/services/tmdbServices.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 |
4 | const BASE_URL = "https://api.themoviedb.org/3";
5 | const TMDB_TOKEN = import.meta.env.VITE_ACCESS_KEY;
6 |
7 | const headers = {
8 | Authorization: "Bearer " + TMDB_TOKEN,
9 | };
10 |
11 | // Define a type for the params
12 | type TMDBParams = {
13 | [key: string]: string | number | boolean | undefined;
14 | };
15 |
16 | export const fetchDataFromApi = async (url: string, params?: TMDBParams) => {
17 | try {
18 | console.log(TMDB_TOKEN);
19 |
20 | const { data } = await axios.get(BASE_URL + url, {
21 | headers,
22 | params,
23 | });
24 | return data;
25 | } catch (err) {
26 | console.error(err);
27 | throw err;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/greetings.yml:
--------------------------------------------------------------------------------
1 | name: Greetings
2 |
3 | on: [pull_request_target, issues]
4 |
5 | jobs:
6 | greeting:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 | steps:
12 | - uses: actions/first-interaction@v1
13 | with:
14 | repo-token: ${{ secrets.GITHUB_TOKEN }}
15 | issue-message: "👋 Thank you @${{ github.actor }} for raising an issue! We appreciate your effort in helping us improve. Our team will review it shortly. Stay tuned!"
16 | pr-message: " 🎉 Thank you @${{ github.actor }} for your contribution! Your pull request has been submitted successfully. A maintainer will review it as soon as possible. We appreciate your support in making this project better"
17 |
--------------------------------------------------------------------------------
/server/models/Person.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { Schema, model, Document } from "mongoose"
2 |
3 | interface Person {
4 | id: number
5 | name: string
6 | profile_path: string | null
7 | known_for_department: "Acting" | "Directing"
8 | }
9 | export const personSchema = new Schema(
10 | {
11 | id: { type: Number, required: true },
12 | name: { type: String, required: true },
13 | profile_path: { type: String, default: null },
14 | known_for_department: {
15 | type: String,
16 | enum: ["Acting", "Directing"],
17 | required: true,
18 | },
19 | },
20 | { timestamps: true }
21 | )
22 |
23 | const Person = model("Person", personSchema)
24 |
25 | export default Person
26 |
--------------------------------------------------------------------------------
/server/routes/journalRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express"
2 | import { verifyToken } from "../middleware/verifyToken"
3 | import {
4 | addJournalEntry,
5 | getJournalEntries,
6 | updateJournalEntry,
7 | deleteJournalEntry,
8 | getFriendJournalEntries,
9 | } from "../controllers/journalController"
10 |
11 | const router = Router()
12 |
13 | console.log("I am here in journalRoutes")
14 |
15 | router.post("/add", verifyToken, addJournalEntry)
16 | router.get("/entries", verifyToken, getJournalEntries)
17 | router.get("/entries/:userName", verifyToken, getFriendJournalEntries)
18 | router.put("/update/:entryId", verifyToken, updateJournalEntry)
19 | router.delete("/delete/:entryId", verifyToken, deleteJournalEntry)
20 |
21 | export { router as journalRoutes }
22 |
--------------------------------------------------------------------------------
/client/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | // hooks/useTheme.ts
2 | import { useState, useEffect } from 'react';
3 |
4 | const themes = ['retro', 'cyberpunk', 'valentine','Ashu'];
5 |
6 | export const useTheme = () => {
7 | const [theme, setTheme] = useState('Ashu');
8 |
9 | useEffect(() => {
10 | const savedTheme = localStorage.getItem('theme') || 'Ashu';
11 | setTheme(savedTheme);
12 | document.documentElement.setAttribute('data-theme', savedTheme);
13 | }, []);
14 |
15 | const changeTheme = (newTheme: string) => {
16 | if (themes.includes(newTheme)) {
17 | setTheme(newTheme);
18 | localStorage.setItem('theme', newTheme);
19 | document.documentElement.setAttribute('data-theme', newTheme);
20 | }
21 | };
22 |
23 | return { theme, changeTheme, themes };
24 | };
--------------------------------------------------------------------------------
/server/middleware/verifyToken.ts:
--------------------------------------------------------------------------------
1 | import type{ Request, Response, NextFunction } from 'express';
2 | import admin from 'firebase-admin';
3 |
4 | export const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
5 | try {
6 | const authHeader = req.headers.authorization;
7 | const idToken = authHeader && authHeader.split('Bearer ')[1];
8 |
9 | if (!idToken) {
10 | return res.status(401).json({ error: 'No token provided' });
11 | }
12 | const decodedToken = await admin.auth().verifyIdToken(idToken);
13 | // Attach the decoded token to the request object
14 | req.user = decodedToken;
15 |
16 | next();
17 | } catch (error) {
18 | console.error('Error verifying token:', error);
19 | res.status(403).json({ error: 'Unauthorized' });
20 | }
21 | };
--------------------------------------------------------------------------------
/client/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef &
14 | VariantProps
15 | >(({ className, ...props }, ref) => (
16 |
21 | ))
22 | Label.displayName = LabelPrimitive.Root.displayName
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/server/routes/listRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express"
2 | import { verifyToken } from "../middleware/verifyToken"
3 | import { createList, getMoveList, removeList } from "../controllers/listController"
4 | import { addMovie, addMovieInList, removeMovie } from "../controllers/movieController"
5 |
6 | const router = Router()
7 |
8 | router.get("/:uid", verifyToken, getMoveList)
9 | router.post("/create", verifyToken, createList)
10 | //delete list by id
11 | router.delete("/:listId/remove-list", verifyToken, removeList);
12 | router.post("/:listId/add-movie", verifyToken, addMovie)
13 | // Add remove-movie endpoint
14 | router.delete("/:listId/remove-movie", verifyToken, removeMovie)
15 | // Add movie to list endpoint
16 | router.post("/:listId/add-movie-in-list", verifyToken, addMovieInList);
17 |
18 | export { router as listRoutes }
19 |
--------------------------------------------------------------------------------
/server/routes/friendRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express"
2 | import { verifyToken } from "../middleware/verifyToken"
3 | import {
4 | acceptRejectRequest,
5 | getAllFriendRequests,
6 | getFriendInfo,
7 | getFriends,
8 | getFriendTopMovies,
9 | removeFriend,
10 | sendRequest
11 | } from "../controllers/friendController"
12 |
13 | const router = Router()
14 |
15 | router.get("/", verifyToken, getFriends);
16 | router.post("/request", verifyToken, sendRequest);
17 | router.post("/respond", verifyToken, acceptRejectRequest);
18 | router.post("/remove", verifyToken, removeFriend);
19 | router.get("/requests",verifyToken, getAllFriendRequests);
20 | router.get("/data/:username", verifyToken, getFriendInfo);
21 | router.get("/top-movies", verifyToken, getFriendTopMovies)
22 |
23 | export { router as friendRoutes }
24 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "baseUrl": ".",
9 | "paths": {
10 | "@/*": [
11 | "./src/*"
12 | ]
13 | },
14 |
15 | /* Bundler mode */
16 | "moduleResolution": "bundler",
17 | "allowImportingTsExtensions": true,
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 |
23 | /* Linting */
24 | "strict": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 | "noFallthroughCasesInSwitch": true
28 | },
29 | "include": ["src", "src/components/custom/Navbar/TopNavbar.tsx"],
30 | "references": [{ "path": "./tsconfig.node.json" }]
31 | }
32 |
--------------------------------------------------------------------------------
/client/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/client/src/layouts/depraceted/layout-wrapper.tsx:
--------------------------------------------------------------------------------
1 | // LayoutWrapper.tsx
2 | import React from 'react';
3 | import { Navigate, Outlet } from 'react-router-dom';
4 | import { useAuth } from '../../hooks/useAuth';
5 |
6 | const LayoutWrapper: React.FC = () => {
7 | const { isLoaded, user, isOnboarded } = useAuth();
8 |
9 |
10 | if (!isLoaded) {
11 | return (
12 |
15 | );
16 | }
17 |
18 | if (!user) {
19 | return ;
20 | }
21 |
22 | if (user && !isOnboarded) {
23 | return ;
24 | }
25 |
26 | return ;
27 | };
28 |
29 | export default LayoutWrapper;
30 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "module": "main.ts",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "bun --watch main.ts",
7 | "build": "bun main.ts",
8 | "start": "bun main.ts"
9 | },
10 | "devDependencies": {
11 | "@types/bun": "latest",
12 | "@types/uuid": "^10.0.0"
13 | },
14 | "peerDependencies": {
15 | "typescript": "^5.0.0"
16 | },
17 | "dependencies": {
18 | "@types/cors": "^2.8.17",
19 | "@types/express": "^4.17.21",
20 | "@types/mongodb": "^4.0.7",
21 | "@types/mongoose": "^5.11.97",
22 | "axios": "^1.7.7",
23 | "cors": "^2.8.5",
24 | "dotenv": "^16.4.5",
25 | "express": "^4.19.2",
26 | "firebase-admin": "^12.3.0",
27 | "mongodb": "^6.8.0",
28 | "mongoose": "^8.5.1",
29 | "server": ".",
30 | "uuid": "^10.0.0",
31 | "uuid4": "^2.0.3",
32 | "uuidv4": "^6.2.13"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/auto-label.yml:
--------------------------------------------------------------------------------
1 | name: Auto Label Issues and PRs
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 | pull_request_target: # Correct indentation here
7 | types: [opened]
8 |
9 | jobs:
10 | add-labels:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Add labels to new issues
15 | if: github.event_name == 'issues'
16 | uses: actions-ecosystem/action-add-labels@v1
17 | with:
18 | github_token: ${{ secrets.PAT_TOKEN }} # Use PAT_TOKEN for issues
19 | labels: |
20 | gssoc-ext
21 | hacktoberfest-accepted
22 |
23 | - name: Add labels to new pull requests
24 | if: github.event_name == 'pull_request_target'
25 | uses: actions-ecosystem/action-add-labels@v1
26 | with:
27 | github_token: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN for PRs
28 | labels: |
29 | gssoc-ext
30 | hacktoberfest-accepted
--------------------------------------------------------------------------------
/client/src/Types/validationSchema.ts:
--------------------------------------------------------------------------------
1 |
2 | import { z } from 'zod';
3 |
4 | export const signUpSchema = z.object({
5 | email: z.string().email({ message: "Invalid email address" }),
6 | password: z.string().min(8, { message: "Password must be at least 8 characters" }),
7 | username: z.string()
8 | .min(3, 'Username must be at least 3 characters long')
9 | .max(25, 'Username must not exceed 25 characters')
10 | .regex(/^[a-zA-Z][a-zA-Z0-9]+$/, 'Username must start with a letter and contain only letters and numbers'),
11 | confirmPassword: z.string().min(8, { message: "Password must be at least 8 characters" }),
12 | age: z.number().min(0, { message: "Age cannot be negative" }).max(100, { message: "Age cannot be more than 100" }).optional(),
13 | }).refine(data => data.password === data.confirmPassword, {
14 | message: "Passwords don't match",
15 | path: ["confirmPassword"], // path of error
16 | });
17 |
18 |
19 | export type SignUpFormData = z.infer;
20 |
21 |
--------------------------------------------------------------------------------
/client/src/Types/Movie.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod"
2 |
3 | export interface TMDBMovie {
4 | id: string
5 | title?: string
6 | poster_path?: string
7 | release_date?: string
8 | backdrop_path?: string
9 | genre_ids?: number[]
10 | }
11 | export interface SimpleMovie {
12 | movie_id: string
13 | id: string
14 | title?: string | undefined
15 | poster_path?: string | undefined
16 | release_date: string
17 | overview?: string
18 | backdrop_path?: string
19 | friend?: string
20 | genre_ids?: number[]
21 | }
22 |
23 | export interface Movie {
24 | id?: string
25 | title?: string
26 | poster_path?: string
27 | release_date?: string
28 | genre_ids?: number[]
29 | }
30 |
31 | export const movieSchema = z.object({
32 | id: z.string().optional(),
33 | title: z.string().optional(),
34 | poster_path: z.string().optional(),
35 | release_date: z.string().optional(),
36 | genre_ids: z.array(z.number()).optional(),
37 | })
38 |
--------------------------------------------------------------------------------
/server/utils/keepAlive.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios" // Import axios
2 |
3 | export function keepAlive() {
4 | setInterval(async () => {
5 | try {
6 | const url = `https://daccotta-back.onrender.com/ping` // Change this to your deployed server's URL when deploying
7 |
8 | const response = await axios.get(url)
9 |
10 | // Log the response
11 | console.log(`Ping successful: ${response.data}`)
12 | } catch (error) {
13 | if (axios.isAxiosError(error)) {
14 | // Handle Axios-specific errors
15 | console.error("Axios error:", error.message)
16 | } else if (error instanceof Error) {
17 | // Handle general errors
18 | console.error("Error in keepAlive function:", error.message)
19 | } else {
20 | console.error("Unknown error in keepAlive function")
21 | }
22 | }
23 | }, 840000) // 14 minutes in milliseconds
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/layouts/depraceted/protected-route.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from "react"
2 | import { Outlet, Navigate } from "react-router-dom"
3 | import { useAuth } from "../../hooks/useAuth"
4 |
5 | interface ProtectedRouteProps {
6 | children?: ReactNode
7 | }
8 |
9 | const ProtectedRoute: React.FC = ({ children }) => {
10 | const { isLoaded, user, isOnboarded } = useAuth()
11 |
12 | if (!isLoaded) {
13 | return (
14 |
17 | )
18 | }
19 |
20 | if (user && !isOnboarded && window.location.pathname !== "/onboard") {
21 | return
22 | }
23 |
24 | return children ? <>{children}> :
25 | }
26 |
27 | export default ProtectedRoute
28 |
--------------------------------------------------------------------------------
/client/src/assets/avatars.ts:
--------------------------------------------------------------------------------
1 | type avatar =
2 | {
3 | id : string,
4 |
5 | profile: string,
6 |
7 |
8 | }
9 | export const avatars:avatar[] =
10 | [
11 | {
12 | id:"girl1",
13 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_1.png"
14 |
15 | },
16 | {
17 | id:"girl2",
18 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_2.png"
19 |
20 | },
21 | {
22 | id:"girl3",
23 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_4.png"
24 |
25 | },
26 | {
27 | id:"boy1",
28 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_5.png"
29 |
30 | },
31 | {
32 | id:"boy2",
33 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_6.png"
34 |
35 | },
36 | {
37 | id:"boy3",
38 | profile:"https://cdn.jsdelivr.net/gh/alohe/avatars/png/vibrent_7.png"
39 |
40 | },
41 |
42 |
43 | ]
--------------------------------------------------------------------------------
/client/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Bun image
2 | FROM oven/bun:1
3 |
4 | # Set working directory
5 | WORKDIR /app
6 |
7 | # Copy package.json and package-lock.json (if available)
8 | COPY package*.json ./
9 |
10 | # Install dependencies
11 | RUN bun install
12 |
13 | # Copy the rest of your app's source code
14 | COPY . .
15 | RUN echo "VITE_ACCESS_KEY=$VITE_ACCESS_KEY" >> ./client/.env
16 | RUN echo "VITE_ACCESS_TOKEN_SECRET=$VITE_ACCESS_TOKEN_SECRET" >> ./client/.env
17 | RUN echo "VITE_TMDB_API=$VITE_TMDB_API" >> ./client/.env
18 | RUN echo "VITE_API_KEY=$VITE_API_KEY" >> ./client/.env
19 | RUN echo "VITE_AUTH_DOMAIN=$VITE_AUTH_DOMAIN" >> ./client/.env
20 | RUN echo "VITE_PROJECT_ID=$VITE_PROJECT_ID" >> ./client/.env
21 | RUN echo "VITE_STORAGE_BUCKET=$VITE_STORAGE_BUCKET" >> ./client/.env
22 | RUN echo "VITE_MESSAGING_SENDER_ID=$VITE_MESSAGING_SENDER_ID" >> ./client/.env
23 | RUN echo "VITE_APP_ID=$VITE_APP_ID" >> ./client/.env
24 | # Build your app
25 | RUN bun run build
26 |
27 | # Expose the port your app runs on
28 | EXPOSE 8080
29 |
30 | # Start the app
31 | CMD [ "bun", "run", "start" ]
--------------------------------------------------------------------------------
/client/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4 | import { RouterProvider } from 'react-router-dom';
5 | import { ToastContainer } from 'react-toastify';
6 | import 'react-toastify/dist/ReactToastify.css'; // Import the CSS for react-toastify
7 | import './index.css';
8 | import { router } from './router'; // Assuming you have this in a separate file
9 |
10 | const queryClient = new QueryClient();
11 |
12 | ReactDOM.createRoot(document.getElementById('root')!).render(
13 |
14 |
15 |
16 |
28 |
29 |
30 | );
31 |
--------------------------------------------------------------------------------
/.github/workflows/post-pr-thankyou.yml:
--------------------------------------------------------------------------------
1 | name: Post-PR Merge Thank You
2 |
3 | on:
4 | pull_request_target:
5 | types: [closed] # Trigger when a PR is closed
6 |
7 | permissions:
8 | issues: write
9 | pull-requests: write
10 |
11 | jobs:
12 | post_merge_message:
13 | if: github.event.pull_request.merged == true # Only run if the PR was merged
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Post thank you message
18 | uses: actions/github-script@v7
19 | with:
20 | github-token: ${{ secrets.GITHUB_TOKEN }} # Ensure token is used
21 | script: |
22 | const prNumber = context.payload.pull_request.number;
23 | const owner = context.repo.owner;
24 | const repo = context.repo.repo;
25 |
26 | // Post a thank you message upon PR merge
27 | await github.rest.issues.createComment({
28 | owner: owner,
29 | repo: repo,
30 | issue_number: prNumber,
31 | body: `🎉🎉 Thank you for your contribution! Your PR #${prNumber} has been merged! 🎉🎉`
32 | });
--------------------------------------------------------------------------------
/client/src/pages/Home/HomePage.tsx:
--------------------------------------------------------------------------------
1 | import MovieList from "@/components/custom/MovieCard/Carousel"
2 | import MovieCarousel from "@/components/custom/MovieCarousel/MovieCarousel"
3 | import ProfileIcon from "@/components/custom/ProfileIcon/ProfileIcon"
4 | const HomePage = () => {
5 | return (
6 | <>
7 |
8 |
9 |
12 |
13 |
14 | {" "}
15 |
16 |
21 |
22 |
23 | >
24 | )
25 | }
26 |
27 | export default HomePage
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 daccotta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/routes/userRoutes.ts:
--------------------------------------------------------------------------------
1 | import { Router } from "express"
2 | import { verifyToken } from "../middleware/verifyToken"
3 | import {
4 | checkEmailExists,
5 | checkOnboardedStatus,
6 | checkUsernameAvailability,
7 | completeOnboarding,
8 | getOtherUserData,
9 | getPersonalUserData,
10 | searchUsers,
11 | updateProfileImage,
12 | updateUserProfile,
13 | } from "../controllers/userController"
14 |
15 | const router = Router()
16 |
17 | // Middleware to verify token
18 |
19 | router.post("/check-email", checkEmailExists)
20 | router.get("/:uid", verifyToken, getPersonalUserData)
21 | router.get("/:uid/other", verifyToken, getOtherUserData)
22 | router.put("/:uid/profile", verifyToken, updateUserProfile);
23 | router.get("/:uid/onboarded", verifyToken, checkOnboardedStatus);
24 | router.post("/:uid/complete-onboarding", verifyToken, completeOnboarding);
25 |
26 | //Route to check unique username
27 | router.get("/check-username/:userName", checkUsernameAvailability)
28 | router.get("/:uid/search", verifyToken, searchUsers)
29 | router.put("/:userId/update-profile-image", verifyToken, updateProfileImage);
30 |
31 | export { router as userRoutes }
32 |
--------------------------------------------------------------------------------
/client/src/components/ui/stars.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface StarsProps {
4 | rating: number | null; // Current rating (can be null)
5 | onRatingChange: (rating: number) => void; // Function to update rating
6 | }
7 |
8 | const Stars: React.FC = ({ rating, onRatingChange }) => {
9 | const stars = [1, 2, 3, 4, 5];
10 |
11 | return (
12 |
13 | {stars.map((star) => (
14 |
onRatingChange(star)}
17 | xmlns="http://www.w3.org/2000/svg"
18 | className={`w-6 h-6 cursor-pointer ${rating !== null && rating >= star ? 'text-yellow-500' : 'text-gray-400'}`}
19 | fill="currentColor"
20 | viewBox="0 0 20 20"
21 | >
22 |
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | Stars.displayName = 'Stars';
30 |
31 | export default Stars;
32 |
--------------------------------------------------------------------------------
/.github/workflows/auto_labeler_on_pr.yml:
--------------------------------------------------------------------------------
1 | name: Auto Label PR
2 |
3 | on:
4 | pull_request:
5 | types: [opened, reopened, edited]
6 |
7 | jobs:
8 | label_pr:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | pull-requests: write
12 | steps:
13 | - name: Label PR
14 | uses: actions/github-script@v6
15 | with:
16 | github-token: ${{ secrets.GITHUB_TOKEN }}
17 | script: |
18 | const pullRequest = context.payload.pull_request;
19 |
20 | // Add labels to the PR
21 | await github.rest.issues.addLabels({
22 | owner: context.repo.owner,
23 | repo: context.repo.repo,
24 | issue_number: pullRequest.number,
25 | labels: ['gssoc-ext', 'hacktoberfest-accepted', 'hacktoberfest']
26 | });
27 |
28 | const addLabel = async (label) => {
29 | await github.rest.issues.addLabels({
30 | owner: context.repo.owner,
31 | repo: context.repo.repo,
32 | issue_number: pullRequest.number,
33 | labels: [label]
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/client/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3 | import { Check } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
22 |
23 |
24 |
25 | ))
26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
27 |
28 | export { Checkbox }
29 |
--------------------------------------------------------------------------------
/client/src/data/Groups.ts:
--------------------------------------------------------------------------------
1 | import { FaCircle } from "react-icons/fa6";
2 | import { IGroup } from "../Types/Group";
3 |
4 | export const groups:IGroup[] = [
5 | {id:"1",
6 | name:"group1",
7 | icon: FaCircle,
8 | members:[],
9 |
10 | },
11 | {id:"2",
12 | name:"group2",
13 | icon: FaCircle,
14 | members:[],
15 |
16 | },
17 | {id:"3",
18 | name:"group3",
19 | icon: FaCircle,
20 | members:[],
21 |
22 | },
23 | {id:"4",
24 | name:"group4",
25 | icon: FaCircle,
26 | members:[],
27 |
28 | },
29 | {id:"5",
30 | name:"group5",
31 | icon: FaCircle,
32 | members:[],
33 |
34 | },
35 | {id:"6",
36 | name:"group1",
37 | icon: FaCircle,
38 | members:[],
39 |
40 | },
41 | {id:"7",
42 | name:"group1",
43 | icon: FaCircle,
44 | members:[],
45 |
46 | },
47 | {id:"8",
48 | name:"group1",
49 | icon: FaCircle,
50 | members:[],
51 |
52 | },
53 | {id:"9",
54 | name:"group1",
55 | icon: FaCircle,
56 | members:[],
57 |
58 | },
59 | {id:"10",
60 | name:"group1",
61 | icon: FaCircle,
62 | members:[],
63 |
64 | },
65 | ]
--------------------------------------------------------------------------------
/client/src/layouts/authenticated-layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Outlet } from "react-router-dom"
3 |
4 | import "./layout.css"
5 | //import { DockDemo } from "@/components/ui/DockBar"
6 | import Navbar from "@/components/custom/Navbar/TestNavbar"
7 |
8 | const AuthenticatedLayout: React.FC = () => {
9 | return (
10 |
11 |
12 | {/* Sidebar (Navbar, Groups, Bottom) */}
13 |
14 |
15 |
16 | {/* Main Content */}
17 |
18 |
19 | {/*
20 |
21 |
*/}
22 |
23 |
24 |
25 | )
26 | }
27 |
28 | export default AuthenticatedLayout
29 |
--------------------------------------------------------------------------------
/client/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SwitchPrimitives from "@radix-ui/react-switch"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ))
25 | Switch.displayName = SwitchPrimitives.Root.displayName
26 |
27 | export { Switch }
28 |
--------------------------------------------------------------------------------
/.github/workflows/check-merge-conflicts.yml:
--------------------------------------------------------------------------------
1 | name: Merge Conflict Checker
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 |
7 | permissions:
8 | pull-requests: write
9 | contents: read
10 |
11 | jobs:
12 | check_merge_conflicts:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout repository
16 | uses: actions/checkout@v3
17 |
18 | - name: Check for merge conflicts
19 | run: |
20 | git fetch origin
21 | if git merge-base --is-ancestor HEAD origin/main; then
22 | echo "No merge conflicts."
23 | else
24 | echo "Merge conflicts detected."
25 | exit 1
26 | fi
27 | continue-on-error: true
28 |
29 | - name: Post comment if there are merge conflicts
30 | if: failure()
31 | uses: actions/github-script@v6
32 | with:
33 | script: |
34 | github.rest.issues.createComment({
35 | issue_number: context.payload.pull_request.number,
36 | owner: context.repo.owner,
37 | repo: context.repo.repo,
38 | body: '⚠️ This branch has conflicts that must be resolved. Please resolve the merge conflicts before proceeding.'
39 | })
--------------------------------------------------------------------------------
/client/src/components/custom/MovieCard/MovieCardData.ts:
--------------------------------------------------------------------------------
1 | export interface Movie {
2 | id: string
3 | title: string
4 | poster_path: string
5 | }
6 |
7 | export const sampleMovies: Movie[] = [
8 | {
9 | id: "1",
10 | title: "Bigg Boss",
11 | poster_path: "/hr0L2aueqlP2BYUblTTjmtn0hw4.jpg",
12 | },
13 | {
14 | id: "2",
15 | title: "Spider-Man",
16 | poster_path: "/gh4cZbhZxyTbgxQPxD0dOudNPTn.jpg",
17 | },
18 | {
19 | id: "3",
20 | title: "Spider-Man: No Way Home",
21 | poster_path: "/1g0dhYtq4irTY1GPXvft6k4YLjm.jpg",
22 | },
23 | {
24 | id: "4",
25 | title: "Iron Man",
26 | poster_path: "/78lPtwv72eTNqFW9COBYI0dWDJa.jpg",
27 | },
28 | {
29 | id: "1",
30 | title: "Bigg Boss",
31 | poster_path: "/hr0L2aueqlP2BYUblTTjmtn0hw4.jpg",
32 | },
33 | {
34 | id: "2",
35 | title: "Spider-Man",
36 | poster_path: "/gh4cZbhZxyTbgxQPxD0dOudNPTn.jpg",
37 | },
38 | {
39 | id: "3",
40 | title: "Spider-Man: No Way Home",
41 | poster_path: "/1g0dhYtq4irTY1GPXvft6k4YLjm.jpg",
42 | },
43 | {
44 | id: "4",
45 | title: "Iron Man",
46 | poster_path: "/78lPtwv72eTNqFW9COBYI0dWDJa.jpg",
47 | },
48 | ]
49 |
--------------------------------------------------------------------------------
/client/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | // import * as React from "react"
2 | // import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | // import { cn } from "@/lib/utils"
5 |
6 | // const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | // const Tooltip = TooltipPrimitive.Root
9 |
10 | // const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | // const TooltipContent = React.forwardRef<
13 | // React.ElementRef,
14 | // React.ComponentPropsWithoutRef
15 | // >(({ className, sideOffset = 4, ...props }, ref) => (
16 | //
25 | // ))
26 | // TooltipContent.displayName = TooltipPrimitive.Content.displayName
27 |
28 | // export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
29 |
--------------------------------------------------------------------------------
/client/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14 |
15 |
25 |
26 | ))
27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
28 |
29 | export { Popover, PopoverTrigger, PopoverContent }
30 |
--------------------------------------------------------------------------------
/client/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@fontsource/ibm-plex-mono": "^5.0.14",
4 | "@fontsource/lato": "^5.0.22",
5 | "@fontsource/matemasie": "^5.0.1",
6 | "@fontsource/montserrat": "^5.0.19",
7 | "@fontsource/noto-sans": "^5.0.22",
8 | "@fontsource/roboto": "^5.0.14",
9 | "daccotta": ".",
10 | "eslint": "^9.9.1",
11 | "react-loading-skeleton": "^3.5.0",
12 | "react-toastify": "^10.0.6"
13 | },
14 | "scripts": {
15 | "start:client": "cd client && bunx --bun vite",
16 | "start:server": " cd server && bun --watch main.ts",
17 | "start:all": "concurrently \"bun start:client\" \"bun start:server\"",
18 | "lint": "eslint 'client/**/*.{ts,tsx,js,jsx}' 'server/**/*.{ts,js}'",
19 | "format": "prettier --write 'client/**/*.{ts,tsx,js,jsx,json,css,md}' 'server/**/*.{ts,js,json,css,md}'",
20 | "build": "cd client && bun install && bunx --bun vite build",
21 | "start": "cd server && bun install && bun main.ts"
22 | },
23 | "devDependencies": {
24 | "@eslint/js": "^9.9.1",
25 | "@types/bun": "latest",
26 | "concurrently": "^8.2.2",
27 | "eslint-config-prettier": "^9.1.0",
28 | "eslint-plugin-prettier": "^5.2.1",
29 | "eslint-plugin-react": "^7.35.2",
30 | "globals": "^15.9.0",
31 | "prettier": "^3.3.3",
32 | "typescript-eslint": "^8.4.0"
33 | },
34 | "name": "daccotta",
35 | "module": "index.ts",
36 | "type": "module",
37 | "peerDependencies": {
38 | "typescript": "^5.5.4"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/src/components/ui/bento-grid.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | export const BentoGrid = ({
4 | className,
5 | children,
6 | }: {
7 | className?: string;
8 | children?: React.ReactNode;
9 | }) => {
10 | return (
11 |
17 | {children}
18 |
19 | );
20 | };
21 |
22 | export const BentoGridItem = ({
23 | className,
24 | title,
25 | description,
26 | header,
27 | icon,
28 | }: {
29 | className?: string;
30 | title?: string | React.ReactNode;
31 | description?: string | React.ReactNode;
32 | header?: React.ReactNode;
33 | icon?: React.ReactNode;
34 | }) => {
35 | return (
36 |
42 | {header}
43 |
44 | {icon}
45 |
46 | {title}
47 |
48 |
49 | {description}
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/client/src/components/custom/Navbar/ThemeController.tsx:
--------------------------------------------------------------------------------
1 | // components/ThemeController.tsx
2 | import React from "react"
3 | import { useTheme } from "../../../hooks/useTheme"
4 | import { PiPaintBrushBroadFill } from "react-icons/pi"
5 |
6 | const ThemeController: React.FC = () => {
7 | const { theme, changeTheme, themes } = useTheme()
8 |
9 | return (
10 |
14 |
15 |
16 |
17 |
35 |
36 | )
37 | }
38 |
39 | export default ThemeController
40 |
--------------------------------------------------------------------------------
/client/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/client/src/components/custom/Navbar/TopNavbar.tsx:
--------------------------------------------------------------------------------
1 | import { IoHome } from "react-icons/io5"
2 | import { IoSearch } from "react-icons/io5"
3 | import { FaUserFriends } from "react-icons/fa"
4 | import { Link } from "react-router-dom"
5 | import "../../../index.css"
6 |
7 | const TopNavbar = () => {
8 | return (
9 |
10 |
11 |
15 |
16 | Home
17 |
18 |
19 |
20 |
24 |
25 | Search
26 |
27 |
28 |
29 |
33 |
34 | Friends
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default TopNavbar
42 |
--------------------------------------------------------------------------------
/client/src/components/custom/Share/Share.tsx:
--------------------------------------------------------------------------------
1 | import { CopyIcon } from "@radix-ui/react-icons"
2 |
3 | import {
4 | Dialog,
5 | DialogClose,
6 | DialogContent,
7 | DialogDescription,
8 | DialogFooter,
9 | DialogHeader,
10 | DialogTitle,
11 | DialogTrigger,
12 | } from "@/components/ui/dialog"
13 | import { Button } from "@/components/ui/button"
14 |
15 | export function DialogCloseButton() {
16 | return (
17 |
18 |
19 | Share
20 |
21 |
22 |
23 | Share link
24 |
25 | Anyone who has this link will be able to view this.
26 |
27 |
28 |
29 |
30 |
31 | Copy
32 |
33 |
34 |
35 |
36 |
37 |
38 | Close
39 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/client/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ScrollArea = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, children, ...props }, ref) => (
10 |
15 |
16 | {children}
17 |
18 |
19 |
20 |
21 | ))
22 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23 |
24 | const ScrollBar = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, orientation = "vertical", ...props }, ref) => (
28 |
41 |
42 |
43 | ))
44 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
45 |
46 | export { ScrollArea, ScrollBar }
47 |
--------------------------------------------------------------------------------
/client/src/components/custom/Navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react";
2 | import { Link } from "react-router-dom";
3 | import { RiSearch2Fill } from "react-icons/ri";
4 | import { SignedIn, SignedOut, UserButton } from "@clerk/clerk-react";
5 | import { LuList } from "react-icons/lu";
6 | import { CgProfile } from "react-icons/cg";
7 |
8 | import { motion } from "framer-motion";
9 | const Navbar: FC = () => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Navbar;
56 |
--------------------------------------------------------------------------------
/client/src/services/directorService.ts:
--------------------------------------------------------------------------------
1 | import { Director } from "../Types/Director";
2 | import axios from "axios";
3 | import { useQuery } from "@tanstack/react-query";
4 | const mockDirectors: Director[] = [
5 | { id: "1", name: "Christopher Nolan" },
6 | { id: "2", name: "Martin Scorsese" },
7 | { id: "3", name: "Quentin Tarantino" },
8 | { id: "4", name: "Steven Spielberg" },
9 | { id: "5", name: "Alfred Hitchcock" },
10 | ];
11 |
12 | const TMDB_TOKEN = import.meta.env.VITE_ACCESS_KEY;
13 | const fetchPerson = async (
14 | searchTerm: string,
15 | department: "Directing" | "Acting",
16 | ) => {
17 | if (searchTerm.length < 3) return null;
18 |
19 | const url = `https://api.themoviedb.org/3/search/person?query=${encodeURIComponent(searchTerm)}&language=en-US`;
20 |
21 | const { data } = await axios.get(url, {
22 | headers: {
23 | accept: "application/json",
24 | Authorization: `Bearer ${TMDB_TOKEN}`,
25 | },
26 | });
27 |
28 | // Filter the results based on the specified department
29 | const filteredResults = data.results.filter(
30 | (person: any) => person.known_for_department === department,
31 | );
32 | console.log(data.results);
33 |
34 | return { ...data, results: filteredResults };
35 | };
36 |
37 | export const useSearchPerson = (
38 | searchTerm: string,
39 | department: "Directing" | "Acting",
40 | ) => {
41 | return useQuery({
42 | queryKey: ["searchPerson", searchTerm, department],
43 | queryFn: () => fetchPerson(searchTerm, department),
44 | enabled: searchTerm.length >= 3,
45 | });
46 | };
47 |
48 | export const searchDirectors = async (
49 | searchTerm: string,
50 | ): Promise => {
51 | // Simulate API call delay
52 | await new Promise((resolve) => setTimeout(resolve, 500));
53 |
54 | return mockDirectors.filter((director) =>
55 | director.name.toLowerCase().includes(searchTerm.toLowerCase()),
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/client/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | TabsList.displayName = TabsPrimitive.List.displayName
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ))
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ))
51 | TabsContent.displayName = TabsPrimitive.Content.displayName
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent }
54 |
--------------------------------------------------------------------------------
/client/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ))
19 | AccordionItem.displayName = "AccordionItem"
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180",
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ))
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
50 | {children}
51 |
52 | ))
53 |
54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
55 |
56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
57 |
--------------------------------------------------------------------------------
/client/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/client/src/layouts/root-layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react"
2 | import { Navigate, Outlet, useLocation } from "react-router-dom"
3 | import { useAuth } from "../hooks/useAuth"
4 | const RootLayout: React.FC = () => {
5 | const { user, isOnboarded, isLoaded, checkOnboardingStatus } = useAuth()
6 | const location = useLocation()
7 |
8 | useEffect(() => {
9 | if (user && isOnboarded === undefined) {
10 | checkOnboardingStatus()
11 | }
12 | }, [user, isOnboarded, checkOnboardingStatus])
13 |
14 | if (!isLoaded) {
15 | return (
16 |
19 | )
20 | }
21 |
22 | // Paths that don't require authentication
23 | const publicPaths = ["/signin", "/signup"]
24 |
25 | if (!user) {
26 | // If the user is on a public path, render it
27 | if (publicPaths.includes(location.pathname)) {
28 | return
29 | }
30 | // Otherwise, redirect to signup
31 | return
32 | }
33 |
34 | // If onboarding status is still undefined, show loading
35 | if (isOnboarded === undefined) {
36 | return (
37 |
40 | )
41 | }
42 |
43 | if (!isOnboarded) {
44 | if (location.pathname === "/onboard") {
45 | return
46 | }
47 | return
48 | }
49 |
50 | // User is authenticated and onboarded
51 | if (
52 | publicPaths.includes(location.pathname) ||
53 | location.pathname === "/onboard"
54 | ) {
55 | // Redirect to home if trying to access signin/signup/onboard while authenticated and onboarded
56 | return
57 | }
58 |
59 | return
60 | }
61 |
62 | export default RootLayout
63 |
--------------------------------------------------------------------------------
/client/public/google.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/server/models/List.ts:
--------------------------------------------------------------------------------
1 | import mongoose,{ Schema } from 'mongoose';
2 | import { movieInListSchema, type MovieInList } from './movie';
3 |
4 | export interface List extends Document {
5 | list_id: string;
6 | name: string;
7 | list_type: 'user' | 'group';
8 | movies: MovieInList[];
9 | members: {
10 | user_id: string;
11 | is_author: boolean;
12 | }[];
13 | date_created: Date;
14 | description: string;
15 | isPublic: boolean;
16 | }
17 |
18 | const listSchema = new Schema({
19 | list_id: { type: String, required: true, unique: true, default: () => new mongoose.Types.ObjectId().toString() },
20 | name: { type: String, required: true },
21 | list_type: {
22 | type: String,
23 | enum: ['user', 'group'],
24 | required: true,
25 | },
26 | movies: [movieInListSchema],
27 | members: [{
28 | user_id: { type: String, required: true },
29 | is_author: { type: Boolean, required: true },
30 | }],
31 | date_created: { type: Date, default: Date.now },
32 | description: { type: String, required: false },
33 | isPublic: { type: Boolean, required: true }
34 | });
35 |
36 | const ListModel = mongoose.model('List', listSchema);
37 |
38 | export default ListModel;
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | // import mongoose, { Schema, model, Document } from 'mongoose';
57 |
58 | // interface List extends Document {
59 | // list_id: string;
60 | // name: string;
61 | // list_type: 'user' | 'group';
62 | // movies: string[];
63 | // members: {
64 | // user_id: string;
65 | // is_author: boolean;
66 | // }[];
67 | // date_created: Date;
68 | // }
69 |
70 | // const listSchema = new Schema({
71 | // list_id: { type: String, required: true, unique: true, default: () => new mongoose.Types.ObjectId().toString() },
72 | // name: { type: String, required: true },
73 | // list_type: {
74 | // type: String,
75 | // enum: ['user', 'group'],
76 | // required: true,
77 | // },
78 | // movies: [{ type: String, required: true }],
79 | // members: [{
80 | // user_id: { type: String, required: true },
81 | // is_author: { type: Boolean, required: true },
82 | // }],
83 | // date_created: { type: Date, default: Date.now },
84 | // });
85 |
86 | // const List = model('List', listSchema);
87 |
88 | // export default {List, listSchema};
--------------------------------------------------------------------------------
/client/src/components/custom/ProfileBento/ProfileBento.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | IconClipboardCopy,
3 | IconFileBroken,
4 | IconSignature,
5 | IconTableColumn,
6 | } from "@tabler/icons-react"
7 | import { BentoGrid, BentoGridItem } from "@/components/ui/bento-grid"
8 |
9 | export function BentoGridSecondDemo() {
10 | return (
11 |
12 | {items.map((item, i) => (
13 |
21 | ))}
22 |
23 | )
24 | }
25 | const Skeleton = () => (
26 |
27 | )
28 | const items = [
29 | {
30 | title: "The Dawn of Innovation",
31 | description:
32 | "Explore the birth of groundbreaking ideas and inventions.",
33 | header: ,
34 | className: "md:col-span-2",
35 | icon: ,
36 | },
37 | {
38 | title: "The Digital Revolution",
39 | description: "Dive into the transformative power of technology.",
40 | header: ,
41 | className: "md:col-span-1",
42 | icon: ,
43 | },
44 | {
45 | title: "The Art of Design",
46 | description: "Discover the beauty of thoughtful and functional design.",
47 | header: ,
48 | className: "md:col-span-1",
49 | icon: ,
50 | },
51 | {
52 | title: "The Power of Communication",
53 | description:
54 | "Understand the impact of effective communication in our lives.",
55 | header: ,
56 | className: "md:col-span-2",
57 | icon: ,
58 | },
59 | ]
60 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.yml:
--------------------------------------------------------------------------------
1 | name: 📝 Documentation Update
2 | description: Improve Documentation
3 | title: "[Doc]: "
4 | labels: [documentation]
5 | body:
6 | - type: checkboxes
7 | id: existing-issue
8 | attributes:
9 | label: Is there an existing issue for this?
10 | description: Please search to see if an issue already exists for the updates you want to make.
11 | options:
12 | - label: I have searched the existing issues
13 | required: true
14 |
15 | - type: textarea
16 | id: issue-description
17 | attributes:
18 | label: Issue Description
19 | description: Please provide a clear description of the documentation update you are suggesting.
20 | placeholder: Describe the improvement or correction you'd like to see in the documentation.
21 | validations:
22 | required: true
23 |
24 | - type: textarea
25 | id: suggested-change
26 | attributes:
27 | label: Suggested Change
28 | description: Provide details of the proposed change to the documentation.
29 | placeholder: Explain how the documentation should be updated or corrected.
30 | validations:
31 | required: true
32 |
33 | - type: textarea
34 | id: rationale
35 | attributes:
36 | label: Rationale
37 | description: Why is this documentation update necessary or beneficial?
38 | placeholder: Explain the importance or reasoning behind the suggested change.
39 | validations:
40 | required: false
41 |
42 | - type: dropdown
43 | id: urgency
44 | attributes:
45 | label: Urgency
46 | description: How urgently do you believe this documentation update is needed?
47 | options:
48 | - High
49 | - Medium
50 | - Low
51 | default: 0
52 | validations:
53 | required: true
54 |
55 | - type: checkboxes
56 | id: terms
57 | attributes:
58 | label: Acknowledgements
59 | description: Ensure you have read and agree to the project's guidelines.
60 | options:
61 | - label: I have read the [Contributing Guidelines](https://github.com/alo7lika/daccotta/blob/alo/CONTRIBUTING.md)*
62 | required: true
63 | - label: I'm a GSSOC'24-Extd contributor
64 | - label: I'm a Hacktoberfest contributor
65 | - label: I have starred the repository
66 | required: true
67 | - label: 'I am willing to work on this issue (optional)'
68 | required: false
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/client/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-gray-700 text-primary-foreground hover:bg-gradient-to-tr hover:from-gray-900 hover:to-gray-700",
14 | primary:
15 | "bg-primary text-primary-foreground hover:bg-gradient-to-tr hover:from-gray-900 hover:to-gray-700",
16 | destructive:
17 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
18 | outline:
19 | "border border-input bg-background hover:bg-gradient-to-tr hover:from-gray-900 hover:to-gray-700 hover:text-accent-foreground",
20 | secondary:
21 | "bg-blue-400 text-secondary-foreground hover:bg-gradient-to-tr hover:from-gray-900 hover:to-gray-700",
22 | ghost: "hover:bg-accent hover:text-accent-foreground",
23 | link: "text-primary underline-offset-4 hover:underline",
24 | },
25 | size: {
26 | default: "h-10 px-4 py-2",
27 | sm: "h-9 rounded-md px-3",
28 | lg: "h-11 rounded-md px-8",
29 | icon: "h-10 w-10",
30 | },
31 | },
32 | defaultVariants: {
33 | variant: "default",
34 | size: "default",
35 | },
36 | }
37 | )
38 |
39 | export interface ButtonProps
40 | extends React.ButtonHTMLAttributes,
41 | VariantProps {
42 | asChild?: boolean
43 | }
44 |
45 | const Button = React.forwardRef(
46 | ({ className, variant, size, asChild = false, ...props }, ref) => {
47 | const Comp = asChild ? Slot : "button"
48 | return (
49 |
54 | )
55 | }
56 | )
57 | Button.displayName = "Button"
58 |
59 | export { Button, buttonVariants }
60 |
--------------------------------------------------------------------------------
/.github/workflows/restrict_issues.yml:
--------------------------------------------------------------------------------
1 | name: Check for Duplicate Issues
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | jobs:
8 | check_duplicate:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Check out repository
13 | uses: actions/checkout@v3
14 |
15 | - name: Install jq
16 | run: sudo apt-get update && sudo apt-get install -y jq
17 |
18 | - name: Search for existing issues
19 | id: search_issues
20 | run: |
21 | # URL encode the issue title (replace spaces with %20)
22 | issue_title=$(echo "${{ github.event.issue.title }}" | sed 's/ /%20/g')
23 |
24 | # Search for issues with a similar title
25 | existing_issues=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
26 | "https://api.github.com/search/issues?q=repo:${{ github.repository }}+type:issue+state:open+${issue_title}")
27 |
28 | # Extract issue numbers from the search result
29 | echo "$existing_issues" | jq -r '.items[] | .number' > issue_numbers.txt
30 |
31 | - name: Check if a duplicate exists
32 | id: check_duplicate
33 | run: |
34 | issue_count=$(wc -l < issue_numbers.txt)
35 |
36 | if [ "$issue_count" -gt 0 ]; then
37 | echo "Duplicate issue(s) found."
38 | exit 0
39 | else
40 | echo "No duplicates found."
41 | exit 1
42 | fi
43 |
44 | - name: Comment and close the new issue if duplicate exists
45 | if: steps.check_duplicate.outcome == 'success'
46 | run: |
47 | issue_numbers=$(cat issue_numbers.txt | tr '\n' ', ' | sed 's/, $//')
48 | existing_issue=$(head -n 1 issue_numbers.txt)
49 |
50 | # Comment on the new issue
51 | curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
52 | -H "Content-Type: application/json" \
53 | -d "{\"body\": \"Thanks for raising this issue! However, we believe a similar issue already exists. Kindly go through all the open issues and ask to be assigned to that issue.If you believe that your issue is unique , feel free to comment and ask for it to be reopened , or you can also start a discussion regarding the same ! \"}" \
54 | "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments"
55 |
56 | # Close the new issue
57 | curl -s -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
58 | -H "Content-Type: application/json" \
59 | -d '{"state": "closed"}' \
60 | "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}"
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "bunx --bun vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@clerk/clerk-react": "^5.7.0",
14 | "@clerk/themes": "^2.1.27",
15 | "@emotion/react": "^11.13.3",
16 | "@emotion/styled": "^11.13.0",
17 | "@hookform/resolvers": "^3.9.0",
18 | "@material-tailwind/react": "^2.1.10",
19 | "@mui/icons-material": "^5.16.7",
20 | "@mui/material": "^5.16.7",
21 | "@radix-ui/react-accordion": "^1.2.0",
22 | "@radix-ui/react-avatar": "^1.1.0",
23 | "@radix-ui/react-checkbox": "^1.1.1",
24 | "@radix-ui/react-dialog": "^1.1.1",
25 | "@radix-ui/react-dropdown-menu": "^2.1.1",
26 | "@radix-ui/react-icons": "^1.3.0",
27 | "@radix-ui/react-label": "^2.1.0",
28 | "@radix-ui/react-popover": "^1.1.1",
29 | "@radix-ui/react-scroll-area": "^1.1.0",
30 | "@radix-ui/react-slot": "^1.1.0",
31 | "@radix-ui/react-switch": "^1.1.0",
32 | "@radix-ui/react-tabs": "^1.1.0",
33 | "@tabler/icons-react": "^3.17.0",
34 | "@tanstack/react-query": "^5.55.0",
35 | "@tanstack/react-query-devtools": "^4.36.1",
36 | "@types/react-lazy-load-image-component": "^1.6.4",
37 | "axios": "^1.7.7",
38 | "card": "^2.5.4",
39 | "class-variance-authority": "^0.7.0",
40 | "client": ".",
41 | "clsx": "^2.1.1",
42 | "date-fns": "^4.1.0",
43 | "firebase": "^10.13.1",
44 | "firebase-admin": "^12.4.0",
45 | "framer-motion": "^11.5.4",
46 | "i": "^0.3.7",
47 | "lucide-react": "^0.441.0",
48 | "navigate": "^0.3.6",
49 | "react": "^18.3.1",
50 | "react-day-picker": "8.10.1",
51 | "react-dom": "^18.3.1",
52 | "react-hook-form": "^7.53.0",
53 | "react-icons": "^5.3.0",
54 | "react-lazy-load-image-component": "^1.6.2",
55 | "react-router-dom": "^6.26.1",
56 | "react-toastify": "^10.0.5",
57 | "recharts": "^2.12.7",
58 | "shadcn-ui": "^0.9.0",
59 | "tailwind-merge": "^2.5.2",
60 | "tailwindcss-animate": "^1.0.7",
61 | "vaul": "^0.9.3",
62 | "zod": "^3.23.8"
63 | },
64 | "devDependencies": {
65 | "@types/node": "^22.5.4",
66 | "@types/react": "^18.3.5",
67 | "@types/react-dom": "^18.3.0",
68 | "@typescript-eslint/eslint-plugin": "^7.18.0",
69 | "@typescript-eslint/parser": "^7.18.0",
70 | "@vitejs/plugin-react": "^4.3.1",
71 | "autoprefixer": "^10.4.20",
72 | "daisyui": "^4.12.10",
73 | "eslint": "^8.57.0",
74 | "eslint-plugin-react-hooks": "^4.6.2",
75 | "eslint-plugin-react-refresh": "^0.4.11",
76 | "postcss": "^8.4.45",
77 | "tailwindcss": "^3.4.10",
78 | "typescript": "^5.5.4",
79 | "vite": "^5.4.3"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/client/src/components/charts/BarChart.tsx:
--------------------------------------------------------------------------------
1 | import { TrendingUp } from "lucide-react"
2 | import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
3 |
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardFooter,
9 | CardHeader,
10 | } from "@/components/ui/card"
11 | import {
12 | ChartConfig,
13 | ChartContainer,
14 | ChartTooltip,
15 | ChartTooltipContent,
16 | } from "../ui/chart"
17 |
18 | export const description = "A bar chart"
19 |
20 | const chartData = [
21 | { month: "January", movies: 186 },
22 | { month: "February", movies: 305 },
23 | { month: "March", movies: 237 },
24 | { month: "April", movies: 73 },
25 | { month: "May", movies: 209 },
26 | { month: "June", movies: 214 },
27 | ]
28 |
29 | const chartConfig = {
30 | movies: {
31 | label: "movies",
32 | color: "hsl(var(--chart-1))",
33 | },
34 | } satisfies ChartConfig
35 |
36 | export function BarChart1() {
37 | return (
38 |
39 |
40 |
41 | January - June 2024
42 |
43 |
44 |
45 |
46 |
47 |
48 | value.slice(0, 3)}
54 | />
55 | }
58 | />
59 |
64 |
65 |
66 |
67 |
68 |
69 | You watched 5.2% more movies this month{" "}
70 |
71 |
72 |
73 | Showing total visitors for the last 6 months
74 |
75 |
76 |
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/client/src/components/friend-requests.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from 'react'
4 | import { ArrowLeft } from 'lucide-react'
5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
6 | import { Button } from "@/components/ui/button"
7 |
8 | export function FriendRequests() {
9 | const [requests, setRequests] = useState([
10 | { id: 1, name: 'Vijay Kumar', image: '/placeholder.svg', time: '9w' }
11 | ])
12 |
13 | const handleConfirm = (id: number) => {
14 | setRequests(requests.filter(request => request.id !== id))
15 | // Here you would typically also send a request to your backend to update the friend status
16 | }
17 |
18 | const handleDelete = (id: number) => {
19 | setRequests(requests.filter(request => request.id !== id))
20 | // Here you would typically also send a request to your backend to delete the friend request
21 | }
22 |
23 | return (
24 |
25 |
26 |
30 |
Friend requests
31 |
{requests.length} friend request
32 |
View sent requests
33 |
34 | {requests.map(request => (
35 |
36 |
37 |
38 |
39 | {request.name.split(' ').map(n => n[0]).join('')}
40 |
41 |
42 |
{request.name}
43 |
{request.time}
44 |
45 |
46 |
47 | handleConfirm(request.id)}
49 | className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
50 | >
51 | Confirm
52 |
53 | handleDelete(request.id)}
55 | variant="outline"
56 | className="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded"
57 | >
58 | Delete
59 |
60 |
61 |
62 | ))}
63 |
64 |
65 | )
66 | }
--------------------------------------------------------------------------------
/client/src/data/Movies.ts:
--------------------------------------------------------------------------------
1 | // import {type MovieDetails} from "../components/custom/MovieCard/MovieCard"
2 |
3 | // export const MovieList: MovieDetails[] = [
4 | // {
5 | // Title: "Star Wars",
6 | // Year: 1977,
7 | // imdbRating: "9.8",
8 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
9 | // },
10 | // {
11 | // Title: "Star Wars",
12 | // Year: 1977,
13 | // imdbRating: "9.8",
14 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
15 | // },
16 | // {
17 | // Title: "Star Wars",
18 | // Year: 1977,
19 | // imdbRating: "9.8",
20 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
21 | // },
22 | // {
23 | // Title: "Star Wars",
24 | // Year: 1977,
25 | // imdbRating: "9.8",
26 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
27 | // },
28 | // {
29 | // Title: "Star Wars",
30 | // Year: 1977,
31 | // imdbRating: "9.8",
32 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
33 | // },
34 | // {
35 | // Title: "Star Wars",
36 | // Year: 1977,
37 | // imdbRating: "9.8",
38 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
39 | // },
40 | // {
41 | // Title: "Star Wars",
42 | // Year: 1977,
43 | // imdbRating: "9.8",
44 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
45 | // },
46 | // {
47 | // Title: "Star Wars",
48 | // Year: 1977,
49 | // imdbRating: "9.8",
50 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
51 | // },
52 | // {
53 | // Title: "Star Wars",
54 | // Year: 1977,
55 | // imdbRating: "9.8",
56 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
57 | // },
58 | // {
59 | // Title: "Star Wars",
60 | // Year: 1977,
61 | // imdbRating: "9.8",
62 | // Poster: "https://m.media-amazon.com/images/M/MV5BOTA5NjhiOTAtZWM0ZC00MWNhLThiMzEtZDFkOTk2OTU1ZDJkXkEyXkFqcGdeQXVyMTA4NDI1NTQx._V1_SX300.jpg",
63 | // },
64 | // ]
65 |
--------------------------------------------------------------------------------
/client/src/router.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { createBrowserRouter, Navigate, useNavigate } from "react-router-dom"
3 | import { AuthProvider } from "./context/AuthContext"
4 | import AuthenticatedLayout from "./layouts/authenticated-layout"
5 | import RootLayout from "./layouts/root-layout"
6 |
7 | import FriendsSearch from "./pages/Friends/Friends"
8 | import HomePage from "./pages/Home/HomePage"
9 | import JournalPage from "./pages/Journal/JournalPage"
10 | import MovieList from "./pages/List/MovieList"
11 | import UserLists from "./pages/List/UserList"
12 | import MovieDetailPage from "./pages/MoviePage/MovieDetailPage"
13 | import OnboardingForm from "./pages/Onboard/Onboard"
14 | import Profile from "./pages/Profile/Profile"
15 | import SearchMovie from "./pages/SearchMovie/SearchMovie"
16 | import StatsPage2 from "./pages/Stats/StatsPage2"
17 | import StatsPageFriends from "./pages/Stats/statsPageFriend"
18 | import UserDescriptivePage from "./pages/UserDescriptive/UserDescriptive"
19 | import SignUp2 from "./pages/auth/SignUpPage2"
20 | import SignInPage2 from "./pages/auth/SignInPage2"
21 |
22 | const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
23 | const navigate = useNavigate()
24 | return {children}
25 | }
26 |
27 | export const router = createBrowserRouter([
28 | {
29 | element: (
30 |
31 |
32 |
33 | ),
34 | children: [
35 | {
36 | path: "/onboard",
37 | element: ,
38 | },
39 | {
40 | path: "/signin",
41 | element: ,
42 | },
43 | {
44 | path: "/signup",
45 | element: ,
46 | },
47 | {
48 | element: ,
49 | children: [
50 | { path: "/", element: },
51 | { path: "/profile", element: },
52 | { path: "/friends", element: },
53 | { path: "/search-movie", element: },
54 | // {path:"create-list",element: },
55 | { path: "/movie/:id", element: },
56 | { path: "/lists", element: },
57 | { path: "/list/:listId", element: },
58 | { path: "/journal", element: },
59 | { path: "/stats", element: },
60 | { path: "/stats/:userName", element: },
61 | {
62 | path: "/user/:userName",
63 | element: ,
64 | },
65 | ],
66 | },
67 | { path: "*", element: },
68 | ],
69 | },
70 | ])
71 |
--------------------------------------------------------------------------------
/client/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | prefix: "",
11 | theme: {
12 | extend: {
13 | fontFamily: {
14 | 'lato': ['Lato', 'sans-serif'],
15 | 'noto': ['Noto Sans', 'sans-serif'],
16 | 'montserrat': ['Montserrat', 'sans-serif'],
17 | 'roboto': ['Roboto', 'sans-serif'],
18 | 'matemasie': ['Matemasie', 'sans-serif'],
19 | sans: ['Montserrat', 'Lato', 'sans-serif'],
20 | },
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: "0" },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: "0" },
69 | },
70 | bounce: {
71 | '0%, 100%': { transform: 'translateY(0)' },
72 | '50%': { transform: 'translateY(-10px)' },
73 | },
74 | blink: {
75 | '0%, 100%': { opacity: 0 },
76 | '50%': { opacity: 1 },
77 | },
78 | },
79 | animation: {
80 | "accordion-down": "accordion-down 0.2s ease-out",
81 | "accordion-up": "accordion-up 0.2s ease-out",
82 | bounce: 'bounce 1s infinite',
83 | blink: 'blink 1.5s infinite',
84 | },
85 | },
86 | container: {
87 | center: true,
88 | padding: "2rem",
89 | screens: {
90 | "2xl": "1400px",
91 | },
92 | },
93 | },
94 |
95 | plugins: [require("tailwindcss-animate"), require("daisyui")],
96 | };
97 |
--------------------------------------------------------------------------------
/client/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight } from "lucide-react"
3 | import { DayPicker } from "react-day-picker"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { buttonVariants } from "@/components/ui/button"
7 |
8 | export type CalendarProps = React.ComponentProps
9 |
10 | function Calendar({
11 | className,
12 | classNames,
13 | showOutsideDays = true,
14 | ...props
15 | }: CalendarProps) {
16 | return (
17 | ,
57 | IconRight: () => ,
58 | }}
59 | {...props}
60 | />
61 | )
62 | }
63 | Calendar.displayName = "Calendar"
64 |
65 | export { Calendar }
66 |
--------------------------------------------------------------------------------
/client/src/pages/Profile/AvatarSelectionModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { avatars } from "../../assets/avatars"
3 | import { X } from "lucide-react"
4 |
5 | interface AvatarSelectionModalProps {
6 | isOpen: boolean
7 | onClose: () => void
8 | onSelectAvatar: (avatar: string) => void
9 | currentAvatar: string
10 | }
11 |
12 | const AvatarSelectionModal: React.FC = ({
13 | isOpen,
14 | onClose,
15 | onSelectAvatar,
16 | currentAvatar,
17 | }) => {
18 | const [selectedAvatarIndex, setSelectedAvatarIndex] = useState<
19 | number | null
20 | >(null)
21 |
22 | // Update the selected avatar index based on currentAvatar
23 | React.useEffect(() => {
24 | const currentIndex = avatars.findIndex(
25 | (avatar) => avatar.profile === currentAvatar
26 | )
27 | setSelectedAvatarIndex(currentIndex !== -1 ? currentIndex : null)
28 | }, [currentAvatar])
29 |
30 | if (!isOpen) return null
31 |
32 | const handleAvatarSelect = (index: number) => {
33 | setSelectedAvatarIndex(index)
34 | onSelectAvatar(avatars[index].profile)
35 | onClose()
36 | }
37 |
38 | return (
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 | Choose an Avatar
51 |
52 |
53 |
54 | {avatars.map((avatar, index) => (
55 |
handleAvatarSelect(index)}
58 | aria-label={`Select Avatar ${index + 1}`}
59 | className={`relative rounded-lg overflow-hidden p-2 transition-transform hover:scale-105 ${
60 | selectedAvatarIndex !== null &&
61 | selectedAvatarIndex !== index
62 | ? "opacity-50"
63 | : "opacity-100"
64 | }`}
65 | >
66 |
71 |
72 | ))}
73 |
74 |
75 |
76 | )
77 | }
78 |
79 | export default AvatarSelectionModal
80 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
3 | firebases.json
4 | # Logs
5 |
6 | logs
7 | _.log
8 | npm-debug.log_
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 |
14 | # Caches
15 |
16 | .cache
17 |
18 | # Diagnostic reports (https://nodejs.org/api/report.html)
19 |
20 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
21 |
22 | # Runtime data
23 |
24 | pids
25 | _.pid
26 | _.seed
27 | *.pid.lock
28 |
29 | # Directory for instrumented libs generated by jscoverage/JSCover
30 |
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 |
35 | coverage
36 | *.lcov
37 |
38 | # nyc test coverage
39 |
40 | .nyc_output
41 |
42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
43 |
44 | .grunt
45 |
46 | # Bower dependency directory (https://bower.io/)
47 |
48 | bower_components
49 |
50 | # node-waf configuration
51 |
52 | .lock-wscript
53 |
54 | # Compiled binary addons (https://nodejs.org/api/addons.html)
55 |
56 | build/Release
57 |
58 | # Dependency directories
59 |
60 | node_modules/
61 | jspm_packages/
62 |
63 | # Snowpack dependency directory (https://snowpack.dev/)
64 |
65 | web_modules/
66 |
67 | # TypeScript cache
68 |
69 | *.tsbuildinfo
70 |
71 | # Optional npm cache directory
72 |
73 | .npm
74 |
75 | # Optional eslint cache
76 |
77 | .eslintcache
78 |
79 | # Optional stylelint cache
80 |
81 | .stylelintcache
82 |
83 | # Microbundle cache
84 |
85 | .rpt2_cache/
86 | .rts2_cache_cjs/
87 | .rts2_cache_es/
88 | .rts2_cache_umd/
89 |
90 | # Optional REPL history
91 |
92 | .node_repl_history
93 |
94 | # Output of 'npm pack'
95 |
96 | *.tgz
97 |
98 | # Yarn Integrity file
99 |
100 | .yarn-integrity
101 |
102 | # dotenv environment variable files
103 |
104 | .env
105 | .env.development.local
106 | .env.test.local
107 | .env.production.local
108 | .env.local
109 |
110 | # parcel-bundler cache (https://parceljs.org/)
111 |
112 | .parcel-cache
113 |
114 | # Next.js build output
115 |
116 | .next
117 | out
118 |
119 | # Nuxt.js build / generate output
120 |
121 | .nuxt
122 | dist
123 |
124 | # Gatsby files
125 |
126 | # Comment in the public line in if your project uses Gatsby and not Next.js
127 |
128 | # https://nextjs.org/blog/next-9-1#public-directory-support
129 |
130 | # public
131 |
132 | # vuepress build output
133 |
134 | .vuepress/dist
135 |
136 | # vuepress v2.x temp and cache directory
137 |
138 | .temp
139 |
140 | # Docusaurus cache and generated files
141 |
142 | .docusaurus
143 |
144 | # Serverless directories
145 |
146 | .serverless/
147 |
148 | # FuseBox cache
149 |
150 | .fusebox/
151 |
152 | # DynamoDB Local files
153 |
154 | .dynamodb/
155 |
156 | # TernJS port file
157 |
158 | .tern-port
159 |
160 | # Stores VSCode versions used for testing VSCode extensions
161 |
162 | .vscode-test
163 |
164 | # yarn v2
165 |
166 | .yarn/cache
167 | .yarn/unplugged
168 | .yarn/build-state.yml
169 | .yarn/install-state.gz
170 | .pnp.*
171 |
172 | # IntelliJ based IDEs
173 | .idea
174 |
175 | # Finder (MacOS) folder config
176 | .DS_Store
177 | server/firebases-mk.json
178 |
179 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | firebases.json
4 | # Logs
5 | firebases.json
6 | logs
7 | _.log
8 | npm-debug.log_
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 |
14 | # Caches
15 |
16 | .cache
17 |
18 | # Diagnostic reports (https://nodejs.org/api/report.html)
19 |
20 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
21 |
22 | # Runtime data
23 |
24 | pids
25 | _.pid
26 | _.seed
27 | *.pid.lock
28 |
29 | # Directory for instrumented libs generated by jscoverage/JSCover
30 |
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 |
35 | coverage
36 | *.lcov
37 |
38 | # nyc test coverage
39 |
40 | .nyc_output
41 |
42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
43 |
44 | .grunt
45 |
46 | # Bower dependency directory (https://bower.io/)
47 |
48 | bower_components
49 |
50 | # node-waf configuration
51 |
52 | .lock-wscript
53 |
54 | # Compiled binary addons (https://nodejs.org/api/addons.html)
55 |
56 | build/Release
57 |
58 | # Dependency directories
59 |
60 | node_modules/
61 | jspm_packages/
62 |
63 | # Snowpack dependency directory (https://snowpack.dev/)
64 |
65 | web_modules/
66 |
67 | # TypeScript cache
68 |
69 | *.tsbuildinfo
70 |
71 | # Optional npm cache directory
72 |
73 | .npm
74 |
75 | # Optional eslint cache
76 |
77 | .eslintcache
78 |
79 | # Optional stylelint cache
80 |
81 | .stylelintcache
82 |
83 | # Microbundle cache
84 |
85 | .rpt2_cache/
86 | .rts2_cache_cjs/
87 | .rts2_cache_es/
88 | .rts2_cache_umd/
89 |
90 | # Optional REPL history
91 |
92 | .node_repl_history
93 |
94 | # Output of 'npm pack'
95 |
96 | *.tgz
97 |
98 | # Yarn Integrity file
99 |
100 | .yarn-integrity
101 |
102 | # dotenv environment variable files
103 |
104 | .env
105 | .env.development.local
106 | .env.test.local
107 | .env.production.local
108 | .env.local
109 | firebase-service-account-key.json
110 |
111 | # parcel-bundler cache (https://parceljs.org/)
112 |
113 | .parcel-cache
114 |
115 | # Next.js build output
116 |
117 | .next
118 | out
119 |
120 | # Nuxt.js build / generate output
121 |
122 | .nuxt
123 | dist
124 |
125 | # Gatsby files
126 |
127 | # Comment in the public line in if your project uses Gatsby and not Next.js
128 |
129 | # https://nextjs.org/blog/next-9-1#public-directory-support
130 |
131 | # public
132 |
133 | # vuepress build output
134 |
135 | .vuepress/dist
136 |
137 | # vuepress v2.x temp and cache directory
138 |
139 | .temp
140 |
141 | # Docusaurus cache and generated files
142 |
143 | .docusaurus
144 |
145 | # Serverless directories
146 |
147 | .serverless/
148 |
149 | # FuseBox cache
150 |
151 | .fusebox/
152 |
153 | # DynamoDB Local files
154 |
155 | .dynamodb/
156 |
157 | # TernJS port file
158 |
159 | .tern-port
160 |
161 | # Stores VSCode versions used for testing VSCode extensions
162 |
163 | .vscode-test
164 |
165 | # yarn v2
166 |
167 | .yarn/cache
168 | .yarn/unplugged
169 | .yarn/build-state.yml
170 | .yarn/install-state.gz
171 | .pnp.*
172 |
173 | # IntelliJ based IDEs
174 | .idea
175 |
176 | # Finder (MacOS) folder config
177 | .DS_Store
178 |
179 |
--------------------------------------------------------------------------------
/client/src/pages/Onboard/(components)/UsernameAndPictures.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { useFormContext } from "react-hook-form"
3 | import { avatars } from "../../../assets/avatars"
4 |
5 | interface UsernameAndAvatarProps {
6 | onNext: () => void
7 | }
8 |
9 | const UsernameAndAvatar: React.FC = ({ onNext }) => {
10 | const { setValue, watch } = useFormContext()
11 | const profileUrl = watch("profile_image")
12 | const [selectedAvatarIndex, setSelectedAvatarIndex] = useState<
13 | number | null
14 | >(null)
15 |
16 | const handleAvatarSelect = (index: number) => {
17 | setSelectedAvatarIndex(index)
18 | setValue("profile_image", avatars[index].profile)
19 | }
20 |
21 | const handleNext = () => {
22 | if (profileUrl !== "") {
23 | onNext()
24 | }
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | Choose an Avatar
33 |
34 |
35 | {avatars.map((avatar, index) => (
36 |
handleAvatarSelect(index)}
46 | />
47 | ))}
48 |
49 |
50 |
56 | Next
57 |
58 |
59 |
60 |
61 |
62 |
67 |
68 |
69 | )
70 | }
71 |
72 | export default UsernameAndAvatar
73 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Poppins:wght@400;600;700&display=swap');
2 | @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
3 |
4 | @tailwind base;
5 | @tailwind components;
6 | @tailwind utilities;
7 | @layer base {
8 | :root {
9 | --background: 294 55% 98%;
10 | --foreground: 294 67% 3%;
11 | --muted: 264 35% 86%;
12 | --muted-foreground: 264 15% 36%;
13 | --popover: 294 55% 98%;
14 | --popover-foreground: 294 67% 3%;
15 | --card: 0 0 5%;
16 | --card-foreground: 294 67% 3%;
17 | --border: 294 3% 93%;
18 | --input: 294 3% 93%;
19 | --primary: 294 85% 45%;
20 | --primary-foreground: 0 0% 100%;
21 | --secondary: 0 0 5%;
22 | --secondary-foreground: 0 0% 100%;
23 | --accent: 324 85% 45%;
24 | --accent-foreground: 0 0% 100%;
25 | --destructive: 21 87% 38%;
26 | --destructive-foreground: 0 0% 100%;
27 | --ring: 294 85% 45%;
28 | --radius: 0.5rem;
29 | }
30 |
31 | .dark {
32 | --background: 294 44% 3%;
33 | --foreground: 294 23% 98%;
34 | --muted: 264 35% 14%;
35 | --muted-foreground: 264 15% 64%;
36 | --popover: 294 44% 3%;
37 | --popover-foreground: 294 23% 98%;
38 | --card: 294 44% 3%;
39 | --card-foreground: 294 23% 98%;
40 | --border: 294 3% 10%;
41 | --input: 294 3% 10%;
42 | --primary: 294 85% 45%;
43 | --primary-foreground: 0 0% 100%;
44 | --secondary: 0 0 5%;
45 | --secondary-foreground: 0 0% 100%;
46 | --accent: 324 85% 45%;
47 | --accent-foreground: 0 0% 100%;
48 | --destructive: 21 87% 46%;
49 | --destructive-foreground: 0 0% 100%;
50 | --ring: 294 85% 45%;
51 | --chart-1: 220 70% 50%;
52 | --chart-2: 160 60% 45%;
53 | --chart-3: 30 80% 55%;
54 | --chart-4: 280 65% 60%;
55 | --chart-5: 340 75% 55%;
56 | }
57 | }
58 |
59 |
60 | .scrollbar-hide {
61 | -ms-overflow-style: none; /* IE and Edge */
62 | scrollbar-width: none; /* Firefox */
63 | }
64 | .scrollbar-hide::-webkit-scrollbar {
65 | display: none; /* Chrome, Safari and Opera */
66 | }
67 |
68 | /* .bg-gradient
69 | {
70 | background: rgb(131,58,180);
71 | background: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(225,29,72,1) 50%, rgba(252,176,69,1) 100%);
72 | } */
73 |
74 | /* .bg-main{
75 |
76 | background: linear-gradient(to bottom, rgb(30,32,30) 1%, rgba(0,0,0,0.55) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;
77 | background-blend-mode: multiply,multiply;
78 | } */
79 | .bg-bar{
80 |
81 | background: linear-gradient(to bottom, rgb(30,32,30) 1%, rgba(0,0,0,0.55) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898;
82 | background-blend-mode: multiply,multiply;
83 | opacity: 0.8;
84 | }
85 |
86 |
87 |
88 | .font-heading {
89 | font-family: "Montserrat", serif;
90 | font-weight: 700;
91 | font-style: normal;
92 | }
93 |
94 | .font-body {
95 | font-family: "Roboto", system-ui;
96 | font-weight: 400;
97 | font-style: normal;
98 | }
99 |
100 | .white-font {
101 | color: white;
102 | }
103 |
--------------------------------------------------------------------------------
/client/src/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { ButtonProps, buttonVariants } from "@/components/ui/button"
6 |
7 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
8 |
14 | )
15 | Pagination.displayName = "Pagination"
16 |
17 | const PaginationContent = React.forwardRef<
18 | HTMLUListElement,
19 | React.ComponentProps<"ul">
20 | >(({ className, ...props }, ref) => (
21 |
26 | ))
27 | PaginationContent.displayName = "PaginationContent"
28 |
29 | const PaginationItem = React.forwardRef<
30 | HTMLLIElement,
31 | React.ComponentProps<"li">
32 | >(({ className, ...props }, ref) => (
33 |
34 | ))
35 | PaginationItem.displayName = "PaginationItem"
36 |
37 | type PaginationLinkProps = {
38 | isActive?: boolean
39 | } & Pick &
40 | React.ComponentProps<"a">
41 |
42 | const PaginationLink = ({
43 | className,
44 | isActive,
45 | size = "icon",
46 | ...props
47 | }: PaginationLinkProps) => (
48 |
59 | )
60 | PaginationLink.displayName = "PaginationLink"
61 |
62 | const PaginationPrevious = ({
63 | className,
64 | ...props
65 | }: React.ComponentProps) => (
66 |
72 |
73 | Previous
74 |
75 | )
76 | PaginationPrevious.displayName = "PaginationPrevious"
77 |
78 | const PaginationNext = ({
79 | className,
80 | ...props
81 | }: React.ComponentProps) => (
82 |
88 | Next
89 |
90 |
91 | )
92 | PaginationNext.displayName = "PaginationNext"
93 |
94 | const PaginationEllipsis = ({
95 | className,
96 | ...props
97 | }: React.ComponentProps<"span">) => (
98 |
103 |
104 | More pages
105 |
106 | )
107 | PaginationEllipsis.displayName = "PaginationEllipsis"
108 |
109 | export {
110 | Pagination,
111 | PaginationContent,
112 | PaginationEllipsis,
113 | PaginationItem,
114 | PaginationLink,
115 | PaginationNext,
116 | PaginationPrevious,
117 | }
118 |
--------------------------------------------------------------------------------
/client/src/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Drawer as DrawerPrimitive } from "vaul"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Drawer = ({
7 | shouldScaleBackground = true,
8 | ...props
9 | }: React.ComponentProps) => (
10 |
14 | )
15 | Drawer.displayName = "Drawer"
16 |
17 | const DrawerTrigger = DrawerPrimitive.Trigger
18 |
19 | const DrawerPortal = DrawerPrimitive.Portal
20 |
21 | const DrawerClose = DrawerPrimitive.Close
22 |
23 | const DrawerOverlay = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
34 |
35 | const DrawerContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, ...props }, ref) => (
39 |
40 |
41 |
49 |
50 | {children}
51 |
52 |
53 | ))
54 | DrawerContent.displayName = "DrawerContent"
55 |
56 | const DrawerHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
64 | )
65 | DrawerHeader.displayName = "DrawerHeader"
66 |
67 | const DrawerFooter = ({
68 | className,
69 | ...props
70 | }: React.HTMLAttributes) => (
71 |
75 | )
76 | DrawerFooter.displayName = "DrawerFooter"
77 |
78 | const DrawerTitle = React.forwardRef<
79 | React.ElementRef,
80 | React.ComponentPropsWithoutRef
81 | >(({ className, ...props }, ref) => (
82 |
90 | ))
91 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName
92 |
93 | const DrawerDescription = React.forwardRef<
94 | React.ElementRef,
95 | React.ComponentPropsWithoutRef
96 | >(({ className, ...props }, ref) => (
97 |
102 | ))
103 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName
104 |
105 | export {
106 | Drawer,
107 | DrawerPortal,
108 | DrawerOverlay,
109 | DrawerTrigger,
110 | DrawerClose,
111 | DrawerContent,
112 | DrawerHeader,
113 | DrawerFooter,
114 | DrawerTitle,
115 | DrawerDescription,
116 | }
--------------------------------------------------------------------------------
/client/src/context/AuthContext.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { createContext, useEffect, useState, useCallback } from 'react';
3 | import { getAuth, onAuthStateChanged, User, signOut, getIdToken } from 'firebase/auth';
4 | import axios from 'axios';
5 | import app, { auth } from '../lib/firebase';
6 | import { NavigateFunction } from 'react-router-dom';
7 |
8 | interface AuthState {
9 | user: User | null;
10 | isLoaded: boolean;
11 | sessionId: string | null;
12 | idToken: string | null;
13 | isOnboarded: boolean | undefined;
14 | }
15 |
16 | interface AuthContextType extends AuthState {
17 | routerPush: (to: string) => void;
18 | routerReplace: (to: string) => void;
19 | signOut: () => Promise;
20 | updateOnboardingStatus: (status: boolean) => void;
21 | checkOnboardingStatus: () => Promise;
22 | }
23 |
24 | const initialAuthState: AuthState = {
25 | user: null,
26 | isLoaded: false,
27 | sessionId: null,
28 | idToken: null,
29 | isOnboarded: undefined,
30 | };
31 |
32 | export const AuthContext = createContext(undefined);
33 |
34 | interface AuthProviderProps {
35 | children: React.ReactNode;
36 | navigate: NavigateFunction;
37 | }
38 |
39 | export function AuthProvider({ children, navigate }: AuthProviderProps) {
40 | const [authState, setAuthState] = useState(initialAuthState);
41 | const VITE_API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
42 |
43 | const checkOnboardingStatus = useCallback(async () => {
44 | if (authState.user) {
45 | try {
46 | const idToken = await getIdToken(authState.user);
47 | const response = await axios.get(`${VITE_API_BASE_URL}/api/user/${authState.user.uid}/onboarded`, {
48 | headers: {
49 | Authorization: `Bearer ${idToken}`,
50 | },
51 | });
52 | setAuthState(prevState => ({ ...prevState, isOnboarded: response.data.onboarded }));
53 | } catch (error) {
54 | console.error("Error fetching onboarding status:", error);
55 | setAuthState(prevState => ({ ...prevState, isOnboarded: false }));
56 | }
57 | }
58 | }, [authState.user]);
59 |
60 | useEffect(() => {
61 | const auth = getAuth(app);
62 | const unsubscribe = onAuthStateChanged(auth, async (user) => {
63 | let idToken = null;
64 |
65 | if (user) {
66 | try {
67 | idToken = await getIdToken(user);
68 | } catch (error) {
69 | console.error("Error getting ID token:", error);
70 | }
71 | }
72 |
73 | setAuthState({
74 | user,
75 | isLoaded: true,
76 | sessionId: user ? Math.random().toString(36).substr(2, 9) : null,
77 | idToken,
78 | isOnboarded: undefined,
79 | });
80 |
81 | if (user) {
82 | checkOnboardingStatus();
83 | }
84 | });
85 |
86 | return () => unsubscribe();
87 | }, [checkOnboardingStatus]);
88 |
89 | const updateOnboardingStatus = (status: boolean) => {
90 | setAuthState(prevState => ({ ...prevState, isOnboarded: status }));
91 | };
92 |
93 | const routerPush = (to: string) => navigate(to);
94 | const routerReplace = (to: string) => navigate(to, { replace: true });
95 |
96 | const value = {
97 | ...authState,
98 | routerPush,
99 | routerReplace,
100 | signOut: () => signOut(auth),
101 | updateOnboardingStatus,
102 | checkOnboardingStatus,
103 | };
104 |
105 | return (
106 |
107 | {children}
108 |
109 | );
110 | }
--------------------------------------------------------------------------------
/client/src/components/custom/Navbar/BottomBar.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from "react"
2 | import { IoLogInOutline } from "react-icons/io5"
3 | import { FaUser } from "react-icons/fa"
4 | import { FiLogOut } from "react-icons/fi"
5 | import ThemeController from "./ThemeController"
6 | import { Link } from "react-router-dom"
7 | import { useAuth } from "../../../hooks/useAuth"
8 | import {
9 | Dialog,
10 | DialogContent,
11 | DialogDescription,
12 | DialogFooter,
13 | DialogHeader,
14 | DialogTitle,
15 | } from "../../ui/dialog"
16 | import { Button } from "../../ui/button"
17 |
18 | const Bottom: FC = () => {
19 | const { isSignedIn, signOut } = useAuth()
20 | const [confirmOpen, setConfirmOpen] = useState(false)
21 |
22 | const handleSignOut = async () => {
23 | try {
24 | await signOut()
25 | // You can add post-logout actions here, like redirecting to home page
26 | } catch (error) {
27 | console.error("Error signing out: ", error)
28 | }
29 | }
30 |
31 | return (
32 |
33 | {isSignedIn ? (
34 |
35 |
36 |
40 |
44 |
45 |
46 | setConfirmOpen(true)}
50 | >
51 |
52 |
53 |
54 | ) : (
55 |
56 |
60 |
61 | )}
62 |
63 |
64 |
65 |
66 | Confirm Logout
67 |
68 | are you sure you want to logout?
69 |
70 |
71 |
72 | setConfirmOpen(false)}>
73 | Cancel
74 |
75 | {
78 | await handleSignOut()
79 | setConfirmOpen(false)
80 | }}
81 | >
82 | Remove
83 |
84 |
85 |
86 |
87 |
88 | )
89 | }
90 |
91 | export default Bottom
92 |
--------------------------------------------------------------------------------
/client/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/magicui/dock.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, useRef } from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 | import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | export interface DockProps extends VariantProps {
8 | className?: string
9 | magnification?: number
10 | distance?: number
11 | direction?: "top" | "middle" | "bottom"
12 | children: React.ReactNode
13 | onClick?: () => void
14 | }
15 |
16 | const DEFAULT_MAGNIFICATION = 60
17 | const DEFAULT_DISTANCE = 140
18 |
19 | const dockVariants = cva(
20 | "mx-auto w-max mt-8 h-[58px] p-2 flex gap-2 rounded-2xl border supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 backdrop-blur-md"
21 | )
22 |
23 | const Dock = React.forwardRef(
24 | (
25 | {
26 | className,
27 | children,
28 | magnification = DEFAULT_MAGNIFICATION,
29 | distance = DEFAULT_DISTANCE,
30 | direction = "bottom",
31 | onClick,
32 | ...props
33 | },
34 | ref
35 | ) => {
36 | const mouseX = useMotionValue(Infinity)
37 |
38 | const renderChildren = () => {
39 | return React.Children.map(children, (child: any) => {
40 | return React.cloneElement(child, {
41 | mouseX: mouseX,
42 | magnification: magnification,
43 | distance: distance,
44 | })
45 | })
46 | }
47 |
48 | return (
49 | mouseX.set(e.pageX)}
52 | onMouseLeave={() => mouseX.set(Infinity)}
53 | {...props}
54 | className={cn(dockVariants({ className }), {
55 | "items-start": direction === "top",
56 | "items-center": direction === "middle",
57 | "items-end": direction === "bottom",
58 | })}
59 | >
60 | {renderChildren()}
61 |
62 | )
63 | }
64 | )
65 |
66 | Dock.displayName = "Dock"
67 |
68 | export interface DockIconProps {
69 | size?: number
70 | magnification?: number
71 | distance?: number
72 | mouseX?: any
73 | className?: string
74 | children?: React.ReactNode
75 | onClick?: () => void
76 | props?: PropsWithChildren
77 | }
78 |
79 | const DockIcon = ({
80 | size,
81 | magnification = DEFAULT_MAGNIFICATION,
82 | distance = DEFAULT_DISTANCE,
83 | mouseX,
84 | className,
85 | children,
86 | ...props
87 | }: DockIconProps) => {
88 | const ref = useRef(null)
89 |
90 | const distanceCalc = useTransform(mouseX, (val: number) => {
91 | const bounds = ref.current?.getBoundingClientRect() ?? {
92 | x: 0,
93 | width: 0,
94 | }
95 |
96 | return val - bounds.x - bounds.width / 2
97 | })
98 |
99 | let widthSync = useTransform(
100 | distanceCalc,
101 | [-distance, 0, distance],
102 | [40, magnification, 40]
103 | )
104 |
105 | let width = useSpring(widthSync, {
106 | mass: 0.1,
107 | stiffness: 150,
108 | damping: 12,
109 | })
110 |
111 | return (
112 |
121 | {children}
122 |
123 | )
124 | }
125 |
126 | DockIcon.displayName = "DockIcon"
127 |
128 | export { Dock, DockIcon, dockVariants }
129 |
--------------------------------------------------------------------------------
/server/models/User.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { Schema, model, Document } from "mongoose"
2 | import { movieInListSchema, type MovieInList } from "./movie"
3 | import Person from "./Person"
4 | import { directorSchema, type Directors } from "./Director"
5 |
6 | export interface Journal {
7 | _id: mongoose.Types.ObjectId
8 | movie: MovieInList
9 | dateWatched: Date
10 | rewatches: number
11 | rating: number
12 | }
13 |
14 | export interface List extends Document {
15 | list_id: string
16 | name: string
17 | list_type: "user" | "group"
18 | movies: MovieInList[]
19 | members: {
20 | user_id: string
21 | is_author: boolean
22 | }[]
23 | date_created: Date
24 | description: string
25 | isPublic: boolean
26 | }
27 |
28 | const listSchema = new Schema({
29 | list_id: {
30 | type: String,
31 | required: true,
32 | unique: true,
33 | default: () => new mongoose.Types.ObjectId().toString(),
34 | },
35 | name: { type: String, required: true },
36 | list_type: {
37 | type: String,
38 | enum: ["user", "group"],
39 | required: true,
40 | },
41 | movies: [movieInListSchema],
42 | members: [
43 | {
44 | user_id: { type: String, required: true },
45 | is_author: { type: Boolean, required: true },
46 | },
47 | ],
48 | date_created: { type: Date, default: Date.now },
49 | description: { type: String, required: false },
50 | isPublic: { type: Boolean, required: true },
51 | })
52 |
53 | export interface FriendRequest {
54 | _id?: mongoose.Types.ObjectId
55 | from: string
56 | to: string
57 | status: "pending" | "accepted" | "rejected"
58 | createdAt: Date
59 | }
60 |
61 | export interface Group {
62 | name: string
63 | members: string[]
64 | admin: string
65 | lists: List[]
66 | }
67 |
68 | interface Users extends Document {
69 | _id: string
70 | userName: string
71 | age: number
72 | email: string
73 | groups: Group[]
74 | badges: string[]
75 | lists: List[]
76 | actor: Schema.Types.ObjectId[]
77 | directorsold: Directors[]
78 | profile_image: string
79 | onboarded: boolean
80 | friends: string[]
81 | friendRequests: FriendRequest[]
82 | journal: Journal[]
83 | }
84 |
85 | const journalSchema = new Schema({
86 | movie: movieInListSchema,
87 | dateWatched: { type: Date, required: true },
88 | rewatches: { type: Number, default: 1 },
89 | rating: { type: Number, default: 0 },
90 | })
91 |
92 | const friendRequestSchema = new Schema({
93 | _id: { type: Schema.Types.ObjectId, auto: true },
94 | from: { type: String, required: true },
95 | to: { type: String, required: true },
96 | status: {
97 | type: String,
98 | enum: ["pending", "accepted", "rejected"],
99 | default: "pending",
100 | },
101 | createdAt: { type: Date, default: Date.now },
102 | })
103 |
104 | const groupSchema = new Schema({
105 | name: { type: String, required: true },
106 | members: [{ type: String, required: true }],
107 | admin: { type: String, required: true },
108 | lists: [listSchema],
109 | })
110 |
111 | const userSchema = new Schema({
112 | _id: {
113 | type: String,
114 | required: true,
115 | default: () => new mongoose.Types.ObjectId().toString(),
116 | },
117 | userName: { type: String, required: true, unique: true },
118 | email: { type: String, required: true },
119 | age: { type: Number },
120 | groups: [groupSchema],
121 | badges: [{ type: String }],
122 | lists: [listSchema],
123 | actor: [{ type: Schema.Types.ObjectId, ref: "Person" }],
124 | directorsold: [directorSchema],
125 | profile_image: { type: String },
126 | onboarded: { type: Boolean, default: false },
127 | friends: [{ type: String }],
128 | friendRequests: [friendRequestSchema],
129 | journal: [journalSchema],
130 | })
131 |
132 | const User = model("User", userSchema)
133 | const FriendRequest = model("FriendRequest", friendRequestSchema);
134 |
135 | export { FriendRequest };
136 | export default User
137 |
--------------------------------------------------------------------------------
/server/controllers/movieController.ts:
--------------------------------------------------------------------------------
1 | import ListModel from "../models/List"
2 | import { type Request, type Response, Router } from "express"
3 | import { verifyToken } from "../middleware/verifyToken"
4 | import User from "../models/User"
5 |
6 | export const addMovie = async (req: Request, res: Response) => {
7 | try {
8 | const { listId } = req.params
9 | const { movie_id, title, poster_path, release_date, genre_ids } =
10 | req.body
11 | const userId = req.user?.uid
12 |
13 | if (!userId) {
14 | return res.status(401).json({ error: "Unauthorized" })
15 | }
16 |
17 | // Find the list
18 | const list = await ListModel.findOne()
19 | .where("list_id")
20 | .equals(listId)
21 |
22 | if (!list) {
23 | return res.status(404).json({ error: "List not found" })
24 | }
25 |
26 | // Check if the user is a member of the list
27 | const isMember = list.members.some(
28 | (member) => member.user_id === userId
29 | )
30 | if (!isMember) {
31 | return res
32 | .status(403)
33 | .json({ error: "You are not a member of this list" })
34 | }
35 |
36 | // Check if the movie already exists in the list
37 | const movieExists = list.movies.some(
38 | (movie) => movie.movie_id === movie_id
39 | )
40 | if (movieExists) {
41 | return res
42 | .status(400)
43 | .json({ error: "Movie already exists in the list" })
44 | }
45 |
46 | // Add the movie to the list
47 | list.movies.push({
48 | movie_id,
49 | title,
50 | poster_path,
51 | release_date,
52 | genre_ids,
53 | })
54 | await list.save()
55 |
56 | // Update the user's list
57 | await User.findByIdAndUpdate(
58 | userId,
59 | { $set: { "lists.$[elem]": list } },
60 | {
61 | arrayFilters: [{ "elem.list_id": listId }],
62 | new: true,
63 | }
64 | )
65 |
66 | res.status(200).json({
67 | message: "Movie added to the list successfully",
68 | list: list,
69 | })
70 | } catch (error) {
71 | console.error("Error adding movie to list:", error)
72 | res.status(500).json({ error: "Internal server error" })
73 | }
74 | }
75 |
76 |
77 | export const removeMovie = async (req: Request, res: Response) => {
78 | try {
79 | const { listId } = req.params;
80 | const { movie_id } = req.body; // Expecting movie_id in request body
81 | const userId = req.user?.uid;
82 |
83 | if (!userId) {
84 | return res.status(401).json({ error: "Unauthorized" });
85 | }
86 |
87 | // Update the user's lists to reflect the removal of the movie
88 | await User.findOneAndUpdate(
89 | { _id: userId, "lists.list_id": listId },
90 | { $pull: { "lists.$.movies": { movie_id } } },
91 | { new: true }
92 | );
93 |
94 | res.status(200).json({
95 | message: "Movie removed from the list successfully",
96 | });
97 | } catch (error) {
98 | console.error("Error removing movie from list:", error);
99 | res.status(500).json({ error: "Internal server error" });
100 | }
101 | }
102 |
103 | export const addMovieInList = async (req: Request, res: Response) => {
104 | try {
105 | const { listId } = req.params;
106 | const { movie_id, title, poster_path, release_date } = req.body; // Expecting movie details in request body
107 | const userId = req.user?.uid;
108 |
109 | if (!userId) {
110 | return res.status(401).json({ error: "Unauthorized" });
111 | }
112 |
113 | // Update the user's lists to add the movie
114 | await User.findOneAndUpdate(
115 | { _id: userId, "lists.list_id": listId },
116 | { $addToSet: { "lists.$.movies": { movie_id, title, poster_path, release_date } } }, // Use $addToSet to prevent duplicates
117 | { new: true }
118 | );
119 |
120 | res.status(200).json({
121 | message: "Movie added to the list successfully",
122 | });
123 | } catch (error) {
124 | console.error("Error adding movie to list:", error);
125 | res.status(500).json({ error: "Internal server error" });
126 | }
127 | }
--------------------------------------------------------------------------------
/.github/workflows/ci-cd.yaml:
--------------------------------------------------------------------------------
1 | name: CI/CD Workflow for Daccotta ⚡
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - "feature/**"
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | # Job 1: Set up Node.js and run tests
14 | test:
15 | name: Run Tests and Linting
16 | runs-on: ubuntu-latest
17 | strategy:
18 | matrix:
19 | node-version: [14.x, 16.x] # Testing on multiple Node versions
20 | steps:
21 | # Checkout repository
22 | - name: Checkout Code
23 | uses: actions/checkout@v3
24 |
25 | # Set up Node.js environment
26 | - name: Setup Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v3
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 |
31 | # Install dependencies (Frontend)
32 | - name: Install Frontend Dependencies
33 | working-directory: ./client
34 | run: bun install
35 |
36 | # Install dependencies (Backend)
37 | - name: Install Backend Dependencies
38 | working-directory: ./server
39 | run: bun install
40 |
41 | # Lint the frontend code
42 | - name: Lint Frontend Code
43 | working-directory: ./client
44 | run: bun run lint
45 |
46 | # Lint the backend code
47 | - name: Lint Backend Code
48 | working-directory: ./server
49 | run: bun run lint
50 |
51 | # Run frontend tests
52 | - name: Run Frontend Tests
53 | working-directory: ./client
54 | run: bun run test -- --coverage
55 |
56 | # Run backend tests
57 | - name: Run Backend Tests
58 | working-directory: ./server
59 | run: bun run test -- --coverage
60 |
61 | # Job 2: Build the frontend app and deploy preview to Netlify
62 | build-and-preview:
63 | name: Build and Deploy Preview to Netlify
64 | runs-on: ubuntu-latest
65 | needs: test
66 | steps:
67 | - name: Checkout Code
68 | uses: actions/checkout@v3
69 |
70 | - name: Setup Node.js 16.x
71 | uses: actions/setup-node@v3
72 | with:
73 | node-version: 16.x
74 |
75 | - name: Install Frontend Dependencies
76 | working-directory: ./client
77 | run: bun install
78 |
79 | - name: Build Frontend
80 | working-directory: ./client
81 | run: bun run build
82 |
83 | # Deploy to Netlify for preview
84 | - name: Install Netlify CLI
85 | run: bun install -g netlify-cli
86 |
87 | - name: Deploy Preview to Netlify
88 | env:
89 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
90 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
91 | run: |
92 | netlify deploy --dir=./client/build --site=$NETLIFY_SITE_ID --auth=$NETLIFY_AUTH_TOKEN --message="Deploy Preview for PR #${{ github.event.pull_request.number }}" --json | tee deploy-output.json
93 |
94 | # Post Netlify Preview URL as a comment on the PR
95 | - name: Add Netlify Preview URL as PR Comment
96 | if: github.event_name == 'pull_request'
97 | uses: peter-evans/create-or-update-comment@v3
98 | with:
99 | token: ${{ secrets.GITHUB_TOKEN }}
100 | issue-number: ${{ github.event.pull_request.number }}
101 | body: |
102 | 🚀 Preview deployment available: [Netlify Preview URL](${{
103 | steps.deploy-output.outputs.url }})
104 |
105 | # Job 3: Deploy to Production (only on main branch push)
106 | deploy:
107 | name: Deploy to Production
108 | runs-on: ubuntu-latest
109 | if: github.ref == 'refs/heads/main' # Only deploy on main branch
110 | needs: build-and-preview
111 | steps:
112 | - name: Checkout Code
113 | uses: actions/checkout@v3
114 |
115 | # Install Backend Dependencies for Deployment
116 | - name: Install Backend Dependencies
117 | working-directory: ./server
118 | run: bun install --production
119 |
120 | # Install Frontend Dependencies for Deployment
121 | - name: Install Frontend Dependencies
122 | working-directory: ./client
123 | run: bun install --production
124 |
125 | # Deploy Backend (Node.js server)
126 | - name: Deploy Backend to Server
127 | run: |
128 | ssh user@your-server-ip 'cd /path/to/server && git pull && bun install --production && pm2 restart server-app'
129 |
130 | # Deploy Frontend (React build)
131 | - name: Deploy Frontend to Server
132 | run: |
133 | scp -r ./client/build/* user@your-server-ip:/path/to/client/public
134 |
--------------------------------------------------------------------------------
/client/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DialogPrimitive from "@radix-ui/react-dialog"
3 | import { X } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger
10 |
11 | const DialogPortal = DialogPrimitive.Portal
12 |
13 | const DialogClose = DialogPrimitive.Close
14 |
15 | const DialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29 |
30 | const DialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, children, ...props }, ref) => (
34 |
35 |
36 |
44 | {children}
45 |
46 |
47 | Close
48 |
49 |
50 |
51 | ))
52 | DialogContent.displayName = DialogPrimitive.Content.displayName
53 |
54 | const DialogHeader = ({
55 | className,
56 | ...props
57 | }: React.HTMLAttributes) => (
58 |
65 | )
66 | DialogHeader.displayName = "DialogHeader"
67 |
68 | const DialogFooter = ({
69 | className,
70 | ...props
71 | }: React.HTMLAttributes) => (
72 |
79 | )
80 | DialogFooter.displayName = "DialogFooter"
81 |
82 | const DialogTitle = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ))
95 | DialogTitle.displayName = DialogPrimitive.Title.displayName
96 |
97 | const DialogDescription = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ))
107 | DialogDescription.displayName = DialogPrimitive.Description.displayName
108 |
109 | export {
110 | Dialog,
111 | DialogPortal,
112 | DialogOverlay,
113 | DialogClose,
114 | DialogTrigger,
115 | DialogContent,
116 | DialogHeader,
117 | DialogFooter,
118 | DialogTitle,
119 | DialogDescription,
120 | }
121 |
--------------------------------------------------------------------------------
/client/src/pages/List/MovieSearch.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { Input } from "@/components/ui/input"
3 | import { SimpleMovie } from "@/Types/Movie"
4 | import { useSearchMovies } from "@/services/movieService"
5 | import { Search } from "lucide-react"
6 |
7 | interface MovieSearchProps {
8 | onSelectMovie: (movie: SimpleMovie) => void
9 | }
10 |
11 | export default function MovieSearch({ onSelectMovie }: MovieSearchProps) {
12 | const [searchTerm, setSearchTerm] = useState("")
13 | const { data: searchResults, isLoading } = useSearchMovies(searchTerm)
14 |
15 | const handleSearch = (e: React.ChangeEvent) => {
16 | setSearchTerm(e.target.value)
17 | }
18 |
19 | return (
20 |
21 |
22 |
29 |
30 |
31 |
32 | {isLoading && searchTerm.length > 2 ? (
33 |
34 |
35 | Loading Please Wait
36 | .
37 | .
38 | .
39 |
40 |
41 | ) : (
42 | <>
43 | {searchResults && searchResults.length > 0 ? (
44 |
45 | {searchResults.map((movie) => (
46 | onSelectMovie(movie)}
50 | >
51 |
52 | {movie.poster_path ? (
53 |
58 | ) : (
59 |
60 |
61 | No poster
62 |
63 |
64 | )}
65 |
66 |
67 | {movie.title}
68 |
69 |
70 | {movie.release_date}
71 |
72 |
73 |
74 |
75 | ))}
76 |
77 | ) : (
78 | searchTerm.length > 2 && (
79 |
80 | No movies found.
81 |
82 | )
83 | )}
84 | >
85 | )}
86 |
87 |
88 | )
89 | }
90 |
--------------------------------------------------------------------------------
/client/src/services/journalService.ts:
--------------------------------------------------------------------------------
1 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
2 | import axios from "axios"
3 | import { useAuth } from "@/hooks/useAuth"
4 | import { getIdToken } from "firebase/auth"
5 | import { SimpleMovie } from "@/Types/Movie"
6 |
7 | const API_URL = `${import.meta.env.VITE_API_BASE_URL}/api`
8 |
9 | interface Journal {
10 | _id: string
11 | movie: SimpleMovie
12 | dateWatched: Date
13 | rewatches: number
14 | rating: number
15 | }
16 |
17 | export function useJournal() {
18 | const { user } = useAuth()
19 | const queryClient = useQueryClient()
20 |
21 | const getIdTokenFromUser = async () => {
22 | if (!user) throw new Error("No user logged in")
23 | return await getIdToken(user)
24 | }
25 |
26 | const fetchJournalEntries = async (): Promise => {
27 | const idToken = await getIdTokenFromUser()
28 | const response = await axios.get(`${API_URL}/journal/entries`, {
29 | headers: { Authorization: `Bearer ${idToken}` },
30 | })
31 | return response.data.journalEntries
32 | }
33 | const fetchFriendJournalEntries = async (
34 | userName: string
35 | ): Promise => {
36 | const idToken = await getIdTokenFromUser()
37 | const response = await axios.get(
38 | `${API_URL}/journal/entries/${userName}`,
39 | {
40 | headers: { Authorization: `Bearer ${idToken}` },
41 | }
42 | )
43 | return response.data.journalEntries
44 | }
45 |
46 | const addJournalEntry = async (entry: Omit) => {
47 | const idToken = await getIdTokenFromUser()
48 | const response = await axios.post(`${API_URL}/journal/add`, entry, {
49 | headers: { Authorization: `Bearer ${idToken}` },
50 | })
51 | return response.data
52 | }
53 |
54 | const searchMovie = async (query: string): Promise => {
55 | const idToken = await getIdTokenFromUser()
56 | const response = await axios.get(`${API_URL}/movies/search`, {
57 | params: { query },
58 | headers: { Authorization: `Bearer ${idToken}` },
59 | })
60 | return response.data.results
61 | }
62 |
63 | const deleteJournalEntry = async (entryId: string) => {
64 | const idToken = await getIdTokenFromUser()
65 | const response = await axios.delete(
66 | `${API_URL}/journal/delete/${entryId}`,
67 | {
68 | headers: { Authorization: `Bearer ${idToken}` },
69 | }
70 | )
71 | return response.data
72 | }
73 |
74 | const editJournalEntry = async (entry: Omit) => {
75 | const idToken = await getIdTokenFromUser()
76 | const response = await axios.post(`${API_URL}/journal/edit`, entry, {
77 | headers: { Authorization: `Bearer ${idToken}` },
78 | })
79 | return response.data
80 | }
81 |
82 | return {
83 | useGetJournalEntries: () =>
84 | useQuery({
85 | queryKey: ["journalEntries"],
86 | queryFn: () => fetchJournalEntries(),
87 | enabled: !!user,
88 | }),
89 | useGetFriendJournalEntries: (userName: string) =>
90 | useQuery({
91 | queryKey: ["friendjournalEntries"],
92 | queryFn: () => fetchFriendJournalEntries(userName),
93 | enabled: !!user,
94 | }),
95 | useAddJournalEntry: () =>
96 | useMutation({
97 | mutationFn: addJournalEntry,
98 | onSuccess: () =>
99 | queryClient.invalidateQueries({
100 | queryKey: ["journalEntries"],
101 | }),
102 | }),
103 | useSearchMovie: () =>
104 | useMutation({
105 | mutationFn: searchMovie,
106 | }),
107 | useDeleteJournalEntry: () =>
108 | useMutation({
109 | mutationFn: deleteJournalEntry,
110 | onSuccess: () =>
111 | queryClient.invalidateQueries({
112 | queryKey: ["journalEntries"],
113 | }),
114 | }),
115 | useEditJournalEntry: () =>
116 | useMutation({
117 | mutationFn: editJournalEntry,
118 | onSuccess: () =>
119 | queryClient.invalidateQueries({
120 | queryKey: ["journalEntries"],
121 | }),
122 | }),
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/client/src/components/custom/MovieCarousel/CarouselCard.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { useNavigate } from "react-router-dom"
3 | import { motion } from "framer-motion"
4 | import { ChevronRight, Calendar, Film, User } from "lucide-react"
5 | import { genreMap } from "@/lib/stats"
6 | import { SimpleMovie } from "@/Types/Movie"
7 | import LazyImage from "../LazyLoadImage/LazyImage"
8 |
9 | const IMAGE_URL = "https://image.tmdb.org/t/p"
10 |
11 | const CarouselCard: React.FC = ({
12 | movie_id,
13 | friend,
14 | title,
15 | poster_path,
16 | backdrop_path,
17 | release_date,
18 | genre_ids,
19 | }) => {
20 | const navigate = useNavigate()
21 | const genreNames = genre_ids
22 | ?.map((id) => genreMap[id])
23 | .filter(Boolean)
24 | .slice(0, 3)
25 |
26 | const handleClick = () => {
27 | console.log("movie_id:", movie_id)
28 | navigate(`/movie/${movie_id}`)
29 | }
30 |
31 | return (
32 |
42 |
43 |
44 |
50 |
55 |
56 |
57 | {title}
58 |
59 |
60 |
61 |
62 |
63 | {release_date}
64 |
65 |
66 |
67 |
68 |
69 | {genreNames?.join(", ")}
70 |
71 |
72 |
73 | {friend && (
74 |
75 |
76 |
77 | Watched by{" "}
78 |
79 | {friend}
80 |
81 |
82 |
83 | )}
84 |
89 | View Details
90 |
91 |
92 |
93 |
94 |
95 |
96 | )
97 | }
98 |
99 | export default CarouselCard
100 |
--------------------------------------------------------------------------------
/client/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // import globals from "globals"
2 | // import pluginJs from "@eslint/js"
3 | // import tseslint from "@typescript-eslint/eslint-plugin"
4 | // import pluginReact from "eslint-plugin-react"
5 | // import eslintConfigPrettier from "eslint-config-prettier"
6 | // import eslintPluginPrettier from "eslint-plugin-prettier"
7 |
8 | // export default [
9 | // {
10 | // files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
11 | // languageOptions: {
12 | // globals: globals.browser,
13 | // parser: "@typescript-eslint/parser",
14 | // },
15 | // rules: {
16 | // quotes: ["error", "double"], // Enforce double quotes
17 | // indent: ["error", "tab"], // Enforce tabs for indentation
18 | // "prettier/prettier": [
19 | // "error",
20 | // {
21 | // singleQuote: false, // Double quotes
22 | // useTabs: true, // Tabs for indentation
23 | // tabWidth: 2, // Set tab width to 2 spaces
24 | // },
25 | // ],
26 | // },
27 | // },
28 | // pluginJs.configs.recommended,
29 | // tseslint.configs.recommended, // Use as an object, no "extends"
30 | // pluginReact.configs.flat.recommended,
31 | // eslintConfigPrettier, // Disable conflicting ESLint rules
32 | // eslintPluginPrettier, // Run Prettier as an ESLint rule
33 | // {
34 | // overrides: [
35 | // {
36 | // files: ["server/**/*.ts"],
37 | // languageOptions: {
38 | // globals: globals.node, // Apply Node.js globals for server-side code
39 | // },
40 | // },
41 | // {
42 | // files: ["client/**/*.tsx"],
43 | // languageOptions: {
44 | // globals: globals.browser, // Apply browser globals for client-side code
45 | // },
46 | // },
47 | // ],
48 | // },
49 | // ]
50 |
51 | import globals from "globals"
52 | import pluginJs from "@eslint/js"
53 | import tseslint from "@typescript-eslint/eslint-plugin"
54 | import tsParser from "@typescript-eslint/parser"
55 | import pluginReact from "eslint-plugin-react"
56 | import eslintConfigPrettier from "eslint-config-prettier"
57 | import eslintPluginPrettier from "eslint-plugin-prettier"
58 |
59 | export default [
60 | {
61 | files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
62 | languageOptions: {
63 | globals: globals.browser,
64 | parser: tsParser,
65 | parserOptions: {
66 | ecmaVersion: "latest",
67 | sourceType: "module",
68 | ecmaFeatures: {
69 | jsx: true,
70 | },
71 | project: "./tsconfig.json", // Make sure this points to your tsconfig
72 | },
73 | },
74 | plugins: {
75 | "@typescript-eslint": tseslint,
76 | react: pluginReact,
77 | prettier: eslintPluginPrettier,
78 | },
79 | rules: {
80 | quotes: ["error", "double"],
81 | indent: ["error", "tab"],
82 | "@typescript-eslint/no-unused-vars": [
83 | "off",
84 | {
85 | // Changed to warn
86 | argsIgnorePattern: "^_",
87 | varsIgnorePattern: "^_",
88 | ignoreRestSiblings: true,
89 | destructuredArrayIgnorePattern: "^_",
90 | },
91 | ],
92 | "prettier/prettier": [
93 | "error",
94 | {
95 | singleQuote: false,
96 | useTabs: true,
97 | tabWidth: 2,
98 | },
99 | ],
100 | },
101 | },
102 | pluginJs.configs.recommended,
103 | pluginReact.configs.flat.recommended,
104 | eslintConfigPrettier,
105 | {
106 | overrides: [
107 | {
108 | files: [
109 | "server/**/*.ts",
110 | "client/**/*.{js,mjs,cjs,ts,jsx,tsx}",
111 | ],
112 | languageOptions: {
113 | globals: globals.node,
114 | },
115 | rules: {
116 | "@typescript-eslint/no-unused-vars": [
117 | "warn",
118 | {
119 | // Changed to warn
120 | argsIgnorePattern: "^_",
121 | varsIgnorePattern: "^_",
122 | ignoreRestSiblings: true,
123 | },
124 | ],
125 | },
126 | },
127 | {
128 | files: ["client/**/*.tsx"],
129 | languageOptions: {
130 | globals: globals.browser,
131 | },
132 | },
133 | ],
134 | },
135 | ]
136 |
--------------------------------------------------------------------------------
/client/src/components/friends-request.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
3 | import { Button } from "@/components/ui/button"
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardHeader,
9 | CardTitle,
10 | } from "@/components/ui/card"
11 | import { ScrollArea } from "@/components/ui/scroll-area"
12 | import { UserPlus, UserMinus } from "lucide-react"
13 |
14 | type FriendRequest = {
15 | id: string
16 | name: string
17 | avatar: string
18 | }
19 |
20 | export function FriendsRequest() {
21 | const [requests, setRequests] = useState([
22 | {
23 | id: "1",
24 | name: "Alice Johnson",
25 | avatar: "/placeholder.svg?height=40&width=40",
26 | },
27 | {
28 | id: "2",
29 | name: "Bob Smith",
30 | avatar: "/placeholder.svg?height=40&width=40",
31 | },
32 | {
33 | id: "3",
34 | name: "Charlie Brown",
35 | avatar: "/placeholder.svg?height=40&width=40",
36 | },
37 | {
38 | id: "4",
39 | name: "Diana Prince",
40 | avatar: "/placeholder.svg?height=40&width=40",
41 | },
42 | {
43 | id: "5",
44 | name: "Ethan Hunt",
45 | avatar: "/placeholder.svg?height=40&width=40",
46 | },
47 | ])
48 |
49 | const handleAccept = (id: string) => {
50 | setRequests(requests.filter((request) => request.id !== id))
51 | // In a real app, you would also send an API request to update the server
52 | }
53 |
54 | const handleDecline = (id: string) => {
55 | setRequests(requests.filter((request) => request.id !== id))
56 | // In a real app, you would also send an API request to update the server
57 | }
58 |
59 | return (
60 |
61 |
62 | Friend Requests
63 |
64 | You have {requests.length} pending friend requests
65 |
66 |
67 |
68 |
69 | {requests.map((request) => (
70 |
74 |
75 |
76 |
80 |
81 | {request.name
82 | .split(" ")
83 | .map((n) => n[0])
84 | .join("")}
85 |
86 |
87 |
88 |
89 | {request.name}
90 |
91 |
92 | Wants to be your friend
93 |
94 |
95 |
96 |
97 | handleAccept(request.id)}
100 | className="bg-green-500 hover:bg-green-600"
101 | >
102 |
103 | Accept
104 |
105 | handleDecline(request.id)}
109 | className="text-red-500 border-red-500 hover:bg-red-50"
110 | >
111 |
112 | Decline
113 |
114 |
115 |
116 | ))}
117 |
118 |
119 |
120 | )
121 | }
122 |
--------------------------------------------------------------------------------
/client/src/components/custom/Navbar/TestNavbar.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, type MouseEvent } from "react"
2 | import { Link, useLocation, useNavigate } from "react-router-dom"
3 | import { Home, Search, Users, NotebookPen, List, LogOutIcon } from "lucide-react"
4 | import logo from "../../../assets/logo_light.svg"
5 | import { useAuth } from "../../../hooks/useAuth"
6 | import {
7 | Dialog,
8 | DialogContent,
9 | DialogDescription,
10 | DialogFooter,
11 | DialogHeader,
12 | DialogTitle,
13 | } from "../../ui/dialog"
14 | import { Button } from "../../ui/button"
15 |
16 | const Navbar: FC = () => {
17 | const location = useLocation()
18 | const navigate = useNavigate()
19 | const [confirmOpen, setConfirmOpen] = useState(false)
20 |
21 | const navItems = [
22 | { path: "/", icon: Home, tip: "Home" },
23 | { path: "/search-movie", icon: Search, tip: "Search" },
24 | { path: "/friends", icon: Users, tip: "Friends" },
25 | { path: "/lists", icon: List, tip: "Lists" },
26 | ]
27 |
28 | // Journal item
29 | const journalItem = { path: "/journal", icon: NotebookPen, tip: "Journal" };
30 | const logOutItem = { path: "/", icon: LogOutIcon, tip: "Sign Out" };
31 |
32 | const isActive = (path: string) => location.pathname === path;
33 | const { signOut } = useAuth()
34 |
35 | const handleSignOut = async () => {
36 | try {
37 | await signOut()
38 | setConfirmOpen(false)
39 | navigate("/", { replace: true })
40 | } catch (error) {
41 | console.error("Error signing out: ", error)
42 | }
43 | }
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {navItems.map((item) => (
54 |
55 |
64 |
65 |
66 |
67 | ))}
68 |
69 |
70 | {/* Journal link aligned at the bottom */}
71 |
72 |
81 |
82 |
83 |
84 |
85 | {/* Log Out at the bottom */}
86 |
87 | ) => {
89 | e.preventDefault()
90 | setConfirmOpen(true)
91 | }}
92 | to={logOutItem.path}
93 | className={`block p-2 rounded-md tooltip tooltip-right ${
94 | isActive(logOutItem.path) ? "text-white" : "text-gray-400"
95 | }`}
96 | data-tip={logOutItem.tip}
97 | >
98 |
99 |
100 |
101 |
102 |
103 | Confirm Logout
104 |
105 | are you sure you want to logout?
106 |
107 |
108 |
109 | setConfirmOpen(false)}>
110 | Cancel
111 |
112 |
113 | Remove
114 |
115 |
116 |
117 |
118 |
119 |
120 | )
121 | }
122 |
123 | export default Navbar
124 |
--------------------------------------------------------------------------------