<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: v</title>
    <description>The latest articles on DEV Community by v (@v1f307be1b).</description>
    <link>https://dev.to/v1f307be1b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3722190%2F6b2b587a-f302-4b96-bd6d-186d490d65ff.png</url>
      <title>DEV Community: v</title>
      <link>https://dev.to/v1f307be1b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/v1f307be1b"/>
    <language>en</language>
    <item>
      <title>chat app</title>
      <dc:creator>v</dc:creator>
      <pubDate>Tue, 20 Jan 2026 17:29:36 +0000</pubDate>
      <link>https://dev.to/v1f307be1b/chat-app-4nh</link>
      <guid>https://dev.to/v1f307be1b/chat-app-4nh</guid>
      <description>&lt;p&gt;full-stack-chat-app/&lt;br&gt;
├── chat-app-backend/           # Node.js/Express Backend&lt;br&gt;
│   ├── node_modules/           # Backend dependencies&lt;br&gt;
│   ├── firebaseConfig.js       # Firebase Client SDK Configuration&lt;br&gt;
│   ├── package.json            # Backend scripts and dependencies&lt;br&gt;
│   ├── seed.js                 # Script to seed initial users and conversations&lt;br&gt;
│   ├── server.js               # Main Express server with Socket.io&lt;br&gt;
│   └── serviceAccountKey.json  # (Optional) Admin SDK key (deprecated in favor of Client SDK)&lt;br&gt;
│&lt;br&gt;
├── chat-app-frontend/          # React Frontend (Vite)&lt;br&gt;
│   ├── node_modules/           # Frontend dependencies&lt;br&gt;
│   ├── public/                 # Static assets&lt;br&gt;
│   ├── src/                    # Source code&lt;br&gt;
│   │   ├── assets/             # Images and local assets&lt;br&gt;
│   │   ├── App.css             # Component styles&lt;br&gt;
│   │   ├── App.jsx             # Main Chat Component&lt;br&gt;
│   │   ├── index.css           # Global styles&lt;br&gt;
│   │   └── main.jsx            # Entry point&lt;br&gt;
│   ├── index.html              # HTML template&lt;br&gt;
│   ├── package.json            # Frontend scripts and dependencies&lt;br&gt;
│   └── vite.config.js          # Vite configuration&lt;br&gt;
│&lt;br&gt;
└── firebase_schema.md          # Database schema documentation&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;firebaseConfig.js&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;code&gt;const firebaseConfig = {&lt;br&gt;
    apiKey: "REPLACE_WITH_API_KEY",&lt;br&gt;
    authDomain: "REPLACE_WITH_AUTH_DOMAIN",&lt;br&gt;
    projectId: "REPLACE_WITH_PROJECT_ID",&lt;br&gt;
    storageBucket: "REPLACE_WITH_STORAGE_BUCKET",&lt;br&gt;
    messagingSenderId: "REPLACE_WITH_MESSAGING_SENDER_ID",&lt;br&gt;
    appId: "REPLACE_WITH_APP_ID",&lt;br&gt;
    measurementId: "REPLACE_WITH_MEASUREMENT_ID"&lt;br&gt;
};&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
module.exports = firebaseConfig;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;seed.js &lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { initializeApp } = require('firebase/app');
const { getFirestore, collection, doc, setDoc } = require('firebase/firestore');

const firebaseConfig = require('./firebaseConfig');

try {
    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);

    const seedData = async () =&amp;gt; {
        console.log("Starting seed with Client SDK...");

        // Seed Users
        const users = [
            {
                uid: 'user1',
                username: '3nvky',
                profile_image: 'https://via.placeholder.com/150',
            },
            {
                uid: 'user2',
                username: 'GA7IU',
                profile_image: 'https://via.placeholder.com/150',
            }
        ];

        for (const user of users) {
            // setDoc with merge: true is slightly different in Web SDK
            // setDoc(doc(db, "cities", "LA"), data, { merge: true });
            await setDoc(doc(db, 'users', user.uid), user, { merge: true });
            console.log(`User ${user.username} created/updated.`);
        }

        // Seed a Conversation
        const conversationId = 'conv_user1_user2';
        const conversation = {
            conversationId: conversationId,
            user1: 'user1',
            user2: 'user2'
        };
        await setDoc(doc(db, 'conversations', conversationId), conversation, { merge: true });
        console.log(`Conversation ${conversationId} created.`);
    };

    seedData().then(() =&amp;gt; {
        console.log("Seeding complete.");
        process.exit(0);
    }).catch(err =&amp;gt; {
        console.error("Seeding failed:", err.message);
        if (err.message.includes("insufficient permissions")) {
            console.warn("WARNING: You are using Client SDK. Ensure Firestore Rules are open (test mode) or allow writes.");
        }
        process.exit(1);
    });

} catch (error) {
    console.error("Error initializing:", error);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;server.js&lt;/strong&gt;&lt;br&gt;
const express = require('express');&lt;br&gt;
const http = require('http');&lt;br&gt;
const { Server } = require("socket.io");&lt;br&gt;
const cors = require('cors');&lt;br&gt;
const dotenv = require('dotenv');&lt;br&gt;
const { initializeApp } = require('firebase/app');&lt;br&gt;
// // const { auth } = require('./firebaseConfig') // Removed unused auth import // Removed unused auth import&lt;br&gt;
const { getFirestore, collection, addDoc, getDocs, query, where, orderBy, serverTimestamp, setDoc, doc } = require('firebase/firestore');&lt;/p&gt;

&lt;p&gt;const firebaseConfig = require('./firebaseConfig');&lt;/p&gt;

&lt;p&gt;dotenv.config();&lt;/p&gt;

&lt;p&gt;const app = express();&lt;br&gt;
const server = http.createServer(app);&lt;br&gt;
const io = new Server(server, {&lt;br&gt;
    cors: {&lt;br&gt;
        origin: "*",&lt;br&gt;
        methods: ["GET", "POST"]&lt;br&gt;
    }&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;app.use(cors());&lt;br&gt;
// Increase limit for Base64 uploads&lt;br&gt;
app.use(express.json({ limit: '10mb' }));&lt;/p&gt;

&lt;p&gt;// --- Firebase Setup ---&lt;br&gt;
let db;&lt;br&gt;
try {&lt;br&gt;
    const appFirebase = initializeApp(firebaseConfig);&lt;br&gt;
    db = getFirestore(appFirebase);&lt;br&gt;
    console.log("Firebase initialized ");&lt;br&gt;
} catch (error) {&lt;br&gt;
    console.warn("Error initializing Firebase.");&lt;br&gt;
    console.error(error.message);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// --- Seed Users ---&lt;br&gt;
const seedUsers = async () =&amp;gt; {&lt;br&gt;
    if (!db) return;&lt;br&gt;
    try {&lt;br&gt;
        const usersRef = collection(db, 'users');&lt;br&gt;
        const snapshot = await getDocs(usersRef);&lt;br&gt;
        if (snapshot.empty) {&lt;br&gt;
            console.log("Seeding users...");&lt;br&gt;
            const users = [&lt;br&gt;
                { uid: 'user1', username: 'vivek', profile_image: '&lt;a href="https://via.placeholder.com/150?text=V" rel="noopener noreferrer"&gt;https://via.placeholder.com/150?text=V&lt;/a&gt;' },&lt;br&gt;
                { uid: 'user2', username: 'mihir', profile_image: '&lt;a href="https://via.placeholder.com/150?text=M" rel="noopener noreferrer"&gt;https://via.placeholder.com/150?text=M&lt;/a&gt;' }&lt;br&gt;
            ];&lt;br&gt;
            for (const user of users) {&lt;br&gt;
                // Use uid as doc ID for easy lookup if needed&lt;br&gt;
                await setDoc(doc(db, 'users', user.uid), user);&lt;br&gt;
            }&lt;br&gt;
            console.log("Users seeded.");&lt;br&gt;
        }&lt;br&gt;
    } catch (e) {&lt;br&gt;
        console.error("Error seeding users:", e);&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
seedUsers();&lt;/p&gt;

&lt;p&gt;// --- API Endpoints ---&lt;/p&gt;

&lt;p&gt;// 1. Get Users&lt;br&gt;
app.get('/api/users', async (req, res, next) =&amp;gt; {&lt;br&gt;
    if (!db) return res.json([]);&lt;br&gt;
    try {&lt;br&gt;
        const snapshot = await getDocs(collection(db, 'users'));&lt;br&gt;
        const users = [];&lt;br&gt;
        snapshot.forEach(doc =&amp;gt; users.push(doc.data()));&lt;br&gt;
        res.json(users);&lt;br&gt;
    } catch (e) {&lt;br&gt;
        next(e);&lt;br&gt;
    }&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const multer = require('multer');&lt;/p&gt;

&lt;p&gt;// Configure Multer (Memory Storage)&lt;br&gt;
const upload = multer({ storage: multer.memoryStorage() });&lt;/p&gt;

&lt;p&gt;// 2. Create Message (Handling Text + File from FormData)&lt;br&gt;
app.post('/api/messages', upload.single('file'), async (req, res, next) =&amp;gt; {&lt;br&gt;
    try {&lt;br&gt;
        const { conversationId, content, senderId } = req.body;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Handle file if present
    let fileBase64 = null;
    let fileName = null;
    let fileType = null;

    if (req.file) {
        fileBase64 = `data:${req.file.mimetype};base64,${req.file.buffer.toString('base64')}`;
        fileName = req.file.originalname;
        fileType = req.file.mimetype;
    }

    if (!conversationId || !senderId) {
        return res.status(400).json({ error: "Missing required fields" });
    }

    const messageData = {
        conversationId,
        content: content || "", // Content can be empty if file IS present
        senderId,
        message_at: serverTimestamp()
    };

    if (fileBase64) {
        messageData.fileBase64 = fileBase64;
        messageData.fileName = fileName;
        messageData.fileType = fileType;
    }

    let savedMessage = { ...messageData, message_at: new Date() };

    if (db) {
        const docRef = await addDoc(collection(db, 'messages'), messageData);
        savedMessage.messageId = docRef.id;
    } else {
        savedMessage.messageId = 'mock_id_' + Date.now();
    }

    // Emit via Socket.io for real-time
    io.to(conversationId).emit('receive_message', savedMessage);

    res.status(201).json(savedMessage);
} catch (e) {
    next(e);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;// 3. Message List&lt;br&gt;
app.get('/api/messages/:conversationId', async (req, res, next) =&amp;gt; {&lt;br&gt;
    try {&lt;br&gt;
        const { conversationId } = req.params;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    if (!db) {
        return res.json([]);
    }

    // Query
    const q = query(
        collection(db, 'messages'),
        where('conversationId', '==', conversationId),
        orderBy('message_at', 'asc')
    );

    const snapshot = await getDocs(q);
    const messages = [];
    snapshot.forEach(doc =&amp;gt; {
        messages.push({
            messageId: doc.id,
            ...doc.data()
        });
    });

    res.json(messages);
} catch (e) {
    next(e);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;// Global Error Handler&lt;br&gt;
app.use((err, req, res, next) =&amp;gt; {&lt;br&gt;
    console.error(err.stack);&lt;br&gt;
    res.status(500).json({ error: "Internal Server Error" });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// --- Socket.io ---&lt;br&gt;
io.on('connection', (socket) =&amp;gt; {&lt;br&gt;
    socket.on('join_conversation', (conversationId) =&amp;gt; {&lt;br&gt;
        socket.join(conversationId);&lt;br&gt;
        console.log(&lt;code&gt;User ${socket.id} joined conversation ${conversationId}&lt;/code&gt;);&lt;br&gt;
    });&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;socket.on('disconnect', () =&amp;gt; {
    // console.log('user disconnected', socket.id);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;const PORT = process.env.PORT || 3001;&lt;/p&gt;

&lt;p&gt;server.listen(PORT, () =&amp;gt; {&lt;br&gt;
    console.log(&lt;code&gt;SERVER RUNNING ON PORT ${PORT}&lt;/code&gt;);&lt;br&gt;
});&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;app.jsx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';
import axios from 'axios';
import { motion, AnimatePresence } from 'framer-motion';
import './index.css';

// Configuration
const SOCKET_URL = 'http://localhost:3001';
const API_URL = 'http://localhost:3001/api';
const CONVERSATION_ID = 'conv_user1_user2';
const MAX_FILE_SIZE_BYTES = 700 * 1024; // 700 KB

const socket = io(SOCKET_URL);

function App() {
  const [messages, setMessages] = useState([]);
  const [inputText, setInputText] = useState("");
  const [users, setUsers] = useState([]); // Dynamic users
  const [currentUser, setCurrentUser] = useState(null);
  const [selectedFile, setSelectedFile] = useState(null); // { base64, name, type }
  const messagesEndRef = useRef(null);
  const fileInputRef = useRef(null);

  useEffect(() =&amp;gt; {
    // Join Room
    socket.emit('join_conversation', CONVERSATION_ID);

    // Initial Load: Users &amp;amp; Messages
    fetchUsers();
    fetchMessages();

    // Socket Listener
    socket.on('receive_message', (newMessage) =&amp;gt; {
      setMessages((prev) =&amp;gt; {
        // Avoid duplicates if my message
        if (prev.find(m =&amp;gt; m.messageId === newMessage.messageId)) return prev;
        return [...prev, newMessage];
      });
      scrollToBottom();
    });

    return () =&amp;gt; {
      socket.off('receive_message');
    };
  }, []);

  const fetchUsers = async () =&amp;gt; {
    try {
      const res = await axios.get(`${API_URL}/users`);
      const userList = res.data;
      if (userList.length &amp;gt; 0) {
        setUsers(userList);
        // Default to first user if not set, or maintain current if switching back/refresh logic existed
        // For simple demo, just pick first as default
        setCurrentUser(userList[0]);
      }
    } catch (err) {
      console.error("Failed to load users", err);
    }
  }

  const fetchMessages = async () =&amp;gt; {
    try {
      const res = await axios.get(`${API_URL}/messages/${CONVERSATION_ID}`);
      setMessages(res.data);
      scrollToBottom();
    } catch (err) {
      console.error("Failed to load messages", err);
    }
  };

  const scrollToBottom = () =&amp;gt; {
    minutesTimeout(() =&amp;gt; {
      messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
    }, 100);
  };

  // Custom timeout wrapper to avoid "variable not found" issues if any
  const minutesTimeout = (fn, delay) =&amp;gt; setTimeout(fn, delay);

  const handleFileSelect = (e) =&amp;gt; {
    const file = e.target.files[0];
    if (!file) return;

    if (file.size &amp;gt; MAX_FILE_SIZE_BYTES) {
      alert("File size exceeds 700KB limit.");
      e.target.value = null; // Reset input
      return;
    }

    // Store raw file
    setSelectedFile(file);
  };

  const removeFile = () =&amp;gt; {
    setSelectedFile(null);
    if (fileInputRef.current) fileInputRef.current.value = "";
  };

  const sendMessage = async (e) =&amp;gt; {
    e.preventDefault();
    if (!inputText.trim() &amp;amp;&amp;amp; !selectedFile) return;

    if (!currentUser) return;

    const formData = new FormData();
    formData.append('conversationId', CONVERSATION_ID);
    formData.append('senderId', currentUser.uid);
    formData.append('content', inputText);

    if (selectedFile) {
      formData.append('file', selectedFile);
    }

    try {
      await axios.post(`${API_URL}/messages`, formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });
      setInputText("");
      removeFile();
    } catch (err) {
      console.error("Failed to send", err);
      alert("Failed to send message. Potentially too large or server error.");
    }
  };

  // Helper to format time
  const formatTime = (timestamp) =&amp;gt; {
    if (!timestamp) return '';
    // Handle Firestore Timestamp or JS Date string/obj
    const date = new Date(timestamp.seconds ? timestamp.seconds * 1000 : timestamp);
    return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  };

  // Helper to Switch User
  const switchUser = () =&amp;gt; {
    if (users.length &amp;lt; 2) return;
    const currentIndex = users.findIndex(u =&amp;gt; u.uid === currentUser.uid);
    const nextIndex = (currentIndex + 1) % users.length;
    setCurrentUser(users[nextIndex]);
  }

  // Get user details for rendering
  const getUserParams = (uid) =&amp;gt; {
    return users.find(u =&amp;gt; u.uid === uid) || { username: 'Unknown' };
  }

  if (!currentUser) return &amp;lt;div style={{ padding: 20 }}&amp;gt;Loading Chat...&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div className="app-container" style={{ margin: 'auto', width: '100%', maxWidth: '800px', height: '90vh', backgroundColor: '#fff', borderRadius: '16px', boxShadow: '0 10px 30px rgba(0,0,0,0.1)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}&amp;gt;
      {/* Header / Config for Demo */}
      &amp;lt;div style={{ padding: '10px 20px', borderBottom: '1px solid #eee', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}&amp;gt;
        &amp;lt;h3 style={{ fontWeight: 600 }}&amp;gt;Chat&amp;lt;/h3&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;span style={{ fontSize: '12px', color: '#888', marginRight: '10px' }}&amp;gt;Current User: &amp;lt;b&amp;gt;{currentUser.username}&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;
          {users.length &amp;gt; 1 &amp;amp;&amp;amp; (
            &amp;lt;button onClick={switchUser} style={{ fontSize: '12px', padding: '4px 8px', cursor: 'pointer' }}&amp;gt;
              Switch User
            &amp;lt;/button&amp;gt;
          )}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      {/* Chat Area */}
      &amp;lt;div className="chat-window" style={{ flex: 1, padding: '20px', overflowY: 'auto', backgroundColor: '#FAFAFA', display: 'flex', flexDirection: 'column', gap: '15px' }}&amp;gt;
        &amp;lt;AnimatePresence&amp;gt;
          {messages.map((msg, index) =&amp;gt; {
            const isMe = msg.senderId === currentUser.uid;
            const senderUser = getUserParams(msg.senderId);

            return (
              &amp;lt;motion.div
                key={msg.messageId || index}
                initial={{ opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0 }}
                style={{
                  alignSelf: isMe ? 'flex-end' : 'flex-start',
                  maxWidth: '70%',
                  display: 'flex',
                  flexDirection: 'column',
                  alignItems: isMe ? 'flex-end' : 'flex-start'
                }}
              &amp;gt;
                &amp;lt;div style={{
                  backgroundColor: isMe ? '#E8F5E9' : '#F1F1F1',
                  color: '#000',
                  padding: '12px 18px',
                  borderRadius: isMe ? '18px 18px 0 18px' : '18px 18px 18px 0',
                  boxShadow: '0 2px 5px rgba(0,0,0,0.02)',
                  position: 'relative',
                  minWidth: '120px'
                }}&amp;gt;
                  {/* File Display */}
                  {msg.fileBase64 &amp;amp;&amp;amp; (
                    &amp;lt;div style={{ marginBottom: '10px' }}&amp;gt;
                      {msg.fileType?.startsWith('image/') ? (
                        &amp;lt;img src={msg.fileBase64} alt="uploaded" style={{ maxWidth: '100%', borderRadius: '8px', border: '1px solid #ddd' }} /&amp;gt;
                      ) : (
                        &amp;lt;div style={{ backgroundColor: 'rgba(0,0,0,0.05)', padding: '10px', borderRadius: '8px', display: 'flex', alignItems: 'center', gap: '8px' }}&amp;gt;
                          &amp;lt;span style={{ fontSize: '20px' }}&amp;gt;📄&amp;lt;/span&amp;gt;
                          &amp;lt;a
                            href={msg.fileBase64}
                            download={msg.fileName || 'download'}
                            style={{ color: '#2196F3', textDecoration: 'none', fontSize: '14px', fontWeight: '500' }}
                          &amp;gt;
                            {msg.fileName || 'Download File'}
                          &amp;lt;/a&amp;gt;
                        &amp;lt;/div&amp;gt;
                      )}
                    &amp;lt;/div&amp;gt;
                  )}

                  {msg.content &amp;amp;&amp;amp; &amp;lt;div style={{ fontSize: '15px', lineHeight: '1.4', marginBottom: '4px' }}&amp;gt;{msg.content}&amp;lt;/div&amp;gt;}

                  &amp;lt;div style={{ fontSize: '11px', color: '#888', textAlign: isMe ? 'right' : 'left' }}&amp;gt;
                    {senderUser.username} • {formatTime(msg.message_at)}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/motion.div&amp;gt;
            );
          })}
        &amp;lt;/AnimatePresence&amp;gt;
        &amp;lt;div ref={messagesEndRef} /&amp;gt;
      &amp;lt;/div&amp;gt;

      {/* Input Area */}
      &amp;lt;form onSubmit={sendMessage} style={{ padding: '20px', backgroundColor: '#fff', borderTop: '1px solid #eee', display: 'flex', flexDirection: 'column', gap: '10px' }}&amp;gt;
        {selectedFile &amp;amp;&amp;amp; (
          &amp;lt;div style={{ fontSize: '12px', color: '#666', display: 'flex', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#f9f9f9', padding: '8px 12px', borderRadius: '6px' }}&amp;gt;
            &amp;lt;span&amp;gt;Selected: &amp;lt;b&amp;gt;{selectedFile.name}&amp;lt;/b&amp;gt; ({(selectedFile.size / 1024).toFixed(1)} KB)&amp;lt;/span&amp;gt;
            &amp;lt;button type="button" onClick={removeFile} style={{ border: 'none', background: 'transparent', color: 'red', cursor: 'pointer', fontWeight: 'bold' }}&amp;gt;✕&amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
        )}

        &amp;lt;div style={{ display: 'flex', gap: '10px' }}&amp;gt;
          {/* File Input */}
          &amp;lt;input
            type="file"
            ref={fileInputRef}
            style={{ display: 'none' }}
            onChange={handleFileSelect}
            accept="image/*,application/pdf"
          /&amp;gt;
          &amp;lt;button
            type="button"
            onClick={() =&amp;gt; fileInputRef.current?.click()}
            style={{
              border: '1px solid #ddd',
              background: '#f9f9f9',
              padding: '0 15px',
              borderRadius: '8px',
              cursor: 'pointer',
              fontSize: '18px'
            }}
            title="Upload File"
          &amp;gt;
            📎
          &amp;lt;/button&amp;gt;

          &amp;lt;input
            type="text"
            value={inputText}
            onChange={(e) =&amp;gt; setInputText(e.target.value)}
            placeholder="Type your message..."
            style={{
              flex: 1,
              padding: '12px 15px',
              borderRadius: '8px',
              border: '1px solid #ddd',
              outline: 'none',
              fontSize: '15px'
            }}
          /&amp;gt;
          &amp;lt;button
            type="submit"
            disabled={!inputText.trim() &amp;amp;&amp;amp; !selectedFile}
            style={{
              backgroundColor: '#4CAF50',
              color: 'white',
              border: 'none',
              borderRadius: '8px',
              padding: '0 20px',
              fontSize: '15px',
              fontWeight: '600',
              cursor: 'pointer',
              opacity: (!inputText.trim() &amp;amp;&amp;amp; !selectedFile) ? 0.7 : 1
            }}
          &amp;gt;
            Send
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>node</category>
      <category>react</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
