π¬ Project Introduction: Real-Time Chat Application
This project is a fully functional Real-Time Chat Application that allows users to communicate instantly with each other. It features one-on-one private messaging, online/offline status indicators, typing indicators, and an admin panel to manage users and monitor conversations.
The Problem it Solves:
Modern communication requires instant messaging capabilities. This application provides a lightweight, self-hosted chat solution similar to WhatsApp Web or Facebook Messenger. It eliminates the need for third-party services and gives you complete control over your data.
Key Features:
- User System:
- User registration and login
- Profile pictures
- Online/offline status
- Last seen timestamps
- Typing indicators
- Chat Features:
- One-on-one private messaging
- Real-time message delivery (AJAX polling)
- Message read receipts (delivered/seen)
- Emoji support
- File/image sharing
- Message history
- Delete messages
- Admin Panel:
- User management (view/block/delete users)
- Monitor active chats
- View chat logs
- System statistics
- Announcement broadcast
Technology Stack:
- Frontend: HTML5, CSS3, JavaScript (Fetch API, Emoji Mart library)
- Backend: PHP (Object-Oriented PHP with PDO)
- Database: MySQL
- Real-time: AJAX polling (every 2 seconds)
- Server: Apache (XAMPP/WAMP/LAMP)
π Project File Structure
chat-application/ β βββ index.php # Landing page / Login redirect βββ login.php # User login page βββ register.php # User registration page βββ logout.php # Logout script βββ chat.php # Main chat interface βββ profile.php # User profile page β βββ admin/ # Admin Panel β βββ index.php # Admin login β βββ dashboard.php # Admin dashboard β βββ users.php # Manage users β βββ chats.php # Monitor chats β βββ broadcast.php # Send announcements β βββ logout.php # Admin logout β βββ includes/ # Backend logic β βββ config.php # Database connection β βββ functions.php # Helper functions β βββ session.php # Session management β βββ auth.php # Authentication functions β βββ api/ # AJAX endpoints β βββ send_message.php # Send new message β βββ get_messages.php # Get messages (polling) β βββ get_users.php # Get online users β βββ typing.php # Update typing status β βββ mark_read.php # Mark messages as read β βββ upload_file.php # Handle file uploads β βββ assets/ # Static assets β βββ css/ β β βββ style.css # Main styles β β βββ chat.css # Chat interface styles β βββ js/ β β βββ main.js # Main JavaScript β β βββ chat.js # Chat functionality β β βββ emoji.js # Emoji picker β βββ images/ β β βββ avatars/ # User profile pictures β β βββ uploads/ # Shared files/images β βββ vendor/ # Third-party libraries β βββ emoji-mart/ # Emoji picker library β βββ database/ βββ chat_app.sql # Database dump
ποΈ Database Setup (database/chat_app.sql)
Create a database named chat_app and run this SQL.
-- phpMyAdmin SQL Dump
-- Database: `chat_app`
CREATE DATABASE IF NOT EXISTS `chat_app`;
USE `chat_app`;
-- --------------------------------------------------------
-- Table structure for table `users`
-- --------------------------------------------------------
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL,
`full_name` varchar(100) NOT NULL,
`avatar` varchar(255) DEFAULT 'default.png',
`bio` text DEFAULT NULL,
`role` enum('user','admin') DEFAULT 'user',
`status` enum('online','offline','away') DEFAULT 'offline',
`last_seen` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample users (passwords: password123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`, `status`) VALUES
('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Administrator', 'admin', 'offline'),
('john', '[email protected]', '$2y$10$YourHashedPasswordHere', 'John Doe', 'user', 'offline'),
('jane', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Jane Smith', 'user', 'offline');
-- --------------------------------------------------------
-- Table structure for table `messages`
-- --------------------------------------------------------
CREATE TABLE `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sender_id` int(11) NOT NULL,
`receiver_id` int(11) NOT NULL,
`message` text DEFAULT NULL,
`file_path` varchar(255) DEFAULT NULL,
`file_type` varchar(50) DEFAULT NULL,
`is_read` tinyint(1) DEFAULT 0,
`read_at` timestamp NULL DEFAULT NULL,
`deleted_by_sender` tinyint(1) DEFAULT 0,
`deleted_by_receiver` tinyint(1) DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `sender_id` (`sender_id`),
KEY `receiver_id` (`receiver_id`),
KEY `created_at` (`created_at`),
CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `messages_ibfk_2` FOREIGN KEY (`receiver_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample messages
INSERT INTO `messages` (`sender_id`, `receiver_id`, `message`, `is_read`, `created_at`) VALUES
(2, 3, 'Hey Jane, how are you?', 1, DATE_SUB(NOW(), INTERVAL 10 MINUTE)),
(3, 2, 'Hi John! I\'m good, thanks!', 1, DATE_SUB(NOW(), INTERVAL 9 MINUTE)),
(2, 3, 'Want to discuss the project?', 0, DATE_SUB(NOW(), INTERVAL 5 MINUTE));
-- --------------------------------------------------------
-- Table structure for table `typing_status`
-- --------------------------------------------------------
CREATE TABLE `typing_status` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`chatting_with` int(11) NOT NULL,
`is_typing` tinyint(1) DEFAULT 0,
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `unique_conversation` (`user_id`,`chatting_with`),
KEY `user_id` (`user_id`),
KEY `chatting_with` (`chatting_with`),
CONSTRAINT `typing_status_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `typing_status_ibfk_2` FOREIGN KEY (`chatting_with`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `blocked_users`
-- --------------------------------------------------------
CREATE TABLE `blocked_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`blocked_user_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `unique_block` (`user_id`,`blocked_user_id`),
KEY `user_id` (`user_id`),
KEY `blocked_user_id` (`blocked_user_id`),
CONSTRAINT `blocked_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `blocked_users_ibfk_2` FOREIGN KEY (`blocked_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `announcements`
-- --------------------------------------------------------
CREATE TABLE `announcements` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`message` text NOT NULL,
`created_by` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `created_by` (`created_by`),
CONSTRAINT `announcements_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
COMMIT;
π» Core PHP Files
1. Database Configuration (includes/config.php)
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'chat_app');
define('DB_USER', 'root');
define('DB_PASS', '');
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Site configuration
define('SITE_NAME', 'Chat Application');
define('SITE_URL', 'http://localhost/chat-application/');
define('UPLOAD_PATH', __DIR__ . '/../assets/uploads/');
define('AVATAR_PATH', __DIR__ . '/../assets/images/avatars/');
// Helper functions
function sanitize($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
function redirect($url) {
header("Location: $url");
exit;
}
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
function getUserById($id) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
function timeAgo($timestamp) {
$time_ago = strtotime($timestamp);
$current_time = time();
$time_difference = $current_time - $time_ago;
$seconds = $time_difference;
$minutes = round($seconds / 60);
$hours = round($seconds / 3600);
$days = round($seconds / 86400);
$weeks = round($seconds / 604800);
$months = round($seconds / 2629440);
$years = round($seconds / 31553280);
if ($seconds <= 60) {
return "Just now";
} else if ($minutes <= 60) {
return ($minutes == 1) ? "1 minute ago" : "$minutes minutes ago";
} else if ($hours <= 24) {
return ($hours == 1) ? "1 hour ago" : "$hours hours ago";
} else if ($days <= 7) {
return ($days == 1) ? "yesterday" : "$days days ago";
} else if ($weeks <= 4.3) {
return ($weeks == 1) ? "1 week ago" : "$weeks weeks ago";
} else if ($months <= 12) {
return ($months == 1) ? "1 month ago" : "$months months ago";
} else {
return ($years == 1) ? "1 year ago" : "$years years ago";
}
}
function updateUserStatus($user_id, $status) {
global $pdo;
$stmt = $pdo->prepare("UPDATE users SET status = ?, last_seen = NOW() WHERE id = ?");
return $stmt->execute([$status, $user_id]);
}
?>
2. Authentication Functions (includes/auth.php)
<?php
require_once 'config.php';
class Auth {
public static function login($username, $password) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
$stmt->execute([$username, $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['full_name'];
$_SESSION['user_username'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_avatar'] = $user['avatar'];
// Update status to online
updateUserStatus($user['id'], 'online');
return true;
}
return false;
}
public static function register($data) {
global $pdo;
// Check if username exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$data['username']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Username already taken'];
}
// Check if email exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$data['email']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Email already registered'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
// Insert user
$stmt = $pdo->prepare("
INSERT INTO users (username, email, password, full_name, bio, avatar)
VALUES (?, ?, ?, ?, ?, ?)
");
$avatar = 'default.png';
$success = $stmt->execute([
$data['username'],
$data['email'],
$hashedPassword,
$data['full_name'],
$data['bio'] ?? null,
$avatar
]);
if ($success) {
return ['success' => true, 'message' => 'Registration successful! Please login.'];
}
return ['success' => false, 'message' => 'Registration failed. Please try again.'];
}
public static function logout() {
if (isset($_SESSION['user_id'])) {
updateUserStatus($_SESSION['user_id'], 'offline');
}
session_destroy();
redirect('login.php');
}
public static function checkLogin() {
if (!isset($_SESSION['user_id'])) {
redirect('login.php');
}
}
public static function checkAdmin() {
self::checkLogin();
if ($_SESSION['user_role'] !== 'admin') {
redirect('chat.php');
}
}
public static function getCurrentUser() {
global $pdo;
if (!isset($_SESSION['user_id'])) {
return null;
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
}
?>
3. Login Page (login.php)
<?php
require_once 'includes/config.php';
require_once 'includes/auth.php';
// If already logged in, redirect to chat
if (isLoggedIn()) {
redirect('chat.php');
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (Auth::login($username, $password)) {
redirect('chat.php');
} else {
$error = 'Invalid username/email or password';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-card">
<div class="auth-header">
<h1><?php echo SITE_NAME; ?></h1>
<p>Login to start chatting</p>
</div>
<?php if ($error): ?>
<div class="alert alert-error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST" action="" class="auth-form">
<div class="form-group">
<label for="username">Username or Email</label>
<input type="text" id="username" name="username" required
placeholder="Enter your username or email">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required
placeholder="Enter your password">
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
<div class="auth-footer">
<p>Don't have an account? <a href="register.php">Register</a></p>
</div>
<div class="demo-credentials">
<p><strong>Demo Credentials:</strong></p>
<p>Admin: admin / password123</p>
<p>User1: john / password123</p>
<p>User2: jane / password123</p>
</div>
</div>
</div>
</body>
</html>
4. Registration Page (register.php)
<?php
require_once 'includes/config.php';
require_once 'includes/auth.php';
// If already logged in, redirect to chat
if (isLoggedIn()) {
redirect('chat.php');
}
$message = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'username' => sanitize($_POST['username']),
'email' => sanitize($_POST['email']),
'password' => $_POST['password'],
'confirm_password' => $_POST['confirm_password'],
'full_name' => sanitize($_POST['full_name']),
'bio' => sanitize($_POST['bio'] ?? '')
];
// Validation
if (strlen($data['password']) < 6) {
$error = 'Password must be at least 6 characters';
} elseif ($data['password'] !== $data['confirm_password']) {
$error = 'Passwords do not match';
} elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$error = 'Invalid email format';
} else {
$result = Auth::register($data);
if ($result['success']) {
$message = $result['message'];
} else {
$error = $result['message'];
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-card">
<div class="auth-header">
<h1>Create Account</h1>
<p>Join our chat community</p>
</div>
<?php if ($message): ?>
<div class="alert alert-success"><?php echo $message; ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-error"><?php echo $error; ?></div>
<?php endif; ?>
<form method="POST" action="" class="auth-form">
<div class="form-group">
<label for="full_name">Full Name</label>
<input type="text" id="full_name" name="full_name" required
placeholder="Enter your full name">
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required
placeholder="Choose a username">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required
placeholder="Enter your email">
</div>
<div class="form-row">
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required
placeholder="Min. 6 characters">
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" required
placeholder="Re-enter password">
</div>
</div>
<div class="form-group">
<label for="bio">Bio (Optional)</label>
<textarea id="bio" name="bio" rows="3"
placeholder="Tell something about yourself"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Register</button>
</form>
<div class="auth-footer">
<p>Already have an account? <a href="login.php">Login</a></p>
</div>
</div>
</div>
</body>
</html>
5. Main Chat Interface (chat.php)
<?php
require_once 'includes/config.php';
require_once 'includes/auth.php';
Auth::checkLogin();
$current_user = Auth::getCurrentUser();
$user_id = $_SESSION['user_id'];
// Get all users except current user
$users = $pdo->prepare("
SELECT u.*,
(SELECT COUNT(*) FROM messages
WHERE sender_id = u.id AND receiver_id = ? AND is_read = 0) as unread_count
FROM users u
WHERE u.id != ?
ORDER BY u.status = 'online' DESC, u.last_seen DESC
");
$users->execute([$user_id, $user_id]);
$all_users = $users->fetchAll();
// Get selected chat partner
$chat_with = isset($_GET['user']) ? (int)$_GET['user'] : ($all_users[0]['id'] ?? 0);
// Mark messages as read when opening chat
if ($chat_with > 0) {
$pdo->prepare("
UPDATE messages
SET is_read = 1, read_at = NOW()
WHERE sender_id = ? AND receiver_id = ? AND is_read = 0
")->execute([$chat_with, $user_id]);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="assets/css/chat.css">
<link rel="stylesheet" href="assets/vendor/emoji-mart/emoji-mart.css">
</head>
<body>
<div class="chat-app">
<!-- Sidebar - Users List -->
<aside class="sidebar">
<div class="sidebar-header">
<div class="user-profile">
<img src="assets/images/avatars/<?php echo $current_user['avatar']; ?>"
alt="<?php echo $current_user['full_name']; ?>" class="avatar">
<div class="user-info">
<h3><?php echo $current_user['full_name']; ?></h3>
<span class="status online">Online</span>
</div>
</div>
<div class="sidebar-actions">
<a href="profile.php" class="btn-icon" title="Profile">π€</a>
<?php if (isAdmin()): ?>
<a href="admin/dashboard.php" class="btn-icon" title="Admin Panel">βοΈ</a>
<?php endif; ?>
<a href="logout.php" class="btn-icon" title="Logout">πͺ</a>
</div>
</div>
<div class="search-box">
<input type="text" id="search-users" placeholder="Search users...">
</div>
<div class="users-list" id="users-list">
<?php foreach ($all_users as $user): ?>
<a href="?user=<?php echo $user['id']; ?>"
class="user-item <?php echo $chat_with == $user['id'] ? 'active' : ''; ?>"
data-user-id="<?php echo $user['id']; ?>">
<div class="user-avatar">
<img src="assets/images/avatars/<?php echo $user['avatar']; ?>"
alt="<?php echo $user['full_name']; ?>">
<span class="status-dot <?php echo $user['status']; ?>"></span>
</div>
<div class="user-details">
<h4><?php echo $user['full_name']; ?></h4>
<p class="last-message">
<?php if ($user['status'] == 'online'): ?>
<span class="online-text">Online</span>
<?php else: ?>
Last seen <?php echo timeAgo($user['last_seen']); ?>
<?php endif; ?>
</p>
</div>
<?php if ($user['unread_count'] > 0): ?>
<span class="unread-badge"><?php echo $user['unread_count']; ?></span>
<?php endif; ?>
</a>
<?php endforeach; ?>
</div>
</aside>
<!-- Main Chat Area -->
<main class="chat-area">
<?php if ($chat_with > 0):
$chat_partner = getUserById($chat_with);
?>
<!-- Chat Header -->
<div class="chat-header">
<div class="chat-user-info">
<img src="assets/images/avatars/<?php echo $chat_partner['avatar']; ?>"
alt="<?php echo $chat_partner['full_name']; ?>" class="avatar">
<div>
<h3><?php echo $chat_partner['full_name']; ?></h3>
<span class="user-status" id="partner-status">
<?php echo $chat_partner['status'] == 'online' ? 'Online' : 'Offline'; ?>
</span>
</div>
</div>
<div class="chat-actions">
<button class="btn-icon" onclick="clearChat()" title="Clear chat">ποΈ</button>
<button class="btn-icon" onclick="blockUser()" title="Block user">π«</button>
</div>
</div>
<!-- Messages Container -->
<div class="messages-container" id="messages-container">
<!-- Messages will be loaded here via AJAX -->
<div class="loading">Loading messages...</div>
</div>
<!-- Typing Indicator -->
<div class="typing-indicator" id="typing-indicator" style="display: none;">
<span><?php echo $chat_partner['full_name']; ?> is typing</span>
<span class="dots">
<span>.</span><span>.</span><span>.</span>
</span>
</div>
<!-- Message Input -->
<div class="message-input">
<button class="btn-icon emoji-btn" id="emoji-btn" title="Emoji">π</button>
<input type="text" id="message-text" placeholder="Type a message..." autocomplete="off">
<button class="btn-icon attach-btn" onclick="document.getElementById('file-input').click()" title="Attach file">π</button>
<input type="file" id="file-input" style="display: none;" onchange="uploadFile()">
<button class="btn-send" onclick="sendMessage()" id="send-btn">Send</button>
</div>
<!-- Hidden input for receiver ID -->
<input type="hidden" id="receiver-id" value="<?php echo $chat_with; ?>">
<?php else: ?>
<div class="no-chat-selected">
<div class="welcome-message">
<h2>Welcome to <?php echo SITE_NAME; ?>!</h2>
<p>Select a user from the sidebar to start chatting</p>
</div>
</div>
<?php endif; ?>
</main>
</div>
<!-- Emoji Picker Container -->
<div id="emoji-picker" style="display: none;"></div>
<script src="assets/js/chat.js"></script>
<script src="assets/vendor/emoji-mart/emoji-mart.js"></script>
</body>
</html>
6. API - Send Message (api/send_message.php)
<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
header('Content-Type: application/json');
if (!isLoggedIn()) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit;
}
$sender_id = $_SESSION['user_id'];
$receiver_id = $_POST['receiver_id'] ?? 0;
$message = $_POST['message'] ?? '';
$file_path = null;
$file_type = null;
// Validate receiver
if (!$receiver_id) {
echo json_encode(['success' => false, 'message' => 'Invalid receiver']);
exit;
}
// Check if users are blocked
$stmt = $pdo->prepare("
SELECT id FROM blocked_users
WHERE (user_id = ? AND blocked_user_id = ?)
OR (user_id = ? AND blocked_user_id = ?)
");
$stmt->execute([$sender_id, $receiver_id, $receiver_id, $sender_id]);
if ($stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'Cannot send message to this user']);
exit;
}
// Handle file upload if present
if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'text/plain'];
$max_size = 5 * 1024 * 1024; // 5MB
if (!in_array($_FILES['file']['type'], $allowed_types)) {
echo json_encode(['success' => false, 'message' => 'File type not allowed']);
exit;
}
if ($_FILES['file']['size'] > $max_size) {
echo json_encode(['success' => false, 'message' => 'File too large (max 5MB)']);
exit;
}
$extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $extension;
$upload_path = '../assets/uploads/' . $filename;
if (move_uploaded_file($_FILES['file']['tmp_name'], $upload_path)) {
$file_path = 'assets/uploads/' . $filename;
$file_type = $_FILES['file']['type'];
}
}
// Insert message
$stmt = $pdo->prepare("
INSERT INTO messages (sender_id, receiver_id, message, file_path, file_type, created_at)
VALUES (?, ?, ?, ?, ?, NOW())
");
$success = $stmt->execute([$sender_id, $receiver_id, $message, $file_path, $file_type]);
if ($success) {
$message_id = $pdo->lastInsertId();
// Get the inserted message
$stmt = $pdo->prepare("
SELECT m.*, u.full_name as sender_name, u.avatar as sender_avatar
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE m.id = ?
");
$stmt->execute([$message_id]);
$new_message = $stmt->fetch();
echo json_encode([
'success' => true,
'message' => 'Message sent',
'data' => $new_message
]);
} else {
echo json_encode(['success' => false, 'message' => 'Failed to send message']);
}
?>
7. API - Get Messages (api/get_messages.php)
<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
header('Content-Type: application/json');
if (!isLoggedIn()) {
echo json_encode(['success' => false, 'message' => 'Not authenticated']);
exit;
}
$user_id = $_SESSION['user_id'];
$chat_with = $_GET['chat_with'] ?? 0;
$last_id = $_GET['last_id'] ?? 0;
if (!$chat_with) {
echo json_encode(['success' => false, 'message' => 'No chat partner specified']);
exit;
}
// Get messages
$query = "
SELECT m.*,
u.full_name as sender_name,
u.avatar as sender_avatar
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE (m.sender_id = ? AND m.receiver_id = ? AND m.deleted_by_sender = 0)
OR (m.sender_id = ? AND m.receiver_id = ? AND m.deleted_by_receiver = 0)
";
$params = [$user_id, $chat_with, $chat_with, $user_id];
if ($last_id > 0) {
$query .= " AND m.id > ?";
$params[] = $last_id;
}
$query .= " ORDER BY m.created_at ASC";
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$messages = $stmt->fetchAll();
// Get typing status
$typing_stmt = $pdo->prepare("
SELECT is_typing FROM typing_status
WHERE user_id = ? AND chatting_with = ?
");
$typing_stmt->execute([$chat_with, $user_id]);
$typing = $typing_stmt->fetch();
// Get partner status
$status_stmt = $pdo->prepare("SELECT status, last_seen FROM users WHERE id = ?");
$status_stmt->execute([$chat_with]);
$partner_status = $status_stmt->fetch();
echo json_encode([
'success' => true,
'messages' => $messages,
'typing' => $typing ? (bool)$typing['is_typing'] : false,
'partner_status' => $partner_status
]);
?>
8. JavaScript - Chat Functionality (assets/js/chat.js)
// Chat Application JavaScript
let currentUserId = null;
let currentChatWith = null;
let lastMessageId = 0;
let typingTimer = null;
let isTyping = false;
document.addEventListener('DOMContentLoaded', function() {
// Get current user ID from session (passed via PHP)
currentUserId = document.querySelector('meta[name="user-id"]')?.content;
currentChatWith = document.getElementById('receiver-id')?.value;
if (currentChatWith) {
startMessagePolling();
setupTypingIndicator();
setupEmojiPicker();
markMessagesAsRead();
}
setupUserSearch();
setupEnterKey();
});
// Poll for new messages every 2 seconds
function startMessagePolling() {
pollMessages();
setInterval(pollMessages, 2000);
}
function pollMessages() {
if (!currentChatWith) return;
fetch(`api/get_messages.php?chat_with=${currentChatWith}&last_id=${lastMessageId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
// Update messages
if (data.messages.length > 0) {
appendMessages(data.messages);
lastMessageId = data.messages[data.messages.length - 1].id;
}
// Update typing indicator
updateTypingIndicator(data.typing);
// Update partner status
updatePartnerStatus(data.partner_status);
}
})
.catch(error => console.error('Error polling messages:', error));
}
function appendMessages(messages) {
const container = document.getElementById('messages-container');
messages.forEach(msg => {
const isMe = msg.sender_id == currentUserId;
const messageHtml = createMessageHtml(msg, isMe);
// Remove loading indicator if present
const loading = container.querySelector('.loading');
if (loading) loading.remove();
container.insertAdjacentHTML('beforeend', messageHtml);
});
// Scroll to bottom
container.scrollTop = container.scrollHeight;
}
function createMessageHtml(message, isMe) {
const time = new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
let content = '';
if (message.message) {
content += `<p class="message-text">${escapeHtml(message.message)}</p>`;
}
if (message.file_path) {
const fileUrl = message.file_path;
const fileType = message.file_type;
if (fileType.startsWith('image/')) {
content += `<img src="${fileUrl}" class="message-image" onclick="viewImage('${fileUrl}')">`;
} else {
content += `<a href="${fileUrl}" target="_blank" class="message-file">π Download File</a>`;
}
}
const readReceipt = isMe ? (message.is_read ? 'ββ' : 'β') : '';
return `
<div class="message ${isMe ? 'message-out' : 'message-in'}" data-id="${message.id}">
<div class="message-content">
${content}
<div class="message-meta">
<span class="message-time">${time}</span>
<span class="message-status">${readReceipt}</span>
</div>
</div>
</div>
`;
}
function sendMessage() {
const input = document.getElementById('message-text');
const message = input.value.trim();
const receiverId = document.getElementById('receiver-id').value;
if (!message && !fileToUpload) return;
const formData = new FormData();
formData.append('receiver_id', receiverId);
formData.append('message', message);
// Add file if exists
const fileInput = document.getElementById('file-input');
if (fileInput.files.length > 0) {
formData.append('file', fileInput.files[0]);
}
fetch('api/send_message.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
input.value = '';
fileInput.value = '';
// Immediately append the message
appendMessages([data.data]);
} else {
alert('Failed to send message: ' + data.message);
}
})
.catch(error => console.error('Error sending message:', error));
}
// Typing indicator
function setupTypingIndicator() {
const input = document.getElementById('message-text');
input.addEventListener('input', function() {
if (!isTyping) {
isTyping = true;
updateTypingStatus(true);
}
clearTimeout(typingTimer);
typingTimer = setTimeout(function() {
isTyping = false;
updateTypingStatus(false);
}, 1000);
});
}
function updateTypingStatus(typing) {
const receiverId = document.getElementById('receiver-id').value;
fetch('api/typing.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `receiver_id=${receiverId}&typing=${typing ? 1 : 0}`
});
}
function updateTypingIndicator(isTyping) {
const indicator = document.getElementById('typing-indicator');
if (indicator) {
indicator.style.display = isTyping ? 'flex' : 'none';
}
}
// Mark messages as read
function markMessagesAsRead() {
const receiverId = document.getElementById('receiver-id').value;
fetch('api/mark_read.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `sender_id=${receiverId}`
});
}
// Emoji picker
function setupEmojiPicker() {
const picker = new EmojiMart.Picker({
onSelect: emoji => {
const input = document.getElementById('message-text');
input.value += emoji.native;
input.focus();
}
});
document.getElementById('emoji-picker').appendChild(picker);
document.getElementById('emoji-btn').addEventListener('click', function() {
const picker = document.getElementById('emoji-picker');
picker.style.display = picker.style.display === 'none' ? 'block' : 'none';
});
}
// File upload
function uploadFile() {
sendMessage(); // Reuse send message function
}
// User search
function setupUserSearch() {
const searchInput = document.getElementById('search-users');
searchInput.addEventListener('input', function() {
const term = this.value.toLowerCase();
const users = document.querySelectorAll('.user-item');
users.forEach(user => {
const name = user.querySelector('h4').textContent.toLowerCase();
if (name.includes(term)) {
user.style.display = 'flex';
} else {
user.style.display = 'none';
}
});
});
}
// Enter key to send
function setupEnterKey() {
const input = document.getElementById('message-text');
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
}
// Helper: Escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Helper: View image fullscreen
function viewImage(url) {
const modal = document.createElement('div');
modal.className = 'image-modal';
modal.innerHTML = `
<span class="close">×</span>
<img src="${url}" class="modal-content">
`;
modal.onclick = function() {
modal.remove();
};
document.body.appendChild(modal);
}
9. Admin Dashboard (admin/dashboard.php)
<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkAdmin();
// Get statistics
$totalUsers = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$totalMessages = $pdo->query("SELECT COUNT(*) FROM messages")->fetchColumn();
$onlineUsers = $pdo->query("SELECT COUNT(*) FROM users WHERE status = 'online'")->fetchColumn();
$newToday = $pdo->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()")->fetchColumn();
// Get recent users
$recentUsers = $pdo->query("
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10
")->fetchAll();
// Get recent messages
$recentMessages = $pdo->query("
SELECT m.*,
u1.full_name as sender_name,
u2.full_name as receiver_name
FROM messages m
JOIN users u1 ON m.sender_id = u1.id
JOIN users u2 ON m.receiver_id = u2.id
ORDER BY m.created_at DESC
LIMIT 10
")->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - <?php echo SITE_NAME; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
</head>
<body>
<div class="admin-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Admin Panel</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="dashboard.php" class="active">Dashboard</a></li>
<li><a href="users.php">Manage Users</a></li>
<li><a href="chats.php">Monitor Chats</a></li>
<li><a href="broadcast.php">Broadcast</a></li>
<li><a href="logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="admin-main">
<div class="container">
<h1>Dashboard</h1>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">π₯</div>
<div class="stat-details">
<h3><?php echo $totalUsers; ?></h3>
<p>Total Users</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">π¬</div>
<div class="stat-details">
<h3><?php echo $totalMessages; ?></h3>
<p>Total Messages</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">π’</div>
<div class="stat-details">
<h3><?php echo $onlineUsers; ?></h3>
<p>Online Now</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">π
</div>
<div class="stat-details">
<h3><?php echo $newToday; ?></h3>
<p>New Today</p>
</div>
</div>
</div>
<!-- Recent Users -->
<div class="card">
<div class="card-header">
<h3>Recent Users</h3>
<a href="users.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Full Name</th>
<th>Email</th>
<th>Status</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentUsers as $user): ?>
<tr>
<td>#<?php echo $user['id']; ?></td>
<td><?php echo $user['username']; ?></td>
<td><?php echo $user['full_name']; ?></td>
<td><?php echo $user['email']; ?></td>
<td>
<span class="badge badge-<?php echo $user['status']; ?>">
<?php echo $user['status']; ?>
</span>
</td>
<td><?php echo date('M d, Y', strtotime($user['created_at'])); ?></td>
<td>
<a href="users.php?edit=<?php echo $user['id']; ?>" class="btn-small">Edit</a>
<a href="users.php?block=<?php echo $user['id']; ?>" class="btn-small btn-danger">Block</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Recent Messages -->
<div class="card">
<div class="card-header">
<h3>Recent Messages</h3>
<a href="chats.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>From</th>
<th>To</th>
<th>Message</th>
<th>Time</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentMessages as $msg): ?>
<tr>
<td><?php echo $msg['sender_name']; ?></td>
<td><?php echo $msg['receiver_name']; ?></td>
<td><?php echo substr($msg['message'], 0, 50) . '...'; ?></td>
<td><?php echo timeAgo($msg['created_at']); ?></td>
<td>
<?php if ($msg['is_read']): ?>
<span class="badge badge-success">Read</span>
<?php else: ?>
<span class="badge badge-warning">Unread</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</main>
</div>
</body>
</html>
10. CSS Styling (assets/css/chat.css)
/* Chat Application Specific Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
height: 100vh;
overflow: hidden;
}
.chat-app {
display: flex;
height: 100vh;
background: #f0f2f5;
}
/* Sidebar Styles */
.sidebar {
width: 350px;
background: white;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 20px;
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.user-profile {
display: flex;
align-items: center;
gap: 12px;
}
.user-profile .avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.user-info h3 {
font-size: 16px;
margin-bottom: 4px;
}
.user-info .status {
font-size: 12px;
color: #4caf50;
}
.sidebar-actions {
display: flex;
gap: 8px;
}
.btn-icon {
width: 36px;
height: 36px;
border: none;
background: transparent;
border-radius: 50%;
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.btn-icon:hover {
background: #e0e0e0;
}
.search-box {
padding: 15px;
border-bottom: 1px solid #e0e0e0;
}
.search-box input {
width: 100%;
padding: 10px 15px;
border: 1px solid #e0e0e0;
border-radius: 20px;
font-size: 14px;
outline: none;
}
.search-box input:focus {
border-color: #0084ff;
}
.users-list {
flex: 1;
overflow-y: auto;
}
.user-item {
display: flex;
align-items: center;
padding: 15px;
text-decoration: none;
color: inherit;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
position: relative;
}
.user-item:hover {
background: #f5f5f5;
}
.user-item.active {
background: #e3f2fd;
}
.user-avatar {
position: relative;
margin-right: 12px;
}
.user-avatar img {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.status-dot {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid white;
}
.status-dot.online {
background: #4caf50;
}
.status-dot.offline {
background: #9e9e9e;
}
.user-details {
flex: 1;
}
.user-details h4 {
font-size: 16px;
margin-bottom: 4px;
}
.last-message {
font-size: 13px;
color: #666;
}
.unread-badge {
background: #0084ff;
color: white;
font-size: 12px;
font-weight: bold;
min-width: 20px;
height: 20px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
}
/* Chat Area */
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
background: #f0f2f5;
}
.chat-header {
padding: 15px 20px;
background: white;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-user-info {
display: flex;
align-items: center;
gap: 12px;
}
.chat-user-info .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.chat-user-info h3 {
font-size: 16px;
margin-bottom: 4px;
}
.user-status {
font-size: 12px;
color: #666;
}
/* Messages Container */
.messages-container {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
.message {
display: flex;
margin-bottom: 10px;
}
.message-in {
justify-content: flex-start;
}
.message-out {
justify-content: flex-end;
}
.message-content {
max-width: 60%;
padding: 10px 15px;
border-radius: 18px;
position: relative;
word-wrap: break-word;
}
.message-in .message-content {
background: white;
border-bottom-left-radius: 4px;
}
.message-out .message-content {
background: #0084ff;
color: white;
border-bottom-right-radius: 4px;
}
.message-text {
margin-bottom: 5px;
line-height: 1.4;
}
.message-meta {
font-size: 11px;
opacity: 0.7;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 5px;
}
.message-image {
max-width: 200px;
max-height: 200px;
border-radius: 8px;
cursor: pointer;
margin-bottom: 5px;
}
.message-file {
display: inline-block;
padding: 5px 10px;
background: rgba(0,0,0,0.1);
border-radius: 4px;
text-decoration: none;
color: inherit;
margin-bottom: 5px;
}
/* Typing Indicator */
.typing-indicator {
padding: 10px 20px;
background: white;
border-top: 1px solid #e0e0e0;
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
color: #666;
}
.dots span {
animation: dots 1.5s infinite;
opacity: 0;
}
.dots span:nth-child(1) { animation-delay: 0s; }
.dots span:nth-child(2) { animation-delay: 0.2s; }
.dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes dots {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
/* Message Input */
.message-input {
padding: 15px 20px;
background: white;
border-top: 1px solid #e0e0e0;
display: flex;
align-items: center;
gap: 10px;
}
.message-input input[type="text"] {
flex: 1;
padding: 12px 15px;
border: 1px solid #e0e0e0;
border-radius: 24px;
font-size: 14px;
outline: none;
}
.message-input input[type="text"]:focus {
border-color: #0084ff;
}
.btn-send {
padding: 10px 24px;
background: #0084ff;
color: white;
border: none;
border-radius: 24px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.btn-send:hover {
background: #0073e6;
}
/* Emoji Picker */
#emoji-picker {
position: absolute;
bottom: 80px;
right: 20px;
z-index: 1000;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
/* No Chat Selected */
.no-chat-selected {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #f0f2f5;
}
.welcome-message {
text-align: center;
color: #666;
}
.welcome-message h2 {
font-size: 24px;
margin-bottom: 10px;
color: #333;
}
/* Image Modal */
.image-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.image-modal .modal-content {
max-width: 90%;
max-height: 90%;
object-fit: contain;
}
.image-modal .close {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
/* Loading */
.loading {
text-align: center;
color: #666;
padding: 20px;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
π How to Use This Project (Step-by-Step Guide)
Step 1: Install Local Server
- Download and install XAMPP from apachefriends.org
- Launch XAMPP Control Panel
- Start Apache and MySQL services
Step 2: Create Project Folder
- Navigate to
C:\xampp\htdocs\(Windows) or/Applications/XAMPP/htdocs/(Mac) - Create a new folder named
chat-application
Step 3: Set Up Database
- Open browser and go to
http://localhost/phpmyadmin - Click on "SQL" tab
- Copy the entire SQL from
database/chat_app.sql - Paste and click "Go" to create database and tables
Step 4: Create Password Hashes
Create a file named hash.php in project root:
<?php
echo "password123 hash: " . password_hash('password123', PASSWORD_DEFAULT);
?>
Run it: http://localhost/chat-application/hash.php
Copy the hash and update it in the SQL insert statements for the users.
Step 5: Configure Database Connection
Open includes/config.php and verify:
define('DB_HOST', 'localhost');
define('DB_NAME', 'chat_app');
define('DB_USER', 'root');
define('DB_PASS', '');
Step 6: Create Upload Directories
Create these folders and set permissions:
assets/uploads/- for file sharingassets/images/avatars/- for profile pictures- Copy a default avatar image as
default.pngin avatars folder
Step 7: Test the Application
Access Points:
- Main Site:
http://localhost/chat-application/ - Login:
http://localhost/chat-application/login.php - Register:
http://localhost/chat-application/register.php - Admin:
http://localhost/chat-application/admin/
Test Accounts:
| Role | Username | Password |
|---|---|---|
| Admin | admin | password123 |
| User 1 | john | password123 |
| User 2 | jane | password123 |
Step 8: Test Features
User Features:
- Register a new account
- Login with credentials
- View online users in sidebar
- Select a user to chat with
- Send text messages
- Send emojis (click π button)
- Share files/images (click π button)
- See typing indicators
- View read receipts (β for delivered, ββ for read)
Admin Features:
- Login as admin
- View dashboard statistics
- Manage users (edit/block/delete)
- Monitor all chats
- Send broadcast announcements
π― Features Summary
User Features:
- β User registration and login
- β Profile management
- β Online/offline status
- β Real-time messaging (polling)
- β Emoji support
- β File/image sharing
- β Typing indicators
- β Read receipts
- β Message history
- β Search users
Admin Features:
- β Dashboard with statistics
- β User management
- β Chat monitoring
- β Broadcast announcements
- β View all messages
- β Block/unblock users
Technical Features:
- β Secure password hashing
- β Session management
- β AJAX polling for real-time
- β File upload handling
- β SQL injection prevention
- β XSS protection
- β Responsive design
- β Foreign key constraints
π Future Enhancements
- WebSockets: Replace polling with WebSockets for true real-time
- Group Chats: Create group conversations
- Voice/Video Calls: WebRTC integration
- Push Notifications: Browser notifications
- End-to-End Encryption: Message encryption
- Message Reactions: Like/emoji reactions
- Message Editing/Deletion: Edit or delete sent messages
- User Blocking: Block unwanted users
- Chat Search: Search through message history
- Dark Mode: Theme switching
- Mobile Apps: React Native or Flutter apps
- Message Backup: Export chat history
- Custom Emojis: Upload custom emojis
- Voice Messages: Record and send voice
- Location Sharing: Share real-time location
This comprehensive Chat Application provides a solid foundation for real-time communication that can be extended with additional features as needed!