Merge pull request #1 from Hardaistee/Stable

modernized general ui
This commit is contained in:
Yuvraj Singh
2025-11-07 00:20:47 +05:30
committed by GitHub
10 changed files with 1163 additions and 256 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
PORT=3000

166
README.md
View File

@@ -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!
## 🙏 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**

18
package-lock.json generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -4,60 +4,96 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat App</title>
<title>Modern Chat App - Chat</title>
<link rel="icon" href="./img/favicon.png">
<link rel="stylesheet" href="./css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="chat">
<div id="sidebar" class="chat__sidebar">
<!-- Sidebar content will be populated by JavaScript -->
</div>
<div class="chat__main">
<div id="messages" class="chat__messages"></div>
<div class="chat__header">
<div class="header-content">
<h2 id="room-name">Chat Room</h2>
<div class="connection-status">
<span class="status-indicator online"></span>
<span class="status-text">Connected</span>
</div>
</div>
<button class="back-btn" onclick="location.href='/'">
<i class="fas fa-arrow-left"></i>
Exit
</button>
</div>
<div id="messages" class="chat__messages">
<div class="welcome-message">
<i class="fas fa-comments"></i>
<h3>Welcome to Chat!</h3>
<p>Start sending messages to begin chatting.</p>
</div>
</div>
<div class="compose">
<!-- <button id="increment">+1</button> -->
<form id="message-form">
<input type="text" name="message" placeholder="Enter message" autocomplete="off">
<button type="submit">Send</button>
<div class="input-wrapper">
<input type="text" name="message" placeholder="Type your message..." autocomplete="off">
<button type="submit" id="send-btn">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</form>
<button id="send-location">Send Location</button>
</div>
</div>
</div>
<script id="message-template" type="text/html">
<div class="message">
<p>
<span class="message__name">{{username}}</span>
<span class="message__meta">{{createdAt}}</span>
</p>
<p>{{message}}</p>
</div>
</script>
<script id="locmessage-template" type="text/html">
<div class="message">
<p>
<!-- Message Templates -->
<script id="message-template" type="text/html">
<div class="message {{#isOwn}}own-message{{/isOwn}}">
<div class="message__header">
<span class="message__name">{{username}}</span>
<span class="message__meta">{{createdAt}}</span>
</p>
<p><a href="{{url}}" target="_blank" style="color: #7C5CBF;">My Current Location</a></p>
</div>
<div class="message__content">
<p>{{message}}</p>
</div>
</div>
</script>
<script id="sidebar-template" type="text/html">
<h2 class="room-title" >Room: {{room}}</h2>
<h3 class="list-title" >Users</h3>
<ul class="users" >
{{#users}}
<li> {{username}} </li>
{{/users}}
</ul>
<div class="sidebar-header">
<h2 class="room-title">
<i class="fas fa-door-open"></i>
{{room}}
</h2>
</div>
<div class="sidebar-content">
<h3 class="list-title">
<i class="fas fa-users"></i>
Active Users ({{users.length}})
</h3>
<ul class="users">
{{#users}}
<li class="user-item">
<span class="online-indicator"></span>
<span class="user-name">{{username}}</span>
{{#isCurrentUser}}
<span class="current-user-badge">You</span>
{{/isCurrentUser}}
</li>
{{/users}}
</ul>
</div>
</script>
<!-- External Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>

View File

@@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="10" cy="60" r="0.5" fill="rgba(255,255,255,0.1)"/><circle cx="90" cy="40" r="0.5" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
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);
}
}

View File

@@ -4,25 +4,87 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat App</title>
<title>Modern Chat App - Join Chat</title>
<link rel="icon" href="./img/favicon.png">
<link rel="stylesheet" href="./css/styles.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="centered-form">
<div class="centered-form__box">
<h1>Join</h1>
<form action="./chat.html">
<label>Display Name</label>
<input type="text" name="username" required>
<label>Room</label>
<input type="text" name="room" required>
<button>Join</button>
</form>
</div>
<div class="logo-container">
<i class="fas fa-comments logo-icon"></i>
<h1>Chat App</h1>
<p class="subtitle">Real-time chat experience</p>
</div>
<form action="./chat.html" id="join-form">
<div class="input-group">
<label for="username">
<i class="fas fa-user"></i>
Username
</label>
<input type="text" name="username" id="username" required
placeholder="Enter your username" autocomplete="off">
</div>
<div class="input-group">
<label for="room">
<i class="fas fa-door-open"></i>
Room Name
</label>
<input type="text" name="room" id="room" required
placeholder="Enter room name to join" autocomplete="off">
</div>
<button type="submit" id="join-btn">
<i class="fas fa-sign-in-alt"></i>
Join Chat
</button>
</form>
</div>
</div>
<script>
// Form validation and animations
document.getElementById('join-form').addEventListener('submit', function(e) {
const username = document.getElementById('username').value.trim();
const room = document.getElementById('room').value.trim();
const button = document.getElementById('join-btn');
if (!username || !room) {
e.preventDefault();
button.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Please fill all fields';
button.style.background = 'linear-gradient(135deg, #f56565, #e53e3e)';
setTimeout(() => {
button.innerHTML = '<i class="fas fa-sign-in-alt"></i> Join Chat';
button.style.background = 'linear-gradient(135deg, #667eea, #764ba2)';
}, 2000);
return;
}
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Joining...';
button.disabled = true;
});
// Input focus animations
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
input.addEventListener('focus', function() {
this.parentElement.classList.add('focused');
});
input.addEventListener('blur', function() {
if (!this.value) {
this.parentElement.classList.remove('focused');
}
});
});
</script>
</body>
</html>

View File

@@ -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"),
createdAt: moment(message.createdAt).format("HH:mm"),
isOwn: isOwnMessage
});
$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"),
});
// 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 = '<i class="fas fa-spinner fa-spin"></i>';
//disable
const message = $messageFormInput.value;
const message = $messageFormInput.value.trim();
if (message === "") {
// enable form after submit
$messageFormButton.removeAttribute("disabled");
$messageFormButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
return;
}
socket.emit("sendMessage", message, (error) => {
// enable form after submit
$messageFormButton.removeAttribute("disabled");
$messageFormButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
// 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 = `
<i class="fas fa-${getNotificationIcon(type)}"></i>
<span>${message}</span>
<button onclick="this.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
`;
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");
// });

View File

@@ -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}!`);
});

View File

@@ -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,
};