[
  {
    "path": ".gitignore",
    "content": "node_modules\n.env"
  },
  {
    "path": "README.MD",
    "content": "<h1 align=\"center\">✨ Full-Stack Chat App with Auth & Emails ✨</h1>\n\n![Demo App](/frontend/public/screenshot-for-readme.png)\n\nHighlights:\n\n- 🔐 Custom JWT Authentication (no 3rd-party auth)\n- ⚡ Real-time Messaging via Socket.io\n- 🟢 Online/Offline Presence Indicators\n- 🔔 Notification & Typing Sounds (with toggle)\n- 📨 Welcome Emails on Signup (Resend)\n- 🗂️ Image Uploads (Cloudinary)\n- 🧰 REST API with Node.js & Express\n- 🧱 MongoDB for Data Persistence\n- 🚦 API Rate-Limiting powered by Arcjet\n- 🎨 Beautiful UI with React, Tailwind CSS & DaisyUI\n- 🧠 Zustand for State Management\n- 🧑‍💻 Git & GitHub Workflow (branches, PRs, merges)\n- 🚀 Easy Deployment (free-tier friendly with Sevalla)\n\n---\n\n## 🧪 .env Setup\n\n### Backend (`/backend`)\n\n```bash\nPORT=3000\nMONGO_URI=your_mongo_uri_here\n\nNODE_ENV=development\n\nJWT_SECRET=your_jwt_secret\n\nRESEND_API_KEY=your_resend_api_key\nEMAIL_FROM=your_email_from_address\nEMAIL_FROM_NAME=your_email_from_name\n\nCLIENT_URL=http://localhost:5173\n\nCLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name\nCLOUDINARY_API_KEY=your_cloudinary_api_key\nCLOUDINARY_API_SECRET=your_cloudinary_api_secret\n\nARCJET_KEY=your_arcjet_key\nARCJET_ENV=development\n```\n\n---\n\n## 🔧 Run the Backend\n\n```bash\ncd backend\nnpm install\nnpm run dev\n```\n\n## 💻 Run the Frontend\n\n```bash\ncd frontend\nnpm install\nnpm run dev\n```\n"
  },
  {
    "path": "backend/package.json",
    "content": "{\n  \"name\": \"backend\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"nodemon src/server.js\",\n    \"start\": \"node src/server.js\"\n  },\n  \"keywords\": [],\n  \"type\": \"module\",\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\",\n  \"dependencies\": {\n    \"@arcjet/inspect\": \"^1.0.0-beta.10\",\n    \"@arcjet/node\": \"^1.0.0-beta.10\",\n    \"bcryptjs\": \"^2.4.3\",\n    \"cloudinary\": \"^2.5.1\",\n    \"cookie-parser\": \"^1.4.7\",\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^16.4.7\",\n    \"express\": \"^4.21.2\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"mongoose\": \"^8.10.1\",\n    \"resend\": \"^6.0.2\",\n    \"socket.io\": \"^4.8.1\"\n  },\n  \"devDependencies\": {\n    \"nodemon\": \"^3.1.10\"\n  }\n}\n"
  },
  {
    "path": "backend/src/controllers/auth.controller.js",
    "content": "import { sendWelcomeEmail } from \"../emails/emailHandlers.js\";\nimport { generateToken } from \"../lib/utils.js\";\nimport User from \"../models/User.js\";\nimport bcrypt from \"bcryptjs\";\nimport { ENV } from \"../lib/env.js\";\nimport cloudinary from \"../lib/cloudinary.js\";\n\nexport const signup = async (req, res) => {\n  const { fullName, email, password } = req.body;\n\n  try {\n    if (!fullName || !email || !password) {\n      return res.status(400).json({ message: \"All fields are required\" });\n    }\n\n    if (password.length < 6) {\n      return res.status(400).json({ message: \"Password must be at least 6 characters\" });\n    }\n\n    // check if emailis valid: regex\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    if (!emailRegex.test(email)) {\n      return res.status(400).json({ message: \"Invalid email format\" });\n    }\n\n    const user = await User.findOne({ email });\n    if (user) return res.status(400).json({ message: \"Email already exists\" });\n\n    // 123456 => $dnjasdkasj_?dmsakmk\n    const salt = await bcrypt.genSalt(10);\n    const hashedPassword = await bcrypt.hash(password, salt);\n\n    const newUser = new User({\n      fullName,\n      email,\n      password: hashedPassword,\n    });\n\n    if (newUser) {\n      // before CR:\n      // generateToken(newUser._id, res);\n      // await newUser.save();\n\n      // after CR:\n      // Persist user first, then issue auth cookie\n      const savedUser = await newUser.save();\n      generateToken(savedUser._id, res);\n\n      res.status(201).json({\n        _id: newUser._id,\n        fullName: newUser.fullName,\n        email: newUser.email,\n        profilePic: newUser.profilePic,\n      });\n\n      try {\n        await sendWelcomeEmail(savedUser.email, savedUser.fullName, ENV.CLIENT_URL);\n      } catch (error) {\n        console.error(\"Failed to send welcome email:\", error);\n      }\n    } else {\n      res.status(400).json({ message: \"Invalid user data\" });\n    }\n  } catch (error) {\n    console.log(\"Error in signup controller:\", error);\n    res.status(500).json({ message: \"Internal server error\" });\n  }\n};\n\nexport const login = async (req, res) => {\n  const { email, password } = req.body;\n\n  if (!email || !password) {\n    return res.status(400).json({ message: \"Email and password are required\" });\n  }\n\n  try {\n    const user = await User.findOne({ email });\n    if (!user) return res.status(400).json({ message: \"Invalid credentials\" });\n    // never tell the client which one is incorrect: password or email\n\n    const isPasswordCorrect = await bcrypt.compare(password, user.password);\n    if (!isPasswordCorrect) return res.status(400).json({ message: \"Invalid credentials\" });\n\n    generateToken(user._id, res);\n\n    res.status(200).json({\n      _id: user._id,\n      fullName: user.fullName,\n      email: user.email,\n      profilePic: user.profilePic,\n    });\n  } catch (error) {\n    console.error(\"Error in login controller:\", error);\n    res.status(500).json({ message: \"Internal server error\" });\n  }\n};\n\nexport const logout = (_, res) => {\n  res.cookie(\"jwt\", \"\", { maxAge: 0 });\n  res.status(200).json({ message: \"Logged out successfully\" });\n};\n\nexport const updateProfile = async (req, res) => {\n  try {\n    const { profilePic } = req.body;\n    if (!profilePic) return res.status(400).json({ message: \"Profile pic is required\" });\n\n    const userId = req.user._id;\n\n    const uploadResponse = await cloudinary.uploader.upload(profilePic);\n\n    const updatedUser = await User.findByIdAndUpdate(\n      userId,\n      { profilePic: uploadResponse.secure_url },\n      { new: true }\n    );\n\n    res.status(200).json(updatedUser);\n  } catch (error) {\n    console.log(\"Error in update profile:\", error);\n    res.status(500).json({ message: \"Internal server error\" });\n  }\n};\n"
  },
  {
    "path": "backend/src/controllers/message.controller.js",
    "content": "import cloudinary from \"../lib/cloudinary.js\";\nimport { getReceiverSocketId, io } from \"../lib/socket.js\";\nimport Message from \"../models/Message.js\";\nimport User from \"../models/User.js\";\n\nexport const getAllContacts = async (req, res) => {\n  try {\n    const loggedInUserId = req.user._id;\n    const filteredUsers = await User.find({ _id: { $ne: loggedInUserId } }).select(\"-password\");\n\n    res.status(200).json(filteredUsers);\n  } catch (error) {\n    console.log(\"Error in getAllContacts:\", error);\n    res.status(500).json({ message: \"Server error\" });\n  }\n};\n\nexport const getMessagesByUserId = async (req, res) => {\n  try {\n    const myId = req.user._id;\n    const { id: userToChatId } = req.params;\n\n    const messages = await Message.find({\n      $or: [\n        { senderId: myId, receiverId: userToChatId },\n        { senderId: userToChatId, receiverId: myId },\n      ],\n    });\n\n    res.status(200).json(messages);\n  } catch (error) {\n    console.log(\"Error in getMessages controller: \", error.message);\n    res.status(500).json({ error: \"Internal server error\" });\n  }\n};\n\nexport const sendMessage = async (req, res) => {\n  try {\n    const { text, image } = req.body;\n    const { id: receiverId } = req.params;\n    const senderId = req.user._id;\n\n    if (!text && !image) {\n      return res.status(400).json({ message: \"Text or image is required.\" });\n    }\n    if (senderId.equals(receiverId)) {\n      return res.status(400).json({ message: \"Cannot send messages to yourself.\" });\n    }\n    const receiverExists = await User.exists({ _id: receiverId });\n    if (!receiverExists) {\n      return res.status(404).json({ message: \"Receiver not found.\" });\n    }\n\n    let imageUrl;\n    if (image) {\n      // upload base64 image to cloudinary\n      const uploadResponse = await cloudinary.uploader.upload(image);\n      imageUrl = uploadResponse.secure_url;\n    }\n\n    const newMessage = new Message({\n      senderId,\n      receiverId,\n      text,\n      image: imageUrl,\n    });\n\n    await newMessage.save();\n\n    const receiverSocketId = getReceiverSocketId(receiverId);\n    if (receiverSocketId) {\n      io.to(receiverSocketId).emit(\"newMessage\", newMessage);\n    }\n\n    res.status(201).json(newMessage);\n  } catch (error) {\n    console.log(\"Error in sendMessage controller: \", error.message);\n    res.status(500).json({ error: \"Internal server error\" });\n  }\n};\n\nexport const getChatPartners = async (req, res) => {\n  try {\n    const loggedInUserId = req.user._id;\n\n    // find all the messages where the logged-in user is either sender or receiver\n    const messages = await Message.find({\n      $or: [{ senderId: loggedInUserId }, { receiverId: loggedInUserId }],\n    });\n\n    const chatPartnerIds = [\n      ...new Set(\n        messages.map((msg) =>\n          msg.senderId.toString() === loggedInUserId.toString()\n            ? msg.receiverId.toString()\n            : msg.senderId.toString()\n        )\n      ),\n    ];\n\n    const chatPartners = await User.find({ _id: { $in: chatPartnerIds } }).select(\"-password\");\n\n    res.status(200).json(chatPartners);\n  } catch (error) {\n    console.error(\"Error in getChatPartners: \", error.message);\n    res.status(500).json({ error: \"Internal server error\" });\n  }\n};\n"
  },
  {
    "path": "backend/src/emails/emailHandlers.js",
    "content": "import { resendClient, sender } from \"../lib/resend.js\";\nimport { createWelcomeEmailTemplate } from \"../emails/emailTemplates.js\";\n\nexport const sendWelcomeEmail = async (email, name, clientURL) => {\n  const { data, error } = await resendClient.emails.send({\n    from: `${sender.name} <${sender.email}>`,\n    to: email,\n    subject: \"Welcome to Chatify!\",\n    html: createWelcomeEmailTemplate(name, clientURL),\n  });\n\n  if (error) {\n    console.error(\"Error sending welcome email:\", error);\n    throw new Error(\"Failed to send welcome email\");\n  }\n\n  console.log(\"Welcome Email sent successfully\", data);\n};\n"
  },
  {
    "path": "backend/src/emails/emailTemplates.js",
    "content": "export function createWelcomeEmailTemplate(name, clientURL) {\n  return `\n  <!DOCTYPE html>\n  <html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Welcome to Messenger</title>\n  </head>\n  <body style=\"font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f5f5f5;\">\n    <div style=\"background: linear-gradient(to right, #36D1DC, #5B86E5); padding: 30px; text-align: center; border-radius: 12px 12px 0 0;\">\n      <img src=\"https://img.freepik.com/free-vector/hand-drawn-message-element-vector-cute-sticker_53876-118344.jpg?t=st=1741295028~exp=1741298628~hmac=0d076f885d7095f0b5bc8d34136cd6d64749455f8cb5f29a924281bafc11b96c&w=1480\" alt=\"Messenger Logo\" style=\"width: 80px; height: 80px; margin-bottom: 20px; border-radius: 50%; background-color: white; padding: 10px;\">\n      <h1 style=\"color: white; margin: 0; font-size: 28px; font-weight: 500;\">Welcome to Messenger!</h1>\n    </div>\n    <div style=\"background-color: #ffffff; padding: 35px; border-radius: 0 0 12px 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.05);\">\n      <p style=\"font-size: 18px; color: #5B86E5;\"><strong>Hello ${name},</strong></p>\n      <p>We're excited to have you join our messaging platform! Messenger connects you with friends, family, and colleagues in real-time, no matter where they are.</p>\n      \n      <div style=\"background-color: #f8f9fa; padding: 25px; border-radius: 10px; margin: 25px 0; border-left: 4px solid #36D1DC;\">\n        <p style=\"font-size: 16px; margin: 0 0 15px 0;\"><strong>Get started in just a few steps:</strong></p>\n        <ul style=\"padding-left: 20px; margin: 0;\">\n          <li style=\"margin-bottom: 10px;\">Set up your profile picture</li>\n          <li style=\"margin-bottom: 10px;\">Find and add your contacts</li>\n          <li style=\"margin-bottom: 10px;\">Start a conversation</li>\n          <li style=\"margin-bottom: 0;\">Share photos, videos, and more</li>\n        </ul>\n      </div>\n      \n      <div style=\"text-align: center; margin: 30px 0;\">\n        <a href=${clientURL} style=\"background: linear-gradient(to right, #36D1DC, #5B86E5); color: white; text-decoration: none; padding: 12px 30px; border-radius: 50px; font-weight: 500; display: inline-block;\">Open Messenger</a>\n      </div>\n      \n      <p style=\"margin-bottom: 5px;\">If you need any help or have questions, we're always here to assist you.</p>\n      <p style=\"margin-top: 0;\">Happy messaging!</p>\n      \n      <p style=\"margin-top: 25px; margin-bottom: 0;\">Best regards,<br>The Messenger Team</p>\n    </div>\n    \n    <div style=\"text-align: center; padding: 20px; color: #999; font-size: 12px;\">\n      <p>© 2025 Messenger. All rights reserved.</p>\n      <p>\n        <a href=\"#\" style=\"color: #5B86E5; text-decoration: none; margin: 0 10px;\">Privacy Policy</a>\n        <a href=\"#\" style=\"color: #5B86E5; text-decoration: none; margin: 0 10px;\">Terms of Service</a>\n        <a href=\"#\" style=\"color: #5B86E5; text-decoration: none; margin: 0 10px;\">Contact Us</a>\n      </p>\n    </div>\n  </body>\n  </html>\n  `;\n}\n"
  },
  {
    "path": "backend/src/lib/arcjet.js",
    "content": "import arcjet, { shield, detectBot, slidingWindow } from \"@arcjet/node\";\n\nimport { ENV } from \"./env.js\";\n\nconst aj = arcjet({\n  key: ENV.ARCJET_KEY,\n  rules: [\n    // Shield protects your app from common attacks e.g. SQL injection\n    shield({ mode: \"LIVE\" }),\n    // Create a bot detection rule\n    detectBot({\n      mode: \"LIVE\", // Blocks requests. Use \"DRY_RUN\" to log only\n      // Block all bots except the following\n      allow: [\n        \"CATEGORY:SEARCH_ENGINE\", // Google, Bing, etc\n        // Uncomment to allow these other common bot categories\n        // See the full list at https://arcjet.com/bot-list\n        //\"CATEGORY:MONITOR\", // Uptime monitoring services\n        //\"CATEGORY:PREVIEW\", // Link previews e.g. Slack, Discord\n      ],\n    }),\n    // Create a token bucket rate limit. Other algorithms are supported.\n    slidingWindow({\n      mode: \"LIVE\", // Blocks requests. Use \"DRY_RUN\" to log only\n      max: 100,\n      interval: 60,\n    }),\n  ],\n});\n\nexport default aj;\n"
  },
  {
    "path": "backend/src/lib/cloudinary.js",
    "content": "import { v2 as cloudinary } from \"cloudinary\";\nimport { ENV } from \"./env.js\";\n\ncloudinary.config({\n  cloud_name: ENV.CLOUDINARY_CLOUD_NAME,\n  api_key: ENV.CLOUDINARY_API_KEY,\n  api_secret: ENV.CLOUDINARY_API_SECRET,\n});\n\nexport default cloudinary;\n"
  },
  {
    "path": "backend/src/lib/db.js",
    "content": "import mongoose from \"mongoose\";\nimport { ENV } from \"./env.js\";\n\nexport const connectDB = async () => {\n  try {\n    const { MONGO_URI } = ENV;\n    if (!MONGO_URI) throw new Error(\"MONGO_URI is not set\");\n\n    const conn = await mongoose.connect(ENV.MONGO_URI);\n    console.log(\"MONGODB CONNECTED:\", conn.connection.host);\n  } catch (error) {\n    console.error(\"Error connection to MONGODB:\", error);\n    process.exit(1); // 1 status code means fail, 0 means success\n  }\n};\n"
  },
  {
    "path": "backend/src/lib/env.js",
    "content": "import \"dotenv/config\";\n\nexport const ENV = {\n  PORT: process.env.PORT,\n  MONGO_URI: process.env.MONGO_URI,\n  JWT_SECRET: process.env.JWT_SECRET,\n  NODE_ENV: process.env.NODE_ENV,\n  CLIENT_URL: process.env.CLIENT_URL,\n  RESEND_API_KEY: process.env.RESEND_API_KEY,\n  EMAIL_FROM: process.env.EMAIL_FROM,\n  EMAIL_FROM_NAME: process.env.EMAIL_FROM_NAME,\n  CLOUDINARY_CLOUD_NAME: process.env.CLOUDINARY_CLOUD_NAME,\n  CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY,\n  CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET,\n  ARCJET_KEY: process.env.ARCJET_KEY,\n  ARCJET_ENV: process.env.ARCJET_ENV,\n};\n"
  },
  {
    "path": "backend/src/lib/resend.js",
    "content": "import { Resend } from \"resend\";\nimport { ENV } from \"./env.js\";\n\nexport const resendClient = new Resend(ENV.RESEND_API_KEY);\n\nexport const sender = {\n  email: ENV.EMAIL_FROM,\n  name: ENV.EMAIL_FROM_NAME,\n};\n"
  },
  {
    "path": "backend/src/lib/socket.js",
    "content": "import { Server } from \"socket.io\";\nimport http from \"http\";\nimport express from \"express\";\nimport { ENV } from \"./env.js\";\nimport { socketAuthMiddleware } from \"../middleware/socket.auth.middleware.js\";\n\nconst app = express();\nconst server = http.createServer(app);\n\nconst io = new Server(server, {\n  cors: {\n    origin: [ENV.CLIENT_URL],\n    credentials: true,\n  },\n});\n\n// apply authentication middleware to all socket connections\nio.use(socketAuthMiddleware);\n\n// we will use this function to check if the user is online or not\nexport function getReceiverSocketId(userId) {\n  return userSocketMap[userId];\n}\n\n// this is for storig online users\nconst userSocketMap = {}; // {userId:socketId}\n\nio.on(\"connection\", (socket) => {\n  console.log(\"A user connected\", socket.user.fullName);\n\n  const userId = socket.userId;\n  userSocketMap[userId] = socket.id;\n\n  // io.emit() is used to send events to all connected clients\n  io.emit(\"getOnlineUsers\", Object.keys(userSocketMap));\n\n  // with socket.on we listen for events from clients\n  socket.on(\"disconnect\", () => {\n    console.log(\"A user disconnected\", socket.user.fullName);\n    delete userSocketMap[userId];\n    io.emit(\"getOnlineUsers\", Object.keys(userSocketMap));\n  });\n});\n\nexport { io, app, server };\n"
  },
  {
    "path": "backend/src/lib/utils.js",
    "content": "import jwt from \"jsonwebtoken\";\nimport { ENV } from \"./env.js\";\n\nexport const generateToken = (userId, res) => {\n  const { JWT_SECRET } = ENV;\n  if (!JWT_SECRET) {\n    throw new Error(\"JWT_SECRET is not configured\");\n  }\n\n  const token = jwt.sign({ userId }, JWT_SECRET, {\n    expiresIn: \"7d\",\n  });\n\n  res.cookie(\"jwt\", token, {\n    maxAge: 7 * 24 * 60 * 60 * 1000, // MS\n    httpOnly: true, // prevent XSS attacks: cross-site scripting\n    sameSite: \"strict\", // CSRF attacks\n    secure: ENV.NODE_ENV === \"development\" ? false : true,\n  });\n\n  return token;\n};\n\n// http://localhost\n// https://dsmakmk.com\n"
  },
  {
    "path": "backend/src/middleware/arcjet.middleware.js",
    "content": "import aj from \"../lib/arcjet.js\";\nimport { isSpoofedBot } from \"@arcjet/inspect\";\n\nexport const arcjetProtection = async (req, res, next) => {\n  try {\n    const decision = await aj.protect(req);\n\n    if (decision.isDenied()) {\n      if (decision.reason.isRateLimit()) {\n        return res.status(429).json({ message: \"Rate limit exceeded. Please try again later.\" });\n      } else if (decision.reason.isBot()) {\n        return res.status(403).json({ message: \"Bot access denied.\" });\n      } else {\n        return res.status(403).json({\n          message: \"Access denied by security policy.\",\n        });\n      }\n    }\n\n    // check for spoofed bots\n    if (decision.results.some(isSpoofedBot)) {\n      return res.status(403).json({\n        error: \"Spoofed bot detected\",\n        message: \"Malicious bot activity detected.\",\n      });\n    }\n\n    next();\n  } catch (error) {\n    console.log(\"Arcjet Protection Error:\", error);\n    next();\n  }\n};\n"
  },
  {
    "path": "backend/src/middleware/auth.middleware.js",
    "content": "import jwt from \"jsonwebtoken\";\nimport User from \"../models/User.js\";\nimport { ENV } from \"../lib/env.js\";\n\nexport const protectRoute = async (req, res, next) => {\n  try {\n    const token = req.cookies.jwt;\n    if (!token) return res.status(401).json({ message: \"Unauthorized - No token provided\" });\n\n    const decoded = jwt.verify(token, ENV.JWT_SECRET);\n    if (!decoded) return res.status(401).json({ message: \"Unauthorized - Invalid token\" });\n\n    const user = await User.findById(decoded.userId).select(\"-password\");\n    if (!user) return res.status(404).json({ message: \"User not found\" });\n\n    req.user = user;\n    next();\n  } catch (error) {\n    console.log(\"Error in protectRoute middleware:\", error);\n    res.status(500).json({ message: \"Internal server error\" });\n  }\n};\n"
  },
  {
    "path": "backend/src/middleware/socket.auth.middleware.js",
    "content": "import jwt from \"jsonwebtoken\";\nimport User from \"../models/User.js\";\nimport { ENV } from \"../lib/env.js\";\n\nexport const socketAuthMiddleware = async (socket, next) => {\n  try {\n    // extract token from http-only cookies\n    const token = socket.handshake.headers.cookie\n      ?.split(\"; \")\n      .find((row) => row.startsWith(\"jwt=\"))\n      ?.split(\"=\")[1];\n\n    if (!token) {\n      console.log(\"Socket connection rejected: No token provided\");\n      return next(new Error(\"Unauthorized - No Token Provided\"));\n    }\n\n    // verify the token\n    const decoded = jwt.verify(token, ENV.JWT_SECRET);\n    if (!decoded) {\n      console.log(\"Socket connection rejected: Invalid token\");\n      return next(new Error(\"Unauthorized - Invalid Token\"));\n    }\n\n    // find the user fromdb\n    const user = await User.findById(decoded.userId).select(\"-password\");\n    if (!user) {\n      console.log(\"Socket connection rejected: User not found\");\n      return next(new Error(\"User not found\"));\n    }\n\n    // attach user info to socket\n    socket.user = user;\n    socket.userId = user._id.toString();\n\n    console.log(`Socket authenticated for user: ${user.fullName} (${user._id})`);\n\n    next();\n  } catch (error) {\n    console.log(\"Error in socket authentication:\", error.message);\n    next(new Error(\"Unauthorized - Authentication failed\"));\n  }\n};\n"
  },
  {
    "path": "backend/src/models/Message.js",
    "content": "import mongoose from \"mongoose\";\n\nconst messageSchema = new mongoose.Schema(\n  {\n    senderId: {\n      type: mongoose.Schema.Types.ObjectId,\n      ref: \"User\",\n      required: true,\n    },\n    receiverId: {\n      type: mongoose.Schema.Types.ObjectId,\n      ref: \"User\",\n      required: true,\n    },\n    text: {\n      type: String,\n      trim: true,\n      maxlength: 2000,\n    },\n    image: {\n      type: String,\n    },\n  },\n  { timestamps: true }\n);\n\nconst Message = mongoose.model(\"Message\", messageSchema);\n\nexport default Message;\n"
  },
  {
    "path": "backend/src/models/User.js",
    "content": "import mongoose from \"mongoose\";\n\nconst userSchema = new mongoose.Schema(\n  {\n    email: {\n      type: String,\n      required: true,\n      unique: true,\n    },\n    fullName: {\n      type: String,\n      required: true,\n    },\n    password: {\n      type: String,\n      required: true,\n      minlength: 6,\n    },\n    profilePic: {\n      type: String,\n      default: \"\",\n    },\n  },\n  { timestamps: true } // createdAt & updatedAt\n);\n\nconst User = mongoose.model(\"User\", userSchema);\n\nexport default User;\n"
  },
  {
    "path": "backend/src/routes/auth.route.js",
    "content": "import express from \"express\";\nimport { signup, login, logout, updateProfile } from \"../controllers/auth.controller.js\";\nimport { protectRoute } from \"../middleware/auth.middleware.js\";\nimport { arcjetProtection } from \"../middleware/arcjet.middleware.js\";\n\nconst router = express.Router();\n\nrouter.use(arcjetProtection);\n\nrouter.post(\"/signup\", signup);\nrouter.post(\"/login\", login);\nrouter.post(\"/logout\", logout);\n\nrouter.put(\"/update-profile\", protectRoute, updateProfile);\n\nrouter.get(\"/check\", protectRoute, (req, res) => res.status(200).json(req.user));\n\nexport default router;\n"
  },
  {
    "path": "backend/src/routes/message.route.js",
    "content": "import express from \"express\";\nimport {\n  getAllContacts,\n  getChatPartners,\n  getMessagesByUserId,\n  sendMessage,\n} from \"../controllers/message.controller.js\";\nimport { protectRoute } from \"../middleware/auth.middleware.js\";\nimport { arcjetProtection } from \"../middleware/arcjet.middleware.js\";\n\nconst router = express.Router();\n\n// the middlewares execute in order - so requests get rate-limited first, then authenticated.\n// this is actually more efficient since unauthenticated requests get blocked by rate limiting before hitting the auth middleware.\nrouter.use(arcjetProtection, protectRoute);\n\nrouter.get(\"/contacts\", getAllContacts);\nrouter.get(\"/chats\", getChatPartners);\nrouter.get(\"/:id\", getMessagesByUserId);\nrouter.post(\"/send/:id\", sendMessage);\n\nexport default router;\n"
  },
  {
    "path": "backend/src/server.js",
    "content": "import express from \"express\";\nimport cookieParser from \"cookie-parser\";\nimport path from \"path\";\nimport cors from \"cors\";\n\nimport authRoutes from \"./routes/auth.route.js\";\nimport messageRoutes from \"./routes/message.route.js\";\nimport { connectDB } from \"./lib/db.js\";\nimport { ENV } from \"./lib/env.js\";\nimport { app, server } from \"./lib/socket.js\";\n\nconst __dirname = path.resolve();\n\nconst PORT = ENV.PORT || 3000;\n\napp.use(express.json({ limit: \"5mb\" })); // req.body\napp.use(cors({ origin: ENV.CLIENT_URL, credentials: true }));\napp.use(cookieParser());\n\napp.use(\"/api/auth\", authRoutes);\napp.use(\"/api/messages\", messageRoutes);\n\n// make ready for deployment\nif (ENV.NODE_ENV === \"production\") {\n  app.use(express.static(path.join(__dirname, \"../frontend/dist\")));\n\n  app.get(\"*\", (_, res) => {\n    res.sendFile(path.join(__dirname, \"../frontend\", \"dist\", \"index.html\"));\n  });\n}\n\nserver.listen(PORT, () => {\n  console.log(\"Server running on port: \" + PORT);\n  connectDB();\n});\n"
  },
  {
    "path": "frontend/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "frontend/README.md",
    "content": "# React + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh\n- [@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\n\n## Expanding the ESLint configuration\n\nIf 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.\n"
  },
  {
    "path": "frontend/eslint.config.js",
    "content": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reactRefresh from 'eslint-plugin-react-refresh'\nimport { defineConfig, globalIgnores } from 'eslint/config'\n\nexport default defineConfig([\n  globalIgnores(['dist']),\n  {\n    files: ['**/*.{js,jsx}'],\n    extends: [\n      js.configs.recommended,\n      reactHooks.configs['recommended-latest'],\n      reactRefresh.configs.vite,\n    ],\n    languageOptions: {\n      ecmaVersion: 2020,\n      globals: globals.browser,\n      parserOptions: {\n        ecmaVersion: 'latest',\n        ecmaFeatures: { jsx: true },\n        sourceType: 'module',\n      },\n    },\n    rules: {\n      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],\n    },\n  },\n])\n"
  },
  {
    "path": "frontend/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.jsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "frontend/package.json",
    "content": "{\n  \"name\": \"frontend\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"lint\": \"eslint .\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.11.0\",\n    \"lucide-react\": \"^0.542.0\",\n    \"react\": \"^19.1.1\",\n    \"react-dom\": \"^19.1.1\",\n    \"react-hot-toast\": \"^2.6.0\",\n    \"react-router\": \"^7.8.2\",\n    \"socket.io-client\": \"^4.8.1\",\n    \"zustand\": \"^5.0.3\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.33.0\",\n    \"@types/react\": \"^19.1.10\",\n    \"@types/react-dom\": \"^19.1.7\",\n    \"@vitejs/plugin-react\": \"^5.0.0\",\n    \"autoprefixer\": \"^10.4.21\",\n    \"daisyui\": \"^4.12.24\",\n    \"eslint\": \"^9.33.0\",\n    \"eslint-plugin-react-hooks\": \"^5.2.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.20\",\n    \"globals\": \"^16.3.0\",\n    \"postcss\": \"^8.5.6\",\n    \"tailwindcss\": \"^3.4.17\",\n    \"vite\": \"^7.1.2\"\n  }\n}\n"
  },
  {
    "path": "frontend/postcss.config.js",
    "content": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "frontend/src/App.jsx",
    "content": "import { Navigate, Route, Routes } from \"react-router\";\nimport ChatPage from \"./pages/ChatPage\";\nimport LoginPage from \"./pages/LoginPage\";\nimport SignUpPage from \"./pages/SignUpPage\";\nimport { useAuthStore } from \"./store/useAuthStore\";\nimport { useEffect } from \"react\";\nimport PageLoader from \"./components/PageLoader\";\n\nimport { Toaster } from \"react-hot-toast\";\n\nfunction App() {\n  const { checkAuth, isCheckingAuth, authUser } = useAuthStore();\n\n  useEffect(() => {\n    checkAuth();\n  }, [checkAuth]);\n\n  if (isCheckingAuth) return <PageLoader />;\n\n  return (\n    <div className=\"min-h-screen bg-slate-900 relative flex items-center justify-center p-4 overflow-hidden\">\n      {/* DECORATORS - GRID BG & GLOW SHAPES */}\n      <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#4f4f4f2e_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f2e_1px,transparent_1px)] bg-[size:14px_24px]\" />\n      <div className=\"absolute top-0 -left-4 size-96 bg-pink-500 opacity-20 blur-[100px]\" />\n      <div className=\"absolute bottom-0 -right-4 size-96 bg-cyan-500 opacity-20 blur-[100px]\" />\n\n      <Routes>\n        <Route path=\"/\" element={authUser ? <ChatPage /> : <Navigate to={\"/login\"} />} />\n        <Route path=\"/login\" element={!authUser ? <LoginPage /> : <Navigate to={\"/\"} />} />\n        <Route path=\"/signup\" element={!authUser ? <SignUpPage /> : <Navigate to={\"/\"} />} />\n      </Routes>\n\n      <Toaster />\n    </div>\n  );\n}\nexport default App;\n"
  },
  {
    "path": "frontend/src/components/ActiveTabSwitch.jsx",
    "content": "import { useChatStore } from \"../store/useChatStore\";\n\nfunction ActiveTabSwitch() {\n  const { activeTab, setActiveTab } = useChatStore();\n\n  return (\n    <div className=\"tabs tabs-boxed bg-transparent p-2 m-2\">\n      <button\n        onClick={() => setActiveTab(\"chats\")}\n        className={`tab ${\n          activeTab === \"chats\" ? \"bg-cyan-500/20 text-cyan-400\" : \"text-slate-400\"\n        }`}\n      >\n        Chats\n      </button>\n\n      <button\n        onClick={() => setActiveTab(\"contacts\")}\n        className={`tab ${\n          activeTab === \"contacts\" ? \"bg-cyan-500/20 text-cyan-400\" : \"text-slate-400\"\n        }`}\n      >\n        Contacts\n      </button>\n    </div>\n  );\n}\nexport default ActiveTabSwitch;\n"
  },
  {
    "path": "frontend/src/components/BorderAnimatedContainer.jsx",
    "content": "// How to make animated gradient border 👇\n// https://cruip-tutorials.vercel.app/animated-gradient-border/\nfunction BorderAnimatedContainer({ children }) {\n  return (\n    <div className=\"w-full h-full [background:linear-gradient(45deg,#172033,theme(colors.slate.800)_50%,#172033)_padding-box,conic-gradient(from_var(--border-angle),theme(colors.slate.600/.48)_80%,_theme(colors.cyan.500)_86%,_theme(colors.cyan.300)_90%,_theme(colors.cyan.500)_94%,_theme(colors.slate.600/.48))_border-box] rounded-2xl border border-transparent animate-border  flex overflow-hidden\">\n      {children}\n    </div>\n  );\n}\nexport default BorderAnimatedContainer;\n"
  },
  {
    "path": "frontend/src/components/ChatContainer.jsx",
    "content": "import { useEffect, useRef } from \"react\";\nimport { useAuthStore } from \"../store/useAuthStore\";\nimport { useChatStore } from \"../store/useChatStore\";\nimport ChatHeader from \"./ChatHeader\";\nimport NoChatHistoryPlaceholder from \"./NoChatHistoryPlaceholder\";\nimport MessageInput from \"./MessageInput\";\nimport MessagesLoadingSkeleton from \"./MessagesLoadingSkeleton\";\n\nfunction ChatContainer() {\n  const {\n    selectedUser,\n    getMessagesByUserId,\n    messages,\n    isMessagesLoading,\n    subscribeToMessages,\n    unsubscribeFromMessages,\n  } = useChatStore();\n  const { authUser } = useAuthStore();\n  const messageEndRef = useRef(null);\n\n  useEffect(() => {\n    getMessagesByUserId(selectedUser._id);\n    subscribeToMessages();\n\n    // clean up\n    return () => unsubscribeFromMessages();\n  }, [selectedUser, getMessagesByUserId, subscribeToMessages, unsubscribeFromMessages]);\n\n  useEffect(() => {\n    if (messageEndRef.current) {\n      messageEndRef.current.scrollIntoView({ behavior: \"smooth\" });\n    }\n  }, [messages]);\n\n  return (\n    <>\n      <ChatHeader />\n      <div className=\"flex-1 px-6 overflow-y-auto py-8\">\n        {messages.length > 0 && !isMessagesLoading ? (\n          <div className=\"max-w-3xl mx-auto space-y-6\">\n            {messages.map((msg) => (\n              <div\n                key={msg._id}\n                className={`chat ${msg.senderId === authUser._id ? \"chat-end\" : \"chat-start\"}`}\n              >\n                <div\n                  className={`chat-bubble relative ${\n                    msg.senderId === authUser._id\n                      ? \"bg-cyan-600 text-white\"\n                      : \"bg-slate-800 text-slate-200\"\n                  }`}\n                >\n                  {msg.image && (\n                    <img src={msg.image} alt=\"Shared\" className=\"rounded-lg h-48 object-cover\" />\n                  )}\n                  {msg.text && <p className=\"mt-2\">{msg.text}</p>}\n                  <p className=\"text-xs mt-1 opacity-75 flex items-center gap-1\">\n                    {new Date(msg.createdAt).toLocaleTimeString(undefined, {\n                      hour: \"2-digit\",\n                      minute: \"2-digit\",\n                    })}\n                  </p>\n                </div>\n              </div>\n            ))}\n            {/* 👇 scroll target */}\n            <div ref={messageEndRef} />\n          </div>\n        ) : isMessagesLoading ? (\n          <MessagesLoadingSkeleton />\n        ) : (\n          <NoChatHistoryPlaceholder name={selectedUser.fullName} />\n        )}\n      </div>\n\n      <MessageInput />\n    </>\n  );\n}\n\nexport default ChatContainer;\n"
  },
  {
    "path": "frontend/src/components/ChatHeader.jsx",
    "content": "import { XIcon } from \"lucide-react\";\nimport { useChatStore } from \"../store/useChatStore\";\nimport { useEffect } from \"react\";\nimport { useAuthStore } from \"../store/useAuthStore\";\n\nfunction ChatHeader() {\n  const { selectedUser, setSelectedUser } = useChatStore();\n  const { onlineUsers } = useAuthStore();\n  const isOnline = onlineUsers.includes(selectedUser._id);\n\n  useEffect(() => {\n    const handleEscKey = (event) => {\n      if (event.key === \"Escape\") setSelectedUser(null);\n    };\n\n    window.addEventListener(\"keydown\", handleEscKey);\n\n    // cleanup function\n    return () => window.removeEventListener(\"keydown\", handleEscKey);\n  }, [setSelectedUser]);\n\n  return (\n    <div\n      className=\"flex justify-between items-center bg-slate-800/50 border-b\n   border-slate-700/50 max-h-[84px] px-6 flex-1\"\n    >\n      <div className=\"flex items-center space-x-3\">\n        <div className={`avatar ${isOnline ? \"online\" : \"offline\"}`}>\n          <div className=\"w-12 rounded-full\">\n            <img src={selectedUser.profilePic || \"/avatar.png\"} alt={selectedUser.fullName} />\n          </div>\n        </div>\n\n        <div>\n          <h3 className=\"text-slate-200 font-medium\">{selectedUser.fullName}</h3>\n          <p className=\"text-slate-400 text-sm\">{isOnline ? \"Online\" : \"Offline\"}</p>\n        </div>\n      </div>\n\n      <button onClick={() => setSelectedUser(null)}>\n        <XIcon className=\"w-5 h-5 text-slate-400 hover:text-slate-200 transition-colors cursor-pointer\" />\n      </button>\n    </div>\n  );\n}\nexport default ChatHeader;\n"
  },
  {
    "path": "frontend/src/components/ChatsList.jsx",
    "content": "import { useEffect } from \"react\";\nimport { useChatStore } from \"../store/useChatStore\";\nimport UsersLoadingSkeleton from \"./UsersLoadingSkeleton\";\nimport NoChatsFound from \"./NoChatsFound\";\nimport { useAuthStore } from \"../store/useAuthStore\";\n\nfunction ChatsList() {\n  const { getMyChatPartners, chats, isUsersLoading, setSelectedUser } = useChatStore();\n  const { onlineUsers } = useAuthStore();\n\n  useEffect(() => {\n    getMyChatPartners();\n  }, [getMyChatPartners]);\n\n  if (isUsersLoading) return <UsersLoadingSkeleton />;\n  if (chats.length === 0) return <NoChatsFound />;\n\n  return (\n    <>\n      {chats.map((chat) => (\n        <div\n          key={chat._id}\n          className=\"bg-cyan-500/10 p-4 rounded-lg cursor-pointer hover:bg-cyan-500/20 transition-colors\"\n          onClick={() => setSelectedUser(chat)}\n        >\n          <div className=\"flex items-center gap-3\">\n            <div className={`avatar ${onlineUsers.includes(chat._id) ? \"online\" : \"offline\"}`}>\n              <div className=\"size-12 rounded-full\">\n                <img src={chat.profilePic || \"/avatar.png\"} alt={chat.fullName} />\n              </div>\n            </div>\n            <h4 className=\"text-slate-200 font-medium truncate\">{chat.fullName}</h4>\n          </div>\n        </div>\n      ))}\n    </>\n  );\n}\nexport default ChatsList;\n"
  },
  {
    "path": "frontend/src/components/ContactList.jsx",
    "content": "import { useEffect } from \"react\";\nimport { useChatStore } from \"../store/useChatStore\";\nimport UsersLoadingSkeleton from \"./UsersLoadingSkeleton\";\nimport { useAuthStore } from \"../store/useAuthStore\";\n\nfunction ContactList() {\n  const { getAllContacts, allContacts, setSelectedUser, isUsersLoading } = useChatStore();\n  const { onlineUsers } = useAuthStore();\n\n  useEffect(() => {\n    getAllContacts();\n  }, [getAllContacts]);\n\n  if (isUsersLoading) return <UsersLoadingSkeleton />;\n\n  return (\n    <>\n      {allContacts.map((contact) => (\n        <div\n          key={contact._id}\n          className=\"bg-cyan-500/10 p-4 rounded-lg cursor-pointer hover:bg-cyan-500/20 transition-colors\"\n          onClick={() => setSelectedUser(contact)}\n        >\n          <div className=\"flex items-center gap-3\">\n            <div className={`avatar ${onlineUsers.includes(contact._id) ? \"online\" : \"offline\"}`}>\n              <div className=\"size-12 rounded-full\">\n                <img src={contact.profilePic || \"/avatar.png\"} />\n              </div>\n            </div>\n            <h4 className=\"text-slate-200 font-medium\">{contact.fullName}</h4>\n          </div>\n        </div>\n      ))}\n    </>\n  );\n}\nexport default ContactList;\n"
  },
  {
    "path": "frontend/src/components/MessageInput.jsx",
    "content": "import { useRef, useState } from \"react\";\nimport useKeyboardSound from \"../hooks/useKeyboardSound\";\nimport { useChatStore } from \"../store/useChatStore\";\nimport toast from \"react-hot-toast\";\nimport { ImageIcon, SendIcon, XIcon } from \"lucide-react\";\n\nfunction MessageInput() {\n  const { playRandomKeyStrokeSound } = useKeyboardSound();\n  const [text, setText] = useState(\"\");\n  const [imagePreview, setImagePreview] = useState(null);\n\n  const fileInputRef = useRef(null);\n\n  const { sendMessage, isSoundEnabled } = useChatStore();\n\n  const handleSendMessage = (e) => {\n    e.preventDefault();\n    if (!text.trim() && !imagePreview) return;\n    if (isSoundEnabled) playRandomKeyStrokeSound();\n\n    sendMessage({\n      text: text.trim(),\n      image: imagePreview,\n    });\n    setText(\"\");\n    setImagePreview(\"\");\n    if (fileInputRef.current) fileInputRef.current.value = \"\";\n  };\n\n  const handleImageChange = (e) => {\n    const file = e.target.files[0];\n    if (!file.type.startsWith(\"image/\")) {\n      toast.error(\"Please select an image file\");\n      return;\n    }\n\n    const reader = new FileReader();\n    reader.onloadend = () => setImagePreview(reader.result);\n    reader.readAsDataURL(file);\n  };\n\n  const removeImage = () => {\n    setImagePreview(null);\n    if (fileInputRef.current) fileInputRef.current.value = \"\";\n  };\n\n  return (\n    <div className=\"p-4 border-t border-slate-700/50\">\n      {imagePreview && (\n        <div className=\"max-w-3xl mx-auto mb-3 flex items-center\">\n          <div className=\"relative\">\n            <img\n              src={imagePreview}\n              alt=\"Preview\"\n              className=\"w-20 h-20 object-cover rounded-lg border border-slate-700\"\n            />\n            <button\n              onClick={removeImage}\n              className=\"absolute -top-2 -right-2 w-6 h-6 rounded-full bg-slate-800 flex items-center justify-center text-slate-200 hover:bg-slate-700\"\n              type=\"button\"\n            >\n              <XIcon className=\"w-4 h-4\" />\n            </button>\n          </div>\n        </div>\n      )}\n\n      <form onSubmit={handleSendMessage} className=\"max-w-3xl mx-auto flex space-x-4\">\n        <input\n          type=\"text\"\n          value={text}\n          onChange={(e) => {\n            setText(e.target.value);\n            isSoundEnabled && playRandomKeyStrokeSound();\n          }}\n          className=\"flex-1 bg-slate-800/50 border border-slate-700/50 rounded-lg py-2 px-4\"\n          placeholder=\"Type your message...\"\n        />\n\n        <input\n          type=\"file\"\n          accept=\"image/*\"\n          ref={fileInputRef}\n          onChange={handleImageChange}\n          className=\"hidden\"\n        />\n\n        <button\n          type=\"button\"\n          onClick={() => fileInputRef.current?.click()}\n          className={`bg-slate-800/50 text-slate-400 hover:text-slate-200 rounded-lg px-4 transition-colors ${\n            imagePreview ? \"text-cyan-500\" : \"\"\n          }`}\n        >\n          <ImageIcon className=\"w-5 h-5\" />\n        </button>\n        <button\n          type=\"submit\"\n          disabled={!text.trim() && !imagePreview}\n          className=\"bg-gradient-to-r from-cyan-500 to-cyan-600 text-white rounded-lg px-4 py-2 font-medium hover:from-cyan-600 hover:to-cyan-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed\"\n        >\n          <SendIcon className=\"w-5 h-5\" />\n        </button>\n      </form>\n    </div>\n  );\n}\nexport default MessageInput;\n"
  },
  {
    "path": "frontend/src/components/MessagesLoadingSkeleton.jsx",
    "content": "function MessagesLoadingSkeleton() {\n  return (\n    <div className=\"max-w-3xl mx-auto space-y-6\">\n      {[...Array(6)].map((_, index) => (\n        <div\n          key={index}\n          className={`chat ${index % 2 === 0 ? \"chat-start\" : \"chat-end\"} animate-pulse`}\n        >\n          <div className={`chat-bubble bg-slate-800 text-white w-32`}></div>\n        </div>\n      ))}\n    </div>\n  );\n}\nexport default MessagesLoadingSkeleton;\n"
  },
  {
    "path": "frontend/src/components/NoChatHistoryPlaceholder.jsx",
    "content": "import { MessageCircleIcon } from \"lucide-react\";\n\nconst NoChatHistoryPlaceholder = ({ name }) => {\n  return (\n    <div className=\"flex flex-col items-center justify-center h-full text-center p-6\">\n      <div className=\"w-16 h-16 bg-gradient-to-br from-cyan-500/20 to-cyan-400/10 rounded-full flex items-center justify-center mb-5\">\n        <MessageCircleIcon className=\"size-8 text-cyan-400\" />\n      </div>\n      <h3 className=\"text-lg font-medium text-slate-200 mb-3\">\n        Start your conversation with {name}\n      </h3>\n      <div className=\"flex flex-col space-y-3 max-w-md mb-5\">\n        <p className=\"text-slate-400 text-sm\">\n          This is the beginning of your conversation. Send a message to start chatting!\n        </p>\n        <div className=\"h-px w-32 bg-gradient-to-r from-transparent via-cyan-500/30 to-transparent mx-auto\"></div>\n      </div>\n      <div className=\"flex flex-wrap gap-2 justify-center\">\n        <button className=\"px-4 py-2 text-xs font-medium text-cyan-400 bg-cyan-500/10 rounded-full hover:bg-cyan-500/20 transition-colors\">\n          👋 Say Hello\n        </button>\n        <button className=\"px-4 py-2 text-xs font-medium text-cyan-400 bg-cyan-500/10 rounded-full hover:bg-cyan-500/20 transition-colors\">\n          🤝 How are you?\n        </button>\n        <button className=\"px-4 py-2 text-xs font-medium text-cyan-400 bg-cyan-500/10 rounded-full hover:bg-cyan-500/20 transition-colors\">\n          📅 Meet up soon?\n        </button>\n      </div>\n    </div>\n  );\n};\n\nexport default NoChatHistoryPlaceholder;\n"
  },
  {
    "path": "frontend/src/components/NoChatsFound.jsx",
    "content": "import { MessageCircleIcon } from \"lucide-react\";\nimport { useChatStore } from \"../store/useChatStore\";\n\nfunction NoChatsFound() {\n  const { setActiveTab } = useChatStore();\n\n  return (\n    <div className=\"flex flex-col items-center justify-center py-10 text-center space-y-4\">\n      <div className=\"w-16 h-16 bg-cyan-500/10 rounded-full flex items-center justify-center\">\n        <MessageCircleIcon className=\"w-8 h-8 text-cyan-400\" />\n      </div>\n      <div>\n        <h4 className=\"text-slate-200 font-medium mb-1\">No conversations yet</h4>\n        <p className=\"text-slate-400 text-sm px-6\">\n          Start a new chat by selecting a contact from the contacts tab\n        </p>\n      </div>\n      <button\n        onClick={() => setActiveTab(\"contacts\")}\n        className=\"px-4 py-2 text-sm text-cyan-400 bg-cyan-500/10 rounded-lg hover:bg-cyan-500/20 transition-colors\"\n      >\n        Find contacts\n      </button>\n    </div>\n  );\n}\nexport default NoChatsFound;\n"
  },
  {
    "path": "frontend/src/components/NoConversationPlaceholder.jsx",
    "content": "import { MessageCircleIcon } from \"lucide-react\";\n\nconst NoConversationPlaceholder = () => {\n  return (\n    <div className=\"flex flex-col items-center justify-center h-full text-center p-6\">\n      <div className=\"size-20 bg-cyan-500/20 rounded-full flex items-center justify-center mb-6\">\n        <MessageCircleIcon className=\"size-10 text-cyan-400\" />\n      </div>\n      <h3 className=\"text-xl font-semibold text-slate-200 mb-2\">Select a conversation</h3>\n      <p className=\"text-slate-400 max-w-md\">\n        Choose a contact from the sidebar to start chatting or continue a previous conversation.\n      </p>\n    </div>\n  );\n};\n\nexport default NoConversationPlaceholder;\n"
  },
  {
    "path": "frontend/src/components/PageLoader.jsx",
    "content": "import { LoaderIcon } from \"lucide-react\";\nfunction PageLoader() {\n  return (\n    <div className=\"flex items-center justify-center h-screen\">\n      <LoaderIcon className=\"size-10 animate-spin\" />\n    </div>\n  );\n}\nexport default PageLoader;\n"
  },
  {
    "path": "frontend/src/components/ProfileHeader.jsx",
    "content": "import { useState, useRef } from \"react\";\nimport { LogOutIcon, VolumeOffIcon, Volume2Icon } from \"lucide-react\";\nimport { useAuthStore } from \"../store/useAuthStore\";\nimport { useChatStore } from \"../store/useChatStore\";\n\nconst mouseClickSound = new Audio(\"/sounds/mouse-click.mp3\");\n\nfunction ProfileHeader() {\n  const { logout, authUser, updateProfile } = useAuthStore();\n  const { isSoundEnabled, toggleSound } = useChatStore();\n  const [selectedImg, setSelectedImg] = useState(null);\n\n  const fileInputRef = useRef(null);\n\n  const handleImageUpload = (e) => {\n    const file = e.target.files[0];\n    if (!file) return;\n\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n\n    reader.onloadend = async () => {\n      const base64Image = reader.result;\n      setSelectedImg(base64Image);\n      await updateProfile({ profilePic: base64Image });\n    };\n  };\n\n  return (\n    <div className=\"p-6 border-b border-slate-700/50\">\n      <div className=\"flex items-center justify-between\">\n        <div className=\"flex items-center gap-3\">\n          {/* AVATAR */}\n          <div className=\"avatar online\">\n            <button\n              className=\"size-14 rounded-full overflow-hidden relative group\"\n              onClick={() => fileInputRef.current.click()}\n            >\n              <img\n                src={selectedImg || authUser.profilePic || \"/avatar.png\"}\n                alt=\"User image\"\n                className=\"size-full object-cover\"\n              />\n              <div className=\"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity\">\n                <span className=\"text-white text-xs\">Change</span>\n              </div>\n            </button>\n\n            <input\n              type=\"file\"\n              accept=\"image/*\"\n              ref={fileInputRef}\n              onChange={handleImageUpload}\n              className=\"hidden\"\n            />\n          </div>\n\n          {/* USERNAME & ONLINE TEXT */}\n          <div>\n            <h3 className=\"text-slate-200 font-medium text-base max-w-[180px] truncate\">\n              {authUser.fullName}\n            </h3>\n\n            <p className=\"text-slate-400 text-xs\">Online</p>\n          </div>\n        </div>\n\n        {/* BUTTONS */}\n        <div className=\"flex gap-4 items-center\">\n          {/* LOGOUT BTN */}\n          <button\n            className=\"text-slate-400 hover:text-slate-200 transition-colors\"\n            onClick={logout}\n          >\n            <LogOutIcon className=\"size-5\" />\n          </button>\n\n          {/* SOUND TOGGLE BTN */}\n          <button\n            className=\"text-slate-400 hover:text-slate-200 transition-colors\"\n            onClick={() => {\n              // play click sound before toggling\n              mouseClickSound.currentTime = 0; // reset to start\n              mouseClickSound.play().catch((error) => console.log(\"Audio play failed:\", error));\n              toggleSound();\n            }}\n          >\n            {isSoundEnabled ? (\n              <Volume2Icon className=\"size-5\" />\n            ) : (\n              <VolumeOffIcon className=\"size-5\" />\n            )}\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\nexport default ProfileHeader;\n"
  },
  {
    "path": "frontend/src/components/UsersLoadingSkeleton.jsx",
    "content": "function UsersLoadingSkeleton() {\n  return (\n    <div className=\"space-y-2\">\n      {[1, 2, 3].map((item) => (\n        <div key={item} className=\"bg-slate-800/30 p-4 rounded-lg animate-pulse\">\n          <div className=\"flex items-center space-x-3\">\n            <div className=\"w-12 h-12 bg-slate-700 rounded-full\"></div>\n            <div className=\"flex-1\">\n              <div className=\"h-4 bg-slate-700 rounded w-3/4 mb-2\"></div>\n              <div className=\"h-3 bg-slate-700/70 rounded w-1/2\"></div>\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\nexport default UsersLoadingSkeleton;\n"
  },
  {
    "path": "frontend/src/hooks/useKeyboardSound.js",
    "content": "// audio setup\nconst keyStrokeSounds = [\n  new Audio(\"/sounds/keystroke1.mp3\"),\n  new Audio(\"/sounds/keystroke2.mp3\"),\n  new Audio(\"/sounds/keystroke3.mp3\"),\n  new Audio(\"/sounds/keystroke4.mp3\"),\n];\n\nfunction useKeyboardSound() {\n  const playRandomKeyStrokeSound = () => {\n    const randomSound = keyStrokeSounds[Math.floor(Math.random() * keyStrokeSounds.length)];\n\n    randomSound.currentTime = 0; // this is for a better UX, def add this\n    randomSound.play().catch((error) => console.log(\"Audio play failed:\", error));\n  };\n\n  return { playRandomKeyStrokeSound };\n}\n\nexport default useKeyboardSound;\n"
  },
  {
    "path": "frontend/src/index.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@property --border-angle {\n  inherits: false;\n  initial-value: 0deg;\n  syntax: \"<angle>\";\n}\n\n.input {\n  @apply w-full bg-slate-800/50 border border-slate-700 rounded-lg py-2 pl-10 pr-4 text-slate-200 placeholder-slate-400 focus:ring-2 focus:ring-cyan-500 focus:border-transparent;\n}\n\n.auth-input-icon {\n  @apply absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 size-5;\n}\n\n.auth-input-label {\n  @apply block text-sm font-medium text-slate-300 mb-2;\n}\n\n.auth-badge {\n  @apply inline-flex items-center px-3 py-1 text-xs font-medium rounded-full bg-cyan-500/20 text-cyan-300;\n}\n\n.auth-btn {\n  @apply w-full bg-cyan-500 text-white rounded-lg py-2.5 font-medium\n  hover:bg-cyan-600 focus:ring-2 focus:ring-cyan-500;\n}\n\n.auth-link {\n  @apply px-4 py-2 inline-block bg-cyan-500/10 rounded-lg text-cyan-400\n  hover:text-cyan-500 text-sm transition-colors;\n}\n"
  },
  {
    "path": "frontend/src/lib/axios.js",
    "content": "import axios from \"axios\";\n\nexport const axiosInstance = axios.create({\n  baseURL: import.meta.env.MODE === \"development\" ? \"http://localhost:3000/api\" : \"/api\",\n  withCredentials: true,\n});\n"
  },
  {
    "path": "frontend/src/main.jsx",
    "content": "import { StrictMode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport \"./index.css\";\nimport App from \"./App.jsx\";\nimport { BrowserRouter } from \"react-router\";\n\ncreateRoot(document.getElementById(\"root\")).render(\n  <StrictMode>\n    <BrowserRouter>\n      <App />\n    </BrowserRouter>\n  </StrictMode>\n);\n"
  },
  {
    "path": "frontend/src/pages/ChatPage.jsx",
    "content": "import { useChatStore } from \"../store/useChatStore\";\n\nimport BorderAnimatedContainer from \"../components/BorderAnimatedContainer\";\nimport ProfileHeader from \"../components/ProfileHeader\";\nimport ActiveTabSwitch from \"../components/ActiveTabSwitch\";\nimport ChatsList from \"../components/ChatsList\";\nimport ContactList from \"../components/ContactList\";\nimport ChatContainer from \"../components/ChatContainer\";\nimport NoConversationPlaceholder from \"../components/NoConversationPlaceholder\";\n\nfunction ChatPage() {\n  const { activeTab, selectedUser } = useChatStore();\n\n  return (\n    <div className=\"relative w-full max-w-6xl h-[800px]\">\n      <BorderAnimatedContainer>\n        {/* LEFT SIDE */}\n        <div className=\"w-80 bg-slate-800/50 backdrop-blur-sm flex flex-col\">\n          <ProfileHeader />\n          <ActiveTabSwitch />\n\n          <div className=\"flex-1 overflow-y-auto p-4 space-y-2\">\n            {activeTab === \"chats\" ? <ChatsList /> : <ContactList />}\n          </div>\n        </div>\n\n        {/* RIGHT SIDE */}\n        <div className=\"flex-1 flex flex-col bg-slate-900/50 backdrop-blur-sm\">\n          {selectedUser ? <ChatContainer /> : <NoConversationPlaceholder />}\n        </div>\n      </BorderAnimatedContainer>\n    </div>\n  );\n}\nexport default ChatPage;\n"
  },
  {
    "path": "frontend/src/pages/LoginPage.jsx",
    "content": "import { useState } from \"react\";\nimport { useAuthStore } from \"../store/useAuthStore\";\nimport BorderAnimatedContainer from \"../components/BorderAnimatedContainer\";\nimport { MessageCircleIcon, MailIcon, LoaderIcon, LockIcon } from \"lucide-react\";\nimport { Link } from \"react-router\";\n\nfunction LoginPage() {\n  const [formData, setFormData] = useState({ email: \"\", password: \"\" });\n  const { login, isLoggingIn } = useAuthStore();\n\n  const handleSubmit = (e) => {\n    e.preventDefault();\n    login(formData);\n  };\n\n  return (\n    <div className=\"w-full flex items-center justify-center p-4 bg-slate-900\">\n      <div className=\"relative w-full max-w-6xl md:h-[800px] h-[650px]\">\n        <BorderAnimatedContainer>\n          <div className=\"w-full flex flex-col md:flex-row\">\n            {/* FORM CLOUMN - LEFT SIDE */}\n            <div className=\"md:w-1/2 p-8 flex items-center justify-center md:border-r border-slate-600/30\">\n              <div className=\"w-full max-w-md\">\n                {/* HEADING TEXT */}\n                <div className=\"text-center mb-8\">\n                  <MessageCircleIcon className=\"w-12 h-12 mx-auto text-slate-400 mb-4\" />\n                  <h2 className=\"text-2xl font-bold text-slate-200 mb-2\">Welcome Back</h2>\n                  <p className=\"text-slate-400\">Login to access to your account</p>\n                </div>\n\n                {/* FORM */}\n                <form onSubmit={handleSubmit} className=\"space-y-6\">\n                  {/* EMAIL INPUT */}\n                  <div>\n                    <label className=\"auth-input-label\">Email</label>\n                    <div className=\"relative\">\n                      <MailIcon className=\"auth-input-icon\" />\n\n                      <input\n                        type=\"email\"\n                        value={formData.email}\n                        onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n                        className=\"input\"\n                        placeholder=\"johndoe@gmail.com\"\n                      />\n                    </div>\n                  </div>\n\n                  {/* PASSWORD INPUT */}\n                  <div>\n                    <label className=\"auth-input-label\">Password</label>\n                    <div className=\"relative\">\n                      <LockIcon className=\"auth-input-icon\" />\n\n                      <input\n                        type=\"password\"\n                        value={formData.password}\n                        onChange={(e) => setFormData({ ...formData, password: e.target.value })}\n                        className=\"input\"\n                        placeholder=\"Enter your password\"\n                      />\n                    </div>\n                  </div>\n\n                  {/* SUBMIT BUTTON */}\n                  <button className=\"auth-btn\" type=\"submit\" disabled={isLoggingIn}>\n                    {isLoggingIn ? (\n                      <LoaderIcon className=\"w-full h-5 animate-spin text-center\" />\n                    ) : (\n                      \"Sign In\"\n                    )}\n                  </button>\n                </form>\n\n                <div className=\"mt-6 text-center\">\n                  <Link to=\"/signup\" className=\"auth-link\">\n                    Don't have an account? Sign Up\n                  </Link>\n                </div>\n              </div>\n            </div>\n\n            {/* FORM ILLUSTRATION - RIGHT SIDE */}\n            <div className=\"hidden md:w-1/2 md:flex items-center justify-center p-6 bg-gradient-to-bl from-slate-800/20 to-transparent\">\n              <div>\n                <img\n                  src=\"/login.png\"\n                  alt=\"People using mobile devices\"\n                  className=\"w-full h-auto object-contain\"\n                />\n                <div className=\"mt-6 text-center\">\n                  <h3 className=\"text-xl font-medium text-cyan-400\">Connect anytime, anywhere</h3>\n\n                  <div className=\"mt-4 flex justify-center gap-4\">\n                    <span className=\"auth-badge\">Free</span>\n                    <span className=\"auth-badge\">Easy Setup</span>\n                    <span className=\"auth-badge\">Private</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </BorderAnimatedContainer>\n      </div>\n    </div>\n  );\n}\nexport default LoginPage;\n"
  },
  {
    "path": "frontend/src/pages/SignUpPage.jsx",
    "content": "import { useState } from \"react\";\nimport { useAuthStore } from \"../store/useAuthStore\";\nimport BorderAnimatedContainer from \"../components/BorderAnimatedContainer\";\nimport { MessageCircleIcon, LockIcon, MailIcon, UserIcon, LoaderIcon } from \"lucide-react\";\nimport { Link } from \"react-router\";\n\nfunction SignUpPage() {\n  const [formData, setFormData] = useState({ fullName: \"\", email: \"\", password: \"\" });\n  const { signup, isSigningUp } = useAuthStore();\n\n  const handleSubmit = (e) => {\n    e.preventDefault();\n    signup(formData);\n  };\n\n  return (\n    <div className=\"w-full flex items-center justify-center p-4 bg-slate-900\">\n      <div className=\"relative w-full max-w-6xl md:h-[800px] h-[650px]\">\n        <BorderAnimatedContainer>\n          <div className=\"w-full flex flex-col md:flex-row\">\n            {/* FORM CLOUMN - LEFT SIDE */}\n            <div className=\"md:w-1/2 p-8 flex items-center justify-center md:border-r border-slate-600/30\">\n              <div className=\"w-full max-w-md\">\n                {/* HEADING TEXT */}\n                <div className=\"text-center mb-8\">\n                  <MessageCircleIcon className=\"w-12 h-12 mx-auto text-slate-400 mb-4\" />\n                  <h2 className=\"text-2xl font-bold text-slate-200 mb-2\">Create Account</h2>\n                  <p className=\"text-slate-400\">Sign up for a new account</p>\n                </div>\n\n                {/* FORM */}\n                <form onSubmit={handleSubmit} className=\"space-y-6\">\n                  {/* FULL NAME */}\n                  <div>\n                    <label className=\"auth-input-label\">Full Name</label>\n                    <div className=\"relative\">\n                      <UserIcon className=\"auth-input-icon\" />\n\n                      <input\n                        type=\"text\"\n                        value={formData.fullName}\n                        onChange={(e) => setFormData({ ...formData, fullName: e.target.value })}\n                        className=\"input\"\n                        placeholder=\"John Doe\"\n                      />\n                    </div>\n                  </div>\n\n                  {/* EMAIL INPUT */}\n                  <div>\n                    <label className=\"auth-input-label\">Email</label>\n                    <div className=\"relative\">\n                      <MailIcon className=\"auth-input-icon\" />\n\n                      <input\n                        type=\"email\"\n                        value={formData.email}\n                        onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n                        className=\"input\"\n                        placeholder=\"johndoe@gmail.com\"\n                      />\n                    </div>\n                  </div>\n\n                  {/* PASSWORD INPUT */}\n                  <div>\n                    <label className=\"auth-input-label\">Password</label>\n                    <div className=\"relative\">\n                      <LockIcon className=\"auth-input-icon\" />\n\n                      <input\n                        type=\"password\"\n                        value={formData.password}\n                        onChange={(e) => setFormData({ ...formData, password: e.target.value })}\n                        className=\"input\"\n                        placeholder=\"Enter your password\"\n                      />\n                    </div>\n                  </div>\n\n                  {/* SUBMIT BUTTON */}\n                  <button className=\"auth-btn\" type=\"submit\" disabled={isSigningUp}>\n                    {isSigningUp ? (\n                      <LoaderIcon className=\"w-full h-5 animate-spin text-center\" />\n                    ) : (\n                      \"Create Account\"\n                    )}\n                  </button>\n                </form>\n\n                <div className=\"mt-6 text-center\">\n                  <Link to=\"/login\" className=\"auth-link\">\n                    Already have an account? Login\n                  </Link>\n                </div>\n              </div>\n            </div>\n\n            {/* FORM ILLUSTRATION - RIGHT SIDE */}\n            <div className=\"hidden md:w-1/2 md:flex items-center justify-center p-6 bg-gradient-to-bl from-slate-800/20 to-transparent\">\n              <div>\n                <img\n                  src=\"/signup.png\"\n                  alt=\"People using mobile devices\"\n                  className=\"w-full h-auto object-contain\"\n                />\n                <div className=\"mt-6 text-center\">\n                  <h3 className=\"text-xl font-medium text-cyan-400\">Start Your Journey Today</h3>\n\n                  <div className=\"mt-4 flex justify-center gap-4\">\n                    <span className=\"auth-badge\">Free</span>\n                    <span className=\"auth-badge\">Easy Setup</span>\n                    <span className=\"auth-badge\">Private</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </BorderAnimatedContainer>\n      </div>\n    </div>\n  );\n}\nexport default SignUpPage;\n"
  },
  {
    "path": "frontend/src/store/useAuthStore.js",
    "content": "import { create } from \"zustand\";\nimport { axiosInstance } from \"../lib/axios\";\nimport toast from \"react-hot-toast\";\nimport { io } from \"socket.io-client\";\n\nconst BASE_URL = import.meta.env.MODE === \"development\" ? \"http://localhost:3000\" : \"/\";\n\nexport const useAuthStore = create((set, get) => ({\n  authUser: null,\n  isCheckingAuth: true,\n  isSigningUp: false,\n  isLoggingIn: false,\n  socket: null,\n  onlineUsers: [],\n\n  checkAuth: async () => {\n    try {\n      const res = await axiosInstance.get(\"/auth/check\");\n      set({ authUser: res.data });\n      get().connectSocket();\n    } catch (error) {\n      console.log(\"Error in authCheck:\", error);\n      set({ authUser: null });\n    } finally {\n      set({ isCheckingAuth: false });\n    }\n  },\n\n  signup: async (data) => {\n    set({ isSigningUp: true });\n    try {\n      const res = await axiosInstance.post(\"/auth/signup\", data);\n      set({ authUser: res.data });\n\n      toast.success(\"Account created successfully!\");\n      get().connectSocket();\n    } catch (error) {\n      toast.error(error.response.data.message);\n    } finally {\n      set({ isSigningUp: false });\n    }\n  },\n\n  login: async (data) => {\n    set({ isLoggingIn: true });\n    try {\n      const res = await axiosInstance.post(\"/auth/login\", data);\n      set({ authUser: res.data });\n\n      toast.success(\"Logged in successfully\");\n\n      get().connectSocket();\n    } catch (error) {\n      toast.error(error.response.data.message);\n    } finally {\n      set({ isLoggingIn: false });\n    }\n  },\n\n  logout: async () => {\n    try {\n      await axiosInstance.post(\"/auth/logout\");\n      set({ authUser: null });\n      toast.success(\"Logged out successfully\");\n      get().disconnectSocket();\n    } catch (error) {\n      toast.error(\"Error logging out\");\n      console.log(\"Logout error:\", error);\n    }\n  },\n\n  updateProfile: async (data) => {\n    try {\n      const res = await axiosInstance.put(\"/auth/update-profile\", data);\n      set({ authUser: res.data });\n      toast.success(\"Profile updated successfully\");\n    } catch (error) {\n      console.log(\"Error in update profile:\", error);\n      toast.error(error.response.data.message);\n    }\n  },\n\n  connectSocket: () => {\n    const { authUser } = get();\n    if (!authUser || get().socket?.connected) return;\n\n    const socket = io(BASE_URL, {\n      withCredentials: true, // this ensures cookies are sent with the connection\n    });\n\n    socket.connect();\n\n    set({ socket });\n\n    // listen for online users event\n    socket.on(\"getOnlineUsers\", (userIds) => {\n      set({ onlineUsers: userIds });\n    });\n  },\n\n  disconnectSocket: () => {\n    if (get().socket?.connected) get().socket.disconnect();\n  },\n}));\n"
  },
  {
    "path": "frontend/src/store/useChatStore.js",
    "content": "import { create } from \"zustand\";\nimport { axiosInstance } from \"../lib/axios\";\nimport toast from \"react-hot-toast\";\nimport { useAuthStore } from \"./useAuthStore\";\n\nexport const useChatStore = create((set, get) => ({\n  allContacts: [],\n  chats: [],\n  messages: [],\n  activeTab: \"chats\",\n  selectedUser: null,\n  isUsersLoading: false,\n  isMessagesLoading: false,\n  isSoundEnabled: JSON.parse(localStorage.getItem(\"isSoundEnabled\")) === true,\n\n  toggleSound: () => {\n    localStorage.setItem(\"isSoundEnabled\", !get().isSoundEnabled);\n    set({ isSoundEnabled: !get().isSoundEnabled });\n  },\n\n  setActiveTab: (tab) => set({ activeTab: tab }),\n  setSelectedUser: (selectedUser) => set({ selectedUser }),\n\n  getAllContacts: async () => {\n    set({ isUsersLoading: true });\n    try {\n      const res = await axiosInstance.get(\"/messages/contacts\");\n      set({ allContacts: res.data });\n    } catch (error) {\n      toast.error(error.response.data.message);\n    } finally {\n      set({ isUsersLoading: false });\n    }\n  },\n  getMyChatPartners: async () => {\n    set({ isUsersLoading: true });\n    try {\n      const res = await axiosInstance.get(\"/messages/chats\");\n      set({ chats: res.data });\n    } catch (error) {\n      toast.error(error.response.data.message);\n    } finally {\n      set({ isUsersLoading: false });\n    }\n  },\n\n  getMessagesByUserId: async (userId) => {\n    set({ isMessagesLoading: true });\n    try {\n      const res = await axiosInstance.get(`/messages/${userId}`);\n      set({ messages: res.data });\n    } catch (error) {\n      toast.error(error.response?.data?.message || \"Something went wrong\");\n    } finally {\n      set({ isMessagesLoading: false });\n    }\n  },\n\n  sendMessage: async (messageData) => {\n    const { selectedUser, messages } = get();\n    const { authUser } = useAuthStore.getState();\n\n    const tempId = `temp-${Date.now()}`;\n\n    const optimisticMessage = {\n      _id: tempId,\n      senderId: authUser._id,\n      receiverId: selectedUser._id,\n      text: messageData.text,\n      image: messageData.image,\n      createdAt: new Date().toISOString(),\n      isOptimistic: true, // flag to identify optimistic messages (optional)\n    };\n    // immidetaly update the ui by adding the message\n    set({ messages: [...messages, optimisticMessage] });\n\n    try {\n      const res = await axiosInstance.post(`/messages/send/${selectedUser._id}`, messageData);\n      set({ messages: messages.concat(res.data) });\n    } catch (error) {\n      // remove optimistic message on failure\n      set({ messages: messages });\n      toast.error(error.response?.data?.message || \"Something went wrong\");\n    }\n  },\n\n  subscribeToMessages: () => {\n    const { selectedUser, isSoundEnabled } = get();\n    if (!selectedUser) return;\n\n    const socket = useAuthStore.getState().socket;\n\n    socket.on(\"newMessage\", (newMessage) => {\n      const isMessageSentFromSelectedUser = newMessage.senderId === selectedUser._id;\n      if (!isMessageSentFromSelectedUser) return;\n\n      const currentMessages = get().messages;\n      set({ messages: [...currentMessages, newMessage] });\n\n      if (isSoundEnabled) {\n        const notificationSound = new Audio(\"/sounds/notification.mp3\");\n\n        notificationSound.currentTime = 0; // reset to start\n        notificationSound.play().catch((e) => console.log(\"Audio play failed:\", e));\n      }\n    });\n  },\n\n  unsubscribeFromMessages: () => {\n    const socket = useAuthStore.getState().socket;\n    socket.off(\"newMessage\");\n  },\n}));\n"
  },
  {
    "path": "frontend/tailwind.config.js",
    "content": "import daisyui from \"daisyui\";\n\n/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [\"./index.html\", \"./src/**/*.{js,ts,jsx,tsx}\"],\n  theme: {\n    extend: {\n      animation: {\n        border: \"border 4s linear infinite\",\n      },\n      keyframes: {\n        border: {\n          to: { \"--border-angle\": \"360deg\" },\n        },\n      },\n    },\n  },\n  plugins: [daisyui],\n};\n"
  },
  {
    "path": "frontend/vite.config.js",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chatify-app\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"scripts\": {\n    \"build\": \"npm install --prefix backend && npm install --prefix frontend && npm run build --prefix frontend\",\n    \"start\": \"npm run start --prefix backend\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\"\n}\n"
  }
]