diff --git a/.env b/.env new file mode 100644 index 0000000..2fc80e3 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PORT=3000 diff --git a/README.md b/README.md index b1fb4f4..ac7665e 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,154 @@ -# Realtime Chat App with Node.js and WebSockets +# Modern Chat App -## Overview +A real-time chat application built with Node.js, Express, and Socket.IO. Experience seamless communication with a modern, responsive interface. -This Realtime Chat App is a simple yet powerful application built with Node.js and WebSockets, allowing users to join different chat rooms and communicate with each other in real-time. +## ✨ Features -## Features +- **Real-time Messaging**: Instant message delivery using WebSocket technology +- **Multi-room Support**: Join different chat rooms for organized conversations +- **Modern UI**: Clean, responsive design with smooth animations +- **User Management**: See who's online in your current room +- **Connection Status**: Real-time connection indicator +- **Keyboard Shortcuts**: Quick actions for better user experience +- **Mobile Responsive**: Works perfectly on all devices -- **Real-time Communication:** Utilizes WebSockets to enable instant messaging between users. -- **Multiple Rooms:** Users can join different chat rooms. -- **User-friendly Interface:** Provides an intuitive and clean user interface for an enjoyable chat experience. +## πŸš€ Tech Stack -## Prerequisites +- **Backend**: Node.js, Express.js +- **Real-time Communication**: Socket.IO +- **Frontend**: Vanilla JavaScript, HTML5, CSS3 +- **Styling**: Modern CSS with CSS Variables and Flexbox +- **Icons**: Font Awesome +- **Fonts**: Inter (Google Fonts) -Before running the application, make sure you have the following installed: +## πŸ“‹ Prerequisites -- Node.js: [Download and install Node.js](https://nodejs.org/) +Before running this application, make sure you have the following installed: -## Getting Started +- [Node.js](https://nodejs.org/) (version 14 or higher) +- npm (comes with Node.js) -1. Clone the repository: +## πŸ› οΈ Installation +1. **Clone the repository** ```bash - git clone https://github.com/your-username/realtime-chat-app.git + git clone https://github.com/your-username/modern-chat-app.git + cd modern-chat-app ``` -2. Navigate to the project directory: +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Start the server** + ```bash + # Development mode (with auto-restart) + npm run dev + + # Production mode + npm start + ``` + +4. **Open your browser** + Navigate to `http://localhost:3000` + +## 🎯 Usage + +1. **Join a Chat Room** + - Enter your username + - Choose a room name + - Click "Join Chat" + +2. **Start Chatting** + - Type your message in the input field + - Press Enter or click the send button + - Use Ctrl/Cmd + Enter for quick sending + +3. **Room Features** + - See active users in the sidebar + - Real-time connection status + - Automatic message scrolling + +## 🎨 Features in Detail + +### Real-time Communication +- Instant message delivery +- Live user presence +- Connection status monitoring +- Automatic reconnection + +### Modern Interface +- Gradient backgrounds +- Smooth animations +- Responsive design +- Clean typography +- Intuitive navigation + +### User Experience +- Welcome messages +- Loading states +- Error notifications +- Keyboard shortcuts +- Auto-scroll to new messages + +## πŸ“ Project Structure + +``` +modern-chat-app/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ index.js # Main server file +β”‚ └── utils/ +β”‚ β”œβ”€β”€ messages.js # Message utilities +β”‚ └── user.js # User management +β”œβ”€β”€ public/ +β”‚ β”œβ”€β”€ index.html # Join page +β”‚ β”œβ”€β”€ chat.html # Chat interface +β”‚ β”œβ”€β”€ css/ +β”‚ β”‚ └── styles.css # Modern styling +β”‚ └── js/ +β”‚ └── chat.js # Frontend logic +β”œβ”€β”€ package.json +└── README.md +``` + +## πŸ”§ Configuration + +The application uses environment variables for configuration: ```bash -cd realtime-nodejs-chat-app +# .env file (optional) +PORT=3000 +HOST=0.0.0.0 ``` -3. Install dependencies: +## πŸš€ Deployment + +### Local Development ```bash -npm install +npm run dev ``` -4. Start the server: -```bash -nodemon src/index.js or node src/index.js -``` -5. Open your web browser and go to http://localhost:3000 to access the Realtime Chat App. -## Usage -1. Enter a username. -2. Choose a chat room to join. -3. Start chatting with other users in real-time. +## 🀝 Contributing -## File Structure -- **src/index.js**: The main server file that handles WebSocket connections and serves the HTML page. -- **public/index.html**: The HTML template for the chat application. -- **public/style.css**: The stylesheet for styling the chat application. +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/AmazingFeature`) +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request -## Dependencies -- `express`: Web framework for Node.js. -- `socket.io`: WebSocket library for Node.js. -## Contributing -Feel free to contribute to the development of this Realtime Chat App by creating issues or submitting pull requests. Your feedback and contributions are highly appreciated! \ No newline at end of file +## πŸ™ Acknowledgments + +- [Socket.IO](https://socket.io/) for real-time communication +- [Express.js](https://expressjs.com/) for the web framework +- [Font Awesome](https://fontawesome.com/) for icons +- [Inter Font](https://rsms.me/inter/) for typography + +## πŸ“ž Support + +If you have any questions or need help, please open an issue on GitHub. + +--- + +**Built with ❀️ using Node.js and Socket.IO** \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b8a8b43..1c3c598 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bad-words": "^3.0.4", + "dotenv": "^16.6.1", "express": "^4.18.2", "moment": "^2.29.4", "socket.io": "^4.7.2" @@ -300,6 +301,18 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1509,6 +1522,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index 50c7db1..2661672 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "bad-words": "^3.0.4", + "dotenv": "^16.3.1", "express": "^4.18.2", "moment": "^2.29.4", "socket.io": "^4.7.2" diff --git a/public/chat.html b/public/chat.html index 023ee15..e435d94 100644 --- a/public/chat.html +++ b/public/chat.html @@ -4,60 +4,96 @@ - Chat App + Modern Chat App - Chat + +
+
-
+
+
+

Chat Room

+
+ + Connected +
+
+ +
+ +
+
+ +

Welcome to Chat!

+

Start sending messages to begin chatting.

+
+
-
- - +
+ + +
-
-
- - + diff --git a/public/css/styles.css b/public/css/styles.css index ed550f2..97bb989 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,4 +1,4 @@ -/* General Styles */ +/* Modern Chat App Styles */ * { margin: 0; @@ -6,178 +6,808 @@ box-sizing: border-box; } -html { - font-size: 16px; +:root { + --primary-color: #667eea; + --primary-dark: #5a6fd8; + --secondary-color: #764ba2; + --accent-color: #f093fb; + --text-primary: #2d3748; + --text-secondary: #718096; + --bg-primary: #ffffff; + --bg-secondary: #f7fafc; + --bg-dark: #2d3748; + --border-color: #e2e8f0; + --success-color: #48bb78; + --error-color: #f56565; + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --border-radius: 12px; + --border-radius-sm: 8px; } -input { - font-size: 14px; +html { + font-size: 16px; + scroll-behavior: smooth; } body { - line-height: 1.4; - color: #333333; - font-family: Helvetica, Arial, sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + color: var(--text-primary); + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); + min-height: 100vh; +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + margin-bottom: 1rem; } h1 { - margin-bottom: 16px; + font-size: 2.5rem; + background: linear-gradient(135deg, var(--primary-color), var(--accent-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } -label { - display: block; - font-size: 14px; - margin-bottom: 8px; - color: #777; +h2 { + font-size: 1.875rem; + color: var(--text-primary); +} + +/* Form Elements */ +input, button, textarea { + font-family: inherit; + font-size: 1rem; + border-radius: var(--border-radius-sm); + transition: all 0.3s ease; } input { - border: 1px solid #eeeeee; - padding: 12px; + border: 2px solid var(--border-color); + padding: 1rem 1.25rem; outline: none; + background: var(--bg-primary); + color: var(--text-primary); + width: 100%; + font-size: 1rem; +} + +input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); } button { cursor: pointer; - padding: 12px; - background: #7C5CBF; + padding: 1rem 2rem; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); border: none; color: white; - font-size: 16px; - transition: background .3s ease; + font-weight: 600; + font-size: 1rem; + border-radius: var(--border-radius-sm); + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +button::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; +} + +button:hover::before { + left: 100%; } button:hover { - background: #6b47b8; + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +button:active { + transform: translateY(0); } button:disabled { - cursor: default; - background: #7c5cbf94; + cursor: not-allowed; + opacity: 0.6; + transform: none; + box-shadow: none; +} + +button:disabled::before { + display: none; +} + +label { + display: block; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; } /* Join Page Styles */ - .centered-form { - background: #333744; - width: 100vw; - height: 100vh; + min-height: 100vh; display: flex; justify-content: center; align-items: center; + padding: 2rem; + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); + position: relative; + overflow: hidden; +} + +.centered-form::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + opacity: 0.3; } .centered-form__box { - box-shadow: 0px 0px 17px 1px #1D1F26; - background: #F7F7FA; - padding: 24px; - width: 250px; + background: var(--bg-primary); + padding: 3rem; + border-radius: var(--border-radius); + box-shadow: var(--shadow-lg); + width: 100%; + max-width: 400px; + position: relative; + z-index: 1; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); } -.centered-form button { - width: 100%; +.centered-form__box h1 { + text-align: center; + margin-bottom: 2rem; + font-size: 2.5rem; +} + +.centered-form form { + display: flex; + flex-direction: column; + gap: 1.5rem; } .centered-form input { - margin-bottom: 16px; + margin-bottom: 0; +} + +.centered-form button { + margin-top: 1rem; width: 100%; + padding: 1.25rem; + font-size: 1.1rem; } /* Chat Page Layout */ - .chat { display: flex; + height: 100vh; + background: var(--bg-secondary); } .chat__sidebar { - height: 100vh; + width: 300px; + background: var(--bg-dark); color: white; - background: #333744; - width: 225px; - overflow-y: scroll -} - -/* Chat styles */ - -.chat__main { - flex-grow: 1; display: flex; flex-direction: column; - max-height: 100vh; + border-right: 1px solid var(--border-color); + box-shadow: var(--shadow-md); } +.chat__main { + flex: 1; + display: flex; + flex-direction: column; + background: var(--bg-primary); +} + +/* Sidebar Styles */ +.sidebar-header { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + padding: 2rem 1.5rem 1.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.room-title { + font-size: 1.25rem; + color: white; + margin: 0; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.room-title i { + font-size: 1.1rem; +} + +.sidebar-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.list-title { + padding: 1.5rem 1.5rem 1rem; + font-size: 1rem; + color: rgba(255, 255, 255, 0.8); + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.list-title i { + font-size: 0.9rem; +} + +.users { + list-style: none; + padding: 1rem 1.5rem; + flex: 1; + overflow-y: auto; + margin: 0; +} + +.user-item { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + margin-bottom: 0.5rem; + background: rgba(255, 255, 255, 0.1); + border-radius: var(--border-radius-sm); + border-left: 3px solid var(--primary-color); + transition: all 0.3s ease; + position: relative; +} + +.user-item:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateX(5px); +} + +.user-name { + flex: 1; + color: white; + font-weight: 500; +} + +.current-user-badge { + background: var(--accent-color); + color: white; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Messages Area */ .chat__messages { - flex-grow: 1; - padding: 24px 24px 0 24px; - overflow-y: scroll; + flex: 1; + padding: 2rem; + overflow-y: auto; + background: var(--bg-secondary); + display: flex; + flex-direction: column; + gap: 1rem; } -/* Message Styles */ - .message { - margin-bottom: 16px; + background: var(--bg-primary); + padding: 1.5rem; + border-radius: var(--border-radius); + box-shadow: var(--shadow-sm); + border-left: 4px solid var(--primary-color); + transition: all 0.3s ease; + max-width: 80%; + animation: slideIn 0.3s ease-out; + position: relative; +} + +.message.own-message { + align-self: flex-end; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + border-left-color: var(--accent-color); +} + +.message.own-message .message__name, +.message.own-message .message__meta { + color: rgba(255, 255, 255, 0.9); +} + +.message.own-message .message__content p { + color: white; +} + + + +.message:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.message__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; } .message__name { font-weight: 600; - font-size: 14px; - margin-right: 8px; + color: var(--primary-color); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; } .message__meta { - color: #777; - font-size: 14px; + color: var(--text-secondary); + font-size: 0.75rem; } -.message a { - color: #0070CC; +.message__content { + margin-top: 0.5rem; } -/* Message Composition Styles */ +.message__content p { + color: var(--text-primary); + line-height: 1.6; + margin: 0; +} + + +/* Logo and Header Styles */ +.logo-container { + text-align: center; + margin-bottom: 2rem; +} + +.logo-icon { + font-size: 3rem; + color: var(--primary-color); + margin-bottom: 1rem; + animation: bounce 2s infinite; +} + +.subtitle { + color: var(--text-secondary); + font-size: 1rem; + margin-top: 0.5rem; +} + +.input-group { + position: relative; + margin-bottom: 1.5rem; +} + +.input-group.focused label { + color: var(--primary-color); +} + +.input-group label i { + margin-right: 0.5rem; + color: var(--primary-color); +} + +.features { + display: flex; + justify-content: space-around; + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid var(--border-color); +} + +.feature { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + color: var(--text-secondary); + font-size: 0.875rem; +} + +.feature i { + font-size: 1.5rem; + color: var(--primary-color); + margin-bottom: 0.5rem; +} + +/* Chat Header */ +.chat__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem 2rem; + background: var(--bg-primary); + border-bottom: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); +} + +.header-content { + display: flex; + align-items: center; + gap: 1rem; +} + +.header-content h2 { + margin: 0; + color: var(--text-primary); + font-size: 1.5rem; +} + +.connection-status { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--success-color); + animation: pulse 2s infinite; +} + +.status-indicator.offline { + background: var(--error-color); +} + +.back-btn { + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, var(--error-color), #e53e3e); + font-size: 0.875rem; +} + +.back-btn:hover { + background: linear-gradient(135deg, #e53e3e, var(--error-color)); +} + +/* Welcome Message */ +.welcome-message { + text-align: center; + padding: 3rem 2rem; + color: var(--text-secondary); + animation: fadeIn 1s ease-out; +} + +.welcome-message i { + font-size: 3rem; + color: var(--primary-color); + margin-bottom: 1rem; +} + +.welcome-message h3 { + color: var(--text-primary); + margin-bottom: 0.5rem; +} + +/* Compose Area */ .compose { + padding: 1.5rem 2rem; + background: var(--bg-primary); + border-top: 1px solid var(--border-color); + box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1); +} + +.input-wrapper { display: flex; - flex-shrink: 0; - margin-top: 16px; - padding: 24px; + gap: 1rem; + margin-bottom: 1rem; + background: var(--bg-secondary); + border-radius: var(--border-radius); + padding: 0.5rem; + border: 2px solid var(--border-color); + transition: all 0.3s ease; } -.compose form { +.input-wrapper:focus-within { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.input-wrapper input { + flex: 1; + border: none; + background: transparent; + padding: 1rem; + margin: 0; + box-shadow: none; +} + +.input-wrapper input:focus { + border: none; + box-shadow: none; + transform: none; +} + +.input-wrapper button { + padding: 1rem; + border-radius: var(--border-radius-sm); + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + min-width: 50px; +} + + + +/* Animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-10px); + } + 60% { + transform: translateY(-5px); + } +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(72, 187, 120, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(72, 187, 120, 0); + } +} + +@keyframes typing { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .chat { + flex-direction: column; + } + + .chat__sidebar { + width: 100%; + height: auto; + max-height: 200px; + } + + .centered-form__box { + margin: 1rem; + padding: 2rem; + } + + .compose form { + flex-direction: column; + } + + .message { + max-width: 95%; + } + + h1 { + font-size: 2rem; + } +} + +@media (max-width: 480px) { + .centered-form { + padding: 1rem; + } + + .centered-form__box { + padding: 1.5rem; + } + + .chat__messages { + padding: 1rem; + } + + .compose { + padding: 1rem; + } +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--secondary-color); +} + +/* Loading States */ +.loading { + opacity: 0.6; + pointer-events: none; +} + +/* Success/Error States */ +.success { + border-left-color: var(--success-color) !important; +} + +.error { + border-left-color: var(--error-color) !important; +} + +/* Typing Indicator */ +.typing-indicator { + padding: 1rem; + color: var(--text-secondary); + font-style: italic; + font-size: 0.875rem; +} + +/* Online Status */ +.online-indicator { + display: inline-block; + width: 8px; + height: 8px; + background: var(--success-color); + border-radius: 50%; + margin-right: 0.5rem; + animation: pulse 2s infinite; +} + +/* Notification System */ +.notification { + position: fixed; + top: 20px; + right: 20px; + background: var(--bg-primary); + color: var(--text-primary); + padding: 1rem 1.5rem; + border-radius: var(--border-radius); + box-shadow: var(--shadow-lg); + border-left: 4px solid var(--primary-color); display: flex; - flex-grow: 1; - margin-right: 16px; + align-items: center; + gap: 0.75rem; + z-index: 1000; + animation: slideInRight 0.3s ease-out; + max-width: 400px; } -.compose input { - border: 1px solid #eeeeee; - width: 100%; - padding: 12px; - margin: 0 16px 0 0; - flex-grow: 1; +.notification-success { + border-left-color: var(--success-color); } -.compose button { - font-size: 14px; +.notification-error { + border-left-color: var(--error-color); } -/* Chat Sidebar Styles */ - -.room-title { - font-weight: 400; - font-size: 22px; - background: #2c2f3a; - padding: 24px; +.notification-warning { + border-left-color: #f6ad55; } -.list-title { - font-weight: 500; - font-size: 18px; - margin-bottom: 4px; - padding: 12px 24px 0 24px; +.notification-info { + border-left-color: var(--primary-color); } -.users { - list-style-type: none; - font-weight: 300; - padding: 12px 24px 0 24px; +.notification i:first-child { + font-size: 1.2rem; +} + +.notification-success i:first-child { + color: var(--success-color); +} + +.notification-error i:first-child { + color: var(--error-color); +} + +.notification-warning i:first-child { + color: #f6ad55; +} + +.notification-info i:first-child { + color: var(--primary-color); +} + +.notification button { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 0.25rem; + border-radius: 50%; + transition: all 0.3s ease; + margin-left: auto; +} + +.notification button:hover { + background: var(--bg-secondary); + color: var(--text-primary); +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(72, 187, 120, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(72, 187, 120, 0); + } } \ No newline at end of file diff --git a/public/index.html b/public/index.html index bf1a572..d3eba51 100644 --- a/public/index.html +++ b/public/index.html @@ -4,25 +4,87 @@ - Chat App + Modern Chat App - Join Chat + +
-

Join

-
- - - - - +
+ +

Chat App

+

Real-time chat experience

+
+ + +
+ + +
+ +
+ + +
+ +
+ +
-
+ + \ No newline at end of file diff --git a/public/js/chat.js b/public/js/chat.js index b93ab09..2af5cea 100644 --- a/public/js/chat.js +++ b/public/js/chat.js @@ -1,18 +1,17 @@ -const socket = io(); +// Socket.io connection - for remote server +const socket = io(window.location.origin); // Elements const $messageForm = document.querySelector("#message-form"); const $messageFormInput = $messageForm.querySelector("input"); -const $messageFormButton = $messageForm.querySelector("button"); -const $sendLocationButton = document.querySelector("#send-location"); +const $messageFormButton = $messageForm.querySelector("#send-btn"); const $messages = document.querySelector("#messages"); +const $roomName = document.querySelector("#room-name"); +const $connectionStatus = document.querySelector(".status-indicator"); +const $statusText = document.querySelector(".status-text"); // Templates - const messageTemplate = document.querySelector("#message-template").innerHTML; -const locationTemplate = document.querySelector( - "#locmessage-template" -).innerHTML; const sidebarTemplate = document.querySelector("#sidebar-template").innerHTML; // Options @@ -20,11 +19,39 @@ const { username, room } = Qs.parse(location.search, { ignoreQueryPrefix: true, }); +// Connection status management +let isConnected = true; + +socket.on('connect', () => { + isConnected = true; + updateConnectionStatus(true); +}); + +socket.on('disconnect', () => { + isConnected = false; + updateConnectionStatus(false); +}); + +function updateConnectionStatus(connected) { + if (connected) { + $connectionStatus.classList.remove('offline'); + $connectionStatus.classList.add('online'); + $statusText.textContent = 'Connected'; + } else { + $connectionStatus.classList.remove('online'); + $connectionStatus.classList.add('offline'); + $statusText.textContent = 'Disconnected'; + } +} + +// Auto scroll functionality const autoScroll = () => { // New message element const $newMessage = $messages.lastElementChild; - // hight of the new message + if (!$newMessage) return; + + // height of the new message const newMessageStyle = getComputedStyle($newMessage); const newMessageMargin = parseInt(newMessageStyle.marginBottom); const newMessageHeight = $newMessage.offsetHeight + newMessageMargin; @@ -43,107 +70,164 @@ const autoScroll = () => { } }; -// server (emit) -> client (receive) - countUpdated -// client (emit) -> server (receive) - increment - +// Message handling socket.on("message", (message) => { - // console.log(message); + const isOwnMessage = message.username === username; const html = Mustache.render(messageTemplate, { username: message.username, message: message.text, - createdAt: moment(message.createdAt).format("h:mm a"), - }); - $messages.insertAdjacentHTML("beforeend", html); - autoScroll(); -}); - -socket.on("locationMessage", (url) => { - // console.log(url.username); - const html = Mustache.render(locationTemplate, { - username: url.username, - url: url.url, - createdAt: moment(url.createdAt).format("h:mm a"), + createdAt: moment(message.createdAt).format("HH:mm"), + isOwn: isOwnMessage }); + + // Remove welcome message if it exists + const welcomeMessage = $messages.querySelector('.welcome-message'); + if (welcomeMessage) { + welcomeMessage.remove(); + } + $messages.insertAdjacentHTML("beforeend", html); autoScroll(); }); socket.on("roomData", ({ room, users }) => { + const usersWithCurrent = users.map(user => ({ + ...user, + isCurrentUser: user.username === username + })); + const html = Mustache.render(sidebarTemplate, { room: room, - users: users, + users: usersWithCurrent, }); document.querySelector("#sidebar").innerHTML = html; + $roomName.textContent = room; }); +// Form submission $messageForm.addEventListener("submit", (e) => { e.preventDefault(); // disable form after submit $messageFormButton.setAttribute("disabled", "disabled"); + $messageFormButton.innerHTML = ''; - //disable - - const message = $messageFormInput.value; + const message = $messageFormInput.value.trim(); if (message === "") { // enable form after submit $messageFormButton.removeAttribute("disabled"); + $messageFormButton.innerHTML = ''; return; } + socket.emit("sendMessage", message, (error) => { // enable form after submit $messageFormButton.removeAttribute("disabled"); + $messageFormButton.innerHTML = ''; // clear input $messageFormInput.value = ""; // focus input $messageFormInput.focus(); + if (error) { + showNotification(error, 'error'); return console.log(error); } - // console.log("Message Delivered"); - }); -}); - -document.querySelector("#send-location").addEventListener("click", (e) => { - e.preventDefault(); - if (!navigator.geolocation) { - return alert("Geolocation is not supported by your browser"); - } - - navigator.permissions.query({ name: "geolocation" }).then((res) => { - // console.log(res); - if (res.state === "denied") { - return alert("Please allow permission to send location!"); - } - }); - - navigator.geolocation.getCurrentPosition((position) => { - // console.log(position); - $sendLocationButton.setAttribute("disabled", "disabled"); - - socket.emit( - "sendLocation", - { - Latitude: position.coords.latitude, - Longitude: position.coords.longitude, - }, - () => { - $sendLocationButton.removeAttribute("disabled"); - // console.log("Location Shared"); - } - ); + + showNotification('Message sent!', 'success'); }); }); +// Join room socket.emit("join", { username, room }, (error) => { if (error) { - alert(error); - location.href = "/"; + showNotification(error, 'error'); + setTimeout(() => { + location.href = "/"; + }, 2000); + } +}); + +// Notification system +function showNotification(message, type = 'info') { + // Remove existing notifications + const existingNotifications = document.querySelectorAll('.notification'); + existingNotifications.forEach(notification => notification.remove()); + + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.innerHTML = ` + + ${message} + + `; + + document.body.appendChild(notification); + + // Auto remove after 5 seconds + setTimeout(() => { + if (notification.parentElement) { + notification.remove(); + } + }, 5000); +} + +function getNotificationIcon(type) { + switch (type) { + case 'success': return 'check-circle'; + case 'error': return 'exclamation-circle'; + case 'warning': return 'exclamation-triangle'; + default: return 'info-circle'; + } +} + +// Keyboard shortcuts +document.addEventListener('keydown', (e) => { + // Ctrl/Cmd + Enter to send message + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { + $messageForm.dispatchEvent(new Event('submit')); + } + + // Escape to clear input + if (e.key === 'Escape') { + $messageFormInput.value = ''; + $messageFormInput.blur(); + } +}); + +// Input focus management +$messageFormInput.addEventListener('focus', () => { + $messageFormInput.parentElement.classList.add('focused'); +}); + +$messageFormInput.addEventListener('blur', () => { + if (!$messageFormInput.value) { + $messageFormInput.parentElement.classList.remove('focused'); + } +}); + +// Page visibility API for better UX +document.addEventListener('visibilitychange', () => { + if (document.hidden) { + document.title = 'πŸ’¬ Chat App - Background'; + } else { + document.title = 'Chat App - Chat'; + } +}); + +// Prevent form submission on Enter if Shift is held (for multi-line support) +$messageFormInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && e.shiftKey) { + e.preventDefault(); + // Insert new line + const start = e.target.selectionStart; + const end = e.target.selectionEnd; + const value = e.target.value; + e.target.value = value.substring(0, start) + '\n' + value.substring(end); + e.target.selectionStart = e.target.selectionEnd = start + 1; } }); -// document.querySelector("#increment").addEventListener("click", (e) => { -// console.log("clicked"); -// socket.emit("increment"); -// }); diff --git a/src/index.js b/src/index.js index c91118b..e7b0d8c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,6 @@ +// Load environment variables +require('dotenv').config(); + // emit events // socket.emit, io.emit, socket.broadcast.emit @@ -11,7 +14,6 @@ const socketio = require("socket.io"); const Filter = require("bad-words"); const { generateMessage, - generateLocationMessage, } = require("./utils/messages"); const { @@ -29,6 +31,7 @@ const server = http.createServer(app); const io = socketio(server); const port = process.env.PORT || 3000; +const host = process.env.HOST || '0.0.0.0'; // TΓΌm IP'lerden erişime izin ver // define paths for express config const publicDirectoryPath = path.join(__dirname, "../public"); @@ -110,25 +113,10 @@ io.on("connection", (socket) => { } }); - socket.on("sendLocation", (coords, callback) => { - const user = getUser(socket.id); - if (!user) { - return callback("You are not authenticated"); - } - - io.to(user.room).emit( - "locationMessage", - generateLocationMessage( - user.username, - `https://google.com/maps?q=${coords.Latitude},${coords.Longitude}` - ) - ); - callback(); - }); }); // start the server -server.listen(port, () => { - console.log(`Server is up on port ${port}!`); +server.listen(port, host, () => { + console.log(`Server is up on ${host}:${port}!`); }); diff --git a/src/utils/messages.js b/src/utils/messages.js index 0744dfa..58ced88 100644 --- a/src/utils/messages.js +++ b/src/utils/messages.js @@ -10,15 +10,6 @@ const generateMessage = (username, text) => { }; }; -const generateLocationMessage = (username, url) => { - return { - username: username, - url: url, - createdAt: new Date().getTime(), - }; -}; - module.exports = { generateMessage, - generateLocationMessage, };