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
+
+
+
-
+
+
+
+
+
+
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
+
+
+
+
\ 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,
};