├── Backend ├── .gitignore ├── src │ ├── routes │ │ ├── auth.routes.js │ │ └── chat.routes.js │ ├── db │ │ └── db.js │ ├── models │ │ ├── chat.model.js │ │ ├── user.model.js │ │ └── message.model.js │ ├── middlewares │ │ └── auth.middleware.js │ ├── app.js │ ├── services │ │ ├── vector.service.js │ │ └── ai.service.js │ ├── controllers │ │ ├── chat.controller.js │ │ └── auth.controller.js │ └── sockets │ │ └── socket.server.js ├── server.js ├── public │ ├── index.html │ ├── vite.svg │ └── assets │ │ └── index-DEPrTWc_.css ├── package.json └── package-lock.json └── Frontend ├── vite.config.js ├── src ├── App.jsx ├── components │ ├── chat │ │ ├── aiClient.js │ │ ├── ChatMobileBar.jsx │ │ ├── ChatSidebar.jsx │ │ ├── ChatMobileBar.css │ │ ├── ChatSidebar.css │ │ ├── ChatComposer.jsx │ │ ├── ChatMessages.jsx │ │ ├── ChatComposer.css │ │ ├── ChatMessages.css │ │ └── ChatLayout.css │ └── ThemeToggle.jsx ├── store │ ├── store.js │ └── chatSlice.js ├── main.jsx ├── AppRoutes.jsx ├── styles │ └── theme.css ├── pages │ ├── Login.jsx │ ├── Register.jsx │ └── Home.jsx ├── assets │ └── react.svg └── App.css ├── .gitignore ├── index.html ├── README.md ├── eslint.config.js ├── package.json └── public └── vite.svg /Backend/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /Frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /Frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import './App.css' 4 | import AppRoutes from './AppRoutes' 5 | 6 | function App() { 7 | 8 | 9 | return ( 10 | <> 11 | 12 | 13 | ) 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /Frontend/src/components/chat/aiClient.js: -------------------------------------------------------------------------------- 1 | // Placeholder AI client; replace with real API integration. 2 | export async function fakeAIReply(prompt) { 3 | await new Promise(r => setTimeout(r, 600 + Math.random() * 800)); 4 | return `Echo: ${prompt.slice(0, 400)}`; 5 | } 6 | -------------------------------------------------------------------------------- /Frontend/src/store/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import chatReducer from './chatSlice.js'; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | chat: chatReducer 7 | } 8 | }); 9 | 10 | export default store; 11 | -------------------------------------------------------------------------------- /Backend/src/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const authControllers = require("../controllers/auth.controller") 3 | const router = express.Router(); 4 | 5 | 6 | 7 | router.post("/register", authControllers.registerUser) 8 | router.post("/login", authControllers.loginUser) 9 | 10 | 11 | 12 | module.exports = router; -------------------------------------------------------------------------------- /Backend/src/db/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | async function connectDb() { 5 | try { 6 | await mongoose.connect(process.env.MONGO_URI) 7 | console.log("Connected to MongoDB") 8 | } catch (err) { 9 | console.error("Error connecting to MongoDB:", err) 10 | } 11 | 12 | } 13 | 14 | 15 | module.exports = connectDb; -------------------------------------------------------------------------------- /Frontend/.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 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /Backend/server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const app = require("./src/app") 3 | const connectDb = require("./src/db/db"); 4 | const initSocketServer = require("./src/sockets/socket.server"); 5 | const httpServer = require("http").createServer(app); 6 | 7 | 8 | 9 | connectDb() 10 | initSocketServer(httpServer); 11 | 12 | 13 | httpServer.listen(3000, () => { 14 | console.log("Server is running on port 3000"); 15 | }) -------------------------------------------------------------------------------- /Frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './App.css' 5 | import { Provider } from 'react-redux' 6 | import store from './store/store.js' 7 | 8 | createRoot(document.getElementById('root')).render( 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /Frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Backend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /Backend/src/models/chat.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | const chatSchema = new mongoose.Schema({ 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'user', 8 | required: true 9 | }, 10 | title: { 11 | type: String, 12 | required: true 13 | }, 14 | lastActivity: { 15 | type: Date, 16 | default: Date.now 17 | } 18 | }, { 19 | timestamps: true 20 | }) 21 | 22 | const chatModel = mongoose.model("chat", chatSchema) 23 | 24 | 25 | module.exports = chatModel; -------------------------------------------------------------------------------- /Frontend/src/components/chat/ChatMobileBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './ChatMobileBar.css'; 3 | import './ChatLayout.css'; 4 | 5 | 6 | const ChatMobileBar = ({ onToggleSidebar, onNewChat }) => ( 7 |
8 | 9 |

Chat

10 | 11 |
12 | ); 13 | 14 | export default ChatMobileBar; 15 | -------------------------------------------------------------------------------- /Backend/src/routes/chat.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const authMiddleware = require("../middlewares/auth.middleware") 3 | const chatController = require("../controllers/chat.controller") 4 | 5 | 6 | const router = express.Router(); 7 | 8 | /* POST /api/chat/ */ 9 | router.post('/', authMiddleware.authUser, chatController.createChat) 10 | 11 | 12 | /* GET /api/chat/ */ 13 | router.get('/', authMiddleware.authUser, chatController.getChats) 14 | 15 | 16 | /* GET /api/chat/messages/:id */ 17 | router.get('/messages/:id', authMiddleware.authUser, chatController.getMessages) 18 | 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /Frontend/src/AppRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from 'react-router-dom' 2 | import React from 'react' 3 | import Home from './pages/Home' 4 | import Register from './pages/Register' 5 | import Login from './pages/Login' 6 | 7 | const AppRoutes = () => { 8 | return ( 9 | 10 | 11 | 12 | } /> 13 | } /> 14 | } /> 15 | 16 | 17 | ) 18 | } 19 | 20 | export default AppRoutes -------------------------------------------------------------------------------- /Backend/src/models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | 5 | const userSchema = new mongoose.Schema({ 6 | email: { 7 | type: String, 8 | required: true, 9 | unique: true, 10 | }, 11 | fullName: { 12 | firstName: { 13 | type: String, 14 | required: true 15 | }, 16 | lastName: { 17 | type: String, 18 | required: true 19 | } 20 | }, 21 | password: { 22 | type: String, 23 | } 24 | }, 25 | { 26 | timestamps: true 27 | } 28 | ) 29 | 30 | const userModel = mongoose.model("user", userSchema) 31 | 32 | 33 | module.exports = userModel -------------------------------------------------------------------------------- /Backend/src/models/message.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | 4 | const messageSchema = new mongoose.Schema({ 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "user" 8 | }, 9 | chat: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: "chat" 12 | }, 13 | content: { 14 | type: String, 15 | required: true 16 | }, 17 | role: { 18 | type: String, 19 | enum: [ "user", "model", "system" ], 20 | default: "user" 21 | } 22 | }, { 23 | timestamps: true 24 | }) 25 | 26 | const messageModel = mongoose.model("message", messageSchema); 27 | 28 | module.exports = messageModel; -------------------------------------------------------------------------------- /Backend/src/middlewares/auth.middleware.js: -------------------------------------------------------------------------------- 1 | const userModel = require('../models/user.model'); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | 5 | 6 | async function authUser(req, res, next) { 7 | 8 | const { token } = req.cookies; 9 | 10 | if (!token) { 11 | return res.status(401).json({ message: 'Unauthorized' }); 12 | } 13 | 14 | try { 15 | 16 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 17 | 18 | const user = await userModel.findById(decoded.id) 19 | 20 | req.user = user; 21 | 22 | next() 23 | 24 | } catch (err) { 25 | res.status(401).json({ message: 'Unauthorized' }); 26 | } 27 | 28 | } 29 | 30 | module.exports = { 31 | authUser 32 | } -------------------------------------------------------------------------------- /Backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cohort-1-project-chat-gpt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "npx nodemon server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "type": "commonjs", 14 | "dependencies": { 15 | "@google/genai": "^1.14.0", 16 | "@pinecone-database/pinecone": "^6.1.2", 17 | "bcryptjs": "^3.0.2", 18 | "cookie": "^1.0.2", 19 | "cookie-parser": "^1.4.7", 20 | "cors": "^2.8.5", 21 | "dotenv": "^17.2.1", 22 | "express": "^5.1.0", 23 | "jsonwebtoken": "^9.0.2", 24 | "mongoose": "^8.17.1", 25 | "socket.io": "^4.8.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Frontend/src/components/ThemeToggle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | const ThemeToggle = () => { 4 | const [theme, setTheme] = useState(() => { 5 | return localStorage.getItem('theme') || 'light'; 6 | }); 7 | 8 | useEffect(() => { 9 | document.documentElement.setAttribute('data-theme', theme); 10 | localStorage.setItem('theme', theme); 11 | }, [theme]); 12 | 13 | function toggleTheme() { 14 | setTheme(t => (t === 'light' ? 'dark' : 'light')); 15 | } 16 | 17 | return ( 18 | 26 | ); 27 | }; 28 | 29 | export default ThemeToggle; 30 | -------------------------------------------------------------------------------- /Backend/src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cookieParser = require('cookie-parser'); 3 | const cors = require('cors'); 4 | const path = require('path'); 5 | 6 | 7 | /* Routes */ 8 | const authRoutes = require('./routes/auth.routes'); 9 | const chatRoutes = require("./routes/chat.routes"); 10 | 11 | 12 | const app = express(); 13 | 14 | 15 | 16 | /* using middlewares */ 17 | app.use(cors({ 18 | origin: 'http://localhost:5173', 19 | credentials: true 20 | })) 21 | app.use(express.json()); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, '../public'))); 24 | 25 | 26 | 27 | /* Using Routes */ 28 | app.use('/api/auth', authRoutes); 29 | app.use('/api/chat', chatRoutes); 30 | 31 | 32 | app.get("*name", (req, res) => { 33 | res.sendFile(path.join(__dirname, '../public/index.html')); 34 | }); 35 | 36 | module.exports = app; -------------------------------------------------------------------------------- /Frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. 13 | -------------------------------------------------------------------------------- /Frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import { defineConfig, globalIgnores } from 'eslint/config' 6 | 7 | export default defineConfig([ 8 | globalIgnores(['dist']), 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | extends: [ 12 | js.configs.recommended, 13 | reactHooks.configs['recommended-latest'], 14 | reactRefresh.configs.vite, 15 | ], 16 | languageOptions: { 17 | ecmaVersion: 2020, 18 | globals: globals.browser, 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | ecmaFeatures: { jsx: true }, 22 | sourceType: 'module', 23 | }, 24 | }, 25 | rules: { 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | }, 28 | }, 29 | ]) 30 | -------------------------------------------------------------------------------- /Frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^2.2.7", 14 | "axios": "^1.11.0", 15 | "react": "^19.1.1", 16 | "react-dom": "^19.1.1", 17 | "react-redux": "^9.1.2", 18 | "react-router-dom": "^7.8.1", 19 | "socket.io-client": "^4.8.1" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.33.0", 23 | "@types/react": "^19.1.10", 24 | "@types/react-dom": "^19.1.7", 25 | "@vitejs/plugin-react": "^5.0.0", 26 | "eslint": "^9.33.0", 27 | "eslint-plugin-react-hooks": "^5.2.0", 28 | "eslint-plugin-react-refresh": "^0.4.20", 29 | "globals": "^16.3.0", 30 | "vite": "^7.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Backend/src/services/vector.service.js: -------------------------------------------------------------------------------- 1 | // Import the Pinecone library 2 | const { Pinecone } = require('@pinecone-database/pinecone') 3 | 4 | // Initialize a Pinecone client with your API key 5 | const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY }); 6 | 7 | const cohortChatGptIndex = pc.Index('cohort-chat-gpt'); 8 | 9 | async function createMemory({ vectors, metadata, messageId }) { 10 | await cohortChatGptIndex.upsert([ { 11 | id: messageId, 12 | values: vectors, 13 | metadata 14 | } ]) 15 | } 16 | 17 | 18 | async function queryMemory({ queryVector, limit = 5, metadata }) { 19 | 20 | const data = await cohortChatGptIndex.query({ 21 | vector: queryVector, 22 | topK: limit, 23 | filter: metadata ? metadata : undefined, 24 | includeMetadata: true 25 | }) 26 | 27 | return data.matches 28 | 29 | } 30 | 31 | module.exports = { createMemory, queryMemory } -------------------------------------------------------------------------------- /Frontend/src/components/chat/ChatSidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './ChatSidebar.css'; 3 | 4 | 5 | const ChatSidebar = ({ chats, activeChatId, onSelectChat, onNewChat, open }) => { 6 | 7 | 8 | 9 | return ( 10 | 28 | ); 29 | }; 30 | 31 | export default ChatSidebar; 32 | -------------------------------------------------------------------------------- /Backend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Backend/src/controllers/chat.controller.js: -------------------------------------------------------------------------------- 1 | const chatModel = require('../models/chat.model'); 2 | const messageModel = require('../models/message.model'); 3 | 4 | 5 | async function createChat(req, res) { 6 | 7 | const { title } = req.body; 8 | const user = req.user; 9 | 10 | const chat = await chatModel.create({ 11 | user: user._id, 12 | title 13 | }); 14 | 15 | res.status(201).json({ 16 | message: "Chat created successfully", 17 | chat: { 18 | _id: chat._id, 19 | title: chat.title, 20 | lastActivity: chat.lastActivity, 21 | user: chat.user 22 | } 23 | }); 24 | 25 | } 26 | 27 | async function getChats(req, res) { 28 | const user = req.user; 29 | 30 | const chats = await chatModel.find({ user: user._id }); 31 | 32 | res.status(200).json({ 33 | message: "Chats retrieved successfully", 34 | chats: chats.map(chat => ({ 35 | _id: chat._id, 36 | title: chat.title, 37 | lastActivity: chat.lastActivity, 38 | user: chat.user 39 | })) 40 | }); 41 | } 42 | 43 | async function getMessages(req, res) { 44 | 45 | const chatId = req.params.id; 46 | 47 | const messages = await messageModel.find({ chat: chatId }).sort({ createdAt: 1 }); 48 | 49 | res.status(200).json({ 50 | message: "Messages retrieved successfully", 51 | messages: messages 52 | }) 53 | 54 | } 55 | 56 | module.exports = { 57 | createChat, 58 | getChats, 59 | getMessages 60 | }; -------------------------------------------------------------------------------- /Frontend/src/components/chat/ChatMobileBar.css: -------------------------------------------------------------------------------- 1 | /* Mobile top bar (hidden on desktop) */ 2 | .chat-mobile-bar { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | min-height: 52px; 8 | display: flex; 9 | align-items: center; 10 | gap: var(--space-4); 11 | padding: env(safe-area-inset-top, 0) var(--space-4) 0 var(--space-4); 12 | border-bottom: 1px solid var(--color-border); 13 | background: rgba(5, 5, 5, 0.9); 14 | backdrop-filter: blur(10px); 15 | z-index: 30; 16 | box-sizing: border-box; 17 | } 18 | 19 | .chat-app-title { 20 | margin: 0; 21 | font-size: 1.1rem; 22 | font-weight: var(--font-weight-medium); 23 | flex: 1; 24 | text-align: center; 25 | pointer-events: none; 26 | } 27 | 28 | .chat-icon-btn { 29 | background: #0d0d0d; 30 | border: 1px solid #1e1e1e; 31 | padding: 8px 14px; 32 | border-radius: 10px; 33 | font: inherit; 34 | cursor: pointer; 35 | display: inline-flex; 36 | align-items: center; 37 | justify-content: center; 38 | gap: 6px; 39 | color: #d0d0d0; 40 | flex-shrink: 0; 41 | } 42 | 43 | .chat-icon-btn:hover { 44 | background: #181818; 45 | color: #ffffff; 46 | } 47 | 48 | /* Very small widths: reduce horizontal padding to avoid wrapping/overflow */ 49 | @media (max-width:480px) { 50 | .chat-mobile-bar { 51 | gap: 8px; 52 | padding: env(safe-area-inset-top, 0) 10px 0 10px; 53 | } 54 | 55 | .chat-icon-btn { 56 | padding: 8px 10px; 57 | } 58 | 59 | .chat-app-title { 60 | font-size: 1rem; 61 | } 62 | } 63 | 64 | /* Hide on desktop */ 65 | @media (min-width:960px) { 66 | .chat-mobile-bar { 67 | display: none; 68 | } 69 | } -------------------------------------------------------------------------------- /Frontend/src/styles/theme.css: -------------------------------------------------------------------------------- 1 | /* CSS Variable Theme Tokens (light + dark) */ 2 | /* Keep ONLY variables here; structural styles go elsewhere */ 3 | 4 | :root { 5 | /* Palette */ 6 | --color-bg: #ffffff; 7 | --color-bg-alt: #f5f7fa; 8 | --color-surface: #ffffff; 9 | --color-border: #d0d7de; 10 | --color-text: #111827; 11 | --color-text-muted: #5f6b7a; 12 | --color-primary: #2563eb; 13 | --color-primary-hover: #1d4ed8; 14 | --color-danger: #dc2626; 15 | --color-focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.4); 16 | 17 | /* Elevation */ 18 | --shadow-xs: 0 1px 2px rgba(0,0,0,0.04); 19 | --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04); 20 | 21 | /* Typography */ 22 | --font-sans: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif; 23 | --text-sm: 0.875rem; 24 | --text-base: 1rem; 25 | --text-lg: 1.125rem; 26 | --font-weight-normal: 400; 27 | --font-weight-medium: 500; 28 | --font-weight-semibold: 600; 29 | 30 | /* Sizing & Layout */ 31 | --radius-sm: 4px; 32 | --radius-md: 8px; 33 | --radius-full: 999px; 34 | --space-1: 0.25rem; 35 | --space-2: 0.5rem; 36 | --space-3: 0.75rem; 37 | --space-4: 1rem; 38 | --space-5: 1.25rem; 39 | --space-6: 1.5rem; 40 | --space-8: 2rem; 41 | --transition-fast: 120ms cubic-bezier(.4,0,.2,1); 42 | } 43 | 44 | /* Dark Theme (system preference) */ 45 | @media (prefers-color-scheme: dark) { 46 | :root { 47 | --color-bg: #0f172a; 48 | --color-bg-alt: #1e293b; 49 | --color-surface: #1e293b; 50 | --color-border: #334155; 51 | --color-text: #f1f5f9; 52 | --color-text-muted: #94a3b8; 53 | --color-primary: #3b82f6; 54 | --color-primary-hover: #2563eb; 55 | --color-danger: #f87171; 56 | --color-focus-ring: 0 0 0 3px rgba(59,130,246,0.45); 57 | --shadow-xs: 0 1px 2px rgba(0,0,0,0.6); 58 | --shadow-sm: 0 1px 3px rgba(0,0,0,0.7), 0 1px 2px rgba(0,0,0,0.5); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Backend/src/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | const userModel = require('../models/user.model'); 2 | const bcrypt = require('bcryptjs'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | 6 | async function registerUser(req, res) { 7 | 8 | const { fullName: { firstName, lastName }, email, password } = req.body; 9 | 10 | const isUserAlreadyExists = await userModel.findOne({ email }) 11 | 12 | if (isUserAlreadyExists) { 13 | res.status(400).json({ message: "User already exists" }); 14 | } 15 | 16 | 17 | const hashPassword = await bcrypt.hash(password, 10); 18 | 19 | 20 | const user = await userModel.create({ 21 | fullName: { 22 | firstName, lastName 23 | }, 24 | email, 25 | password: hashPassword 26 | }) 27 | 28 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET) 29 | 30 | 31 | res.cookie("token", token) 32 | 33 | 34 | res.status(201).json({ 35 | message: "User registered successfully", 36 | user: { 37 | email: user.email, 38 | _id: user._id, 39 | fullName: user.fullName 40 | } 41 | }) 42 | } 43 | 44 | async function loginUser(req, res) { 45 | 46 | const { email, password } = req.body; 47 | 48 | const user = await userModel.findOne({ 49 | email 50 | }) 51 | 52 | if (!user) { 53 | return res.status(400).json({ message: "Invalid email or password" }); 54 | } 55 | 56 | const isPasswordValid = await bcrypt.compare(password, user.password); 57 | 58 | 59 | if (!isPasswordValid) { 60 | return res.status(400).json({ message: "Invalid email or password" }); 61 | } 62 | 63 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET); 64 | 65 | 66 | res.cookie("token", token); 67 | 68 | 69 | res.status(200).json({ 70 | message: "user logged in successfully", 71 | user: { 72 | email: user.email, 73 | _id: user._id, 74 | fullName: user.fullName 75 | } 76 | }) 77 | 78 | } 79 | 80 | 81 | module.exports = { 82 | registerUser, 83 | loginUser 84 | } -------------------------------------------------------------------------------- /Frontend/src/components/chat/ChatSidebar.css: -------------------------------------------------------------------------------- 1 | /* Sidebar */ 2 | .chat-sidebar { width:250px; flex-shrink:0; border-right:1px solid var(--color-border); background: var(--color-surface); display:flex; flex-direction:column; position:fixed; top:0; bottom:0; left:-270px; z-index:40; transition: transform var(--transition-fast); padding-top:52px; backdrop-filter: blur(10px); } 3 | .chat-sidebar.open { transform: translateX(270px); } 4 | @media (min-width:960px) { .chat-sidebar { position:static; transform:none; left:0; padding-top:0; } } 5 | .sidebar-header { display:flex; align-items:center; justify-content:space-between; padding: var(--space-4); border-bottom:1px solid var(--color-border); gap:8px; } 6 | .sidebar-header h2 { margin:0; font-size:1rem; font-weight: var(--font-weight-medium); } 7 | .small-btn { font: inherit; background:#111; color:#fafafa; border:1px solid #252525; border-radius:8px; padding:6px 12px; cursor:pointer; display:inline-flex; align-items:center; gap:6px; font-size:0.75rem; font-weight:500; letter-spacing:0.05em; text-transform:uppercase; } 8 | .small-btn:hover { background:#181818; } 9 | .chat-list { display:flex; flex-direction:column; gap:2px; padding: var(--space-3); overflow-y:auto; } 10 | .chat-list-item { text-align:left; background:#0d0d0d; border:1px solid #1c1c1c; padding: var(--space-3) var(--space-4); border-radius:12px; cursor:pointer; display:flex; flex-direction:column; gap:2px; font: inherit; position:relative; transition: background var(--transition-fast), border-color var(--transition-fast); } 11 | .chat-list-item.active { border-color:#ffffff; box-shadow:0 0 0 1px #ffffff; } 12 | .chat-list-item:hover { background:#151515; } 13 | .chat-list-item .title-line { font-size: var(--text-sm); font-weight: var(--font-weight-medium); line-height:1.3; } 14 | .chat-list-item .meta-line { font-size:0.65rem; color: var(--color-text-muted); } 15 | .empty-hint { margin: var(--space-4); font-size: var(--text-sm); color: var(--color-text-muted); } 16 | /* Sidebar backdrop */ 17 | .sidebar-backdrop { position:fixed; inset:0; background: rgba(0,0,0,0.35); border:none; padding:0; margin:0; cursor:pointer; z-index:35; } 18 | @media (min-width:960px) { .sidebar-backdrop { display:none; } } 19 | -------------------------------------------------------------------------------- /Frontend/src/components/chat/ChatComposer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useRef, useLayoutEffect } from 'react'; 2 | import './ChatComposer.css'; 3 | 4 | // NOTE: Public API (props) kept identical for drop-in upgrade 5 | const ChatComposer = ({ input, setInput, onSend, isSending }) => { 6 | const textareaRef = useRef(null); 7 | 8 | // Auto-grow textarea height up to max-height 9 | useLayoutEffect(() => { 10 | const el = textareaRef.current; 11 | if (!el) return; 12 | el.style.height = 'auto'; 13 | el.style.height = Math.min(el.scrollHeight, 320) + 'px'; 14 | }, [input]); 15 | 16 | const handleKeyDown = useCallback((e) => { 17 | if (e.key === 'Enter' && !e.shiftKey) { 18 | e.preventDefault(); 19 | if (input.trim()) onSend(); 20 | } 21 | }, [onSend, input]); 22 | 23 | return ( 24 |
{ e.preventDefault(); if (input.trim()) onSend(); }}> 25 |
26 |
27 |