diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js
index 5a2f718..3b59ba0 100644
--- a/backend/src/db/db.config.js
+++ b/backend/src/db/db.config.js
@@ -1,5 +1,3 @@
-
-
module.exports = {
production: {
dialect: 'postgres',
@@ -12,10 +10,10 @@ module.exports = {
seederStorage: 'sequelize',
},
development: {
- username: 'postgres',
+ username: process.env.DB_USER || 'postgres',
dialect: 'postgres',
- password: '',
- database: 'db_app_draft',
+ password: process.env.DB_PASS || '',
+ database: process.env.DB_NAME || 'db_app_draft',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',
@@ -30,4 +28,4 @@ module.exports = {
logging: console.log,
seederStorage: 'sequelize',
}
-};
+};
\ No newline at end of file
diff --git a/backend/src/db/seeders/20260129000000-chat-demo.js b/backend/src/db/seeders/20260129000000-chat-demo.js
new file mode 100644
index 0000000..38fa9dc
--- /dev/null
+++ b/backend/src/db/seeders/20260129000000-chat-demo.js
@@ -0,0 +1,88 @@
+'use strict';
+const { v4: uuidv4 } = require('uuid');
+
+const adminId = '193bf4b5-9f07-4bd5-9a43-e7e41f3e96af';
+const johnId = 'af5a87be-8f9c-4630-902a-37a60b7005ba';
+const clientId = '5bc531ab-611f-41f3-9373-b7cc5d09c93d';
+
+const convId1 = uuidv4();
+const convId2 = uuidv4();
+
+module.exports = {
+ up: async (queryInterface, Sequelize) => {
+ try {
+ // Create Conversations
+ await queryInterface.bulkInsert('conversations', [
+ {
+ id: convId1,
+ title: 'Project Nexus Sync',
+ is_group: true,
+ status: 'active',
+ last_message_preview: 'The video call module is ready for testing!',
+ createdAt: new Date(),
+ updatedAt: new Date()
+ },
+ {
+ id: convId2,
+ title: 'John Doe',
+ is_group: false,
+ status: 'active',
+ last_message_preview: 'Hey! Did you check the new landing page?',
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }
+ ]);
+
+ // Create Participants (Many-to-Many through table)
+ await queryInterface.bulkInsert('conversationsParticipantsUsers', [
+ { conversations_participantsId: convId1, users_custom_permissionsId: adminId, createdAt: new Date(), updatedAt: new Date() },
+ { conversations_participantsId: convId1, users_custom_permissionsId: johnId, createdAt: new Date(), updatedAt: new Date() },
+ { conversations_participantsId: convId1, users_custom_permissionsId: clientId, createdAt: new Date(), updatedAt: new Date() },
+ { conversations_participantsId: convId2, users_custom_permissionsId: adminId, createdAt: new Date(), updatedAt: new Date() },
+ { conversations_participantsId: convId2, users_custom_permissionsId: johnId, createdAt: new Date(), updatedAt: new Date() }
+ ]);
+
+ // Create Messages
+ await queryInterface.bulkInsert('messages', [
+ {
+ id: uuidv4(),
+ content: 'Hello everyone! Welcome to Nexus Chat.',
+ conversationId: convId1,
+ senderId: adminId,
+ status: 'read',
+ sent_at: new Date(),
+ createdAt: new Date(Date.now() - 3600000),
+ updatedAt: new Date()
+ },
+ {
+ id: uuidv4(),
+ content: 'The video call module is ready for testing!',
+ conversationId: convId1,
+ senderId: johnId,
+ status: 'sent',
+ sent_at: new Date(),
+ createdAt: new Date(Date.now() - 1800000),
+ updatedAt: new Date()
+ },
+ {
+ id: uuidv4(),
+ content: 'Hey! Did you check the new landing page?',
+ conversationId: convId2,
+ senderId: johnId,
+ status: 'read',
+ sent_at: new Date(),
+ createdAt: new Date(Date.now() - 900000),
+ updatedAt: new Date()
+ }
+ ]);
+ } catch (error) {
+ console.error('Error during chat demo seeding:', error);
+ }
+ },
+
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.bulkDelete('messages', null, {});
+ await queryInterface.bulkDelete('conversationsParticipantsUsers', null, {});
+ await queryInterface.bulkDelete('conversations', null, {});
+ }
+};
diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index 72935e6..4ced3eb 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef} from 'react'
+import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
}
return
{NavBarItemComponentContents}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 1b9907d..26c3572 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,5 +1,4 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
)
-}
+}
\ No newline at end of file
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index f7655d2..32fdee1 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -7,7 +7,11 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline,
label: 'Dashboard',
},
-
+ {
+ href: '/chat',
+ icon: icon.mdiChatProcessingOutline,
+ label: 'Nexus Chat',
+ },
{
href: '/users/users-list',
label: 'Users',
@@ -72,4 +76,4 @@ const menuAside: MenuAsideItem[] = [
},
]
-export default menuAside
+export default menuAside
\ No newline at end of file
diff --git a/frontend/src/pages/chat.tsx b/frontend/src/pages/chat.tsx
new file mode 100644
index 0000000..eb54d11
--- /dev/null
+++ b/frontend/src/pages/chat.tsx
@@ -0,0 +1,214 @@
+import React, { useEffect, useState, useRef } from 'react';
+import type { ReactElement } from 'react';
+import Head from 'next/head';
+import LayoutAuthenticated from '../layouts/Authenticated';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
+import { fetch as fetchConversations } from '../stores/conversations/conversationsSlice';
+import { fetch as fetchMessages, create as createMessage } from '../stores/messages/messagesSlice';
+import { mdiSend, mdiVideo, mdiMagnify, mdiDotsVertical, mdiPaperclip, mdiChatProcessingOutline } from '@mdi/js';
+import BaseIcon from '../components/BaseIcon';
+import moment from 'moment';
+
+export default function ChatPage() {
+ const dispatch = useAppDispatch();
+ const { conversations, loading: loadingConversations } = useAppSelector((state) => state.conversations);
+ const { messages, loading: loadingMessages } = useAppSelector((state) => state.messages);
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const [selectedConversation, setSelectedConversation] = useState(null);
+ const [messageText, setMessageText] = useState('');
+ const messagesEndRef = useRef(null);
+
+ useEffect(() => {
+ dispatch(fetchConversations({ query: '?limit=100' }));
+ }, [dispatch]);
+
+ useEffect(() => {
+ if (selectedConversation) {
+ dispatch(fetchMessages({ query: `?conversation=${selectedConversation.id}&limit=100` }));
+ }
+ }, [selectedConversation, dispatch]);
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ const handleSendMessage = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!messageText.trim() || !selectedConversation || !currentUser) return;
+
+ const data = {
+ content: messageText,
+ conversation: selectedConversation.id,
+ sender: currentUser.id,
+ sent_at: new Date(),
+ status: 'sent'
+ };
+
+ await dispatch(createMessage(data));
+ setMessageText('');
+ dispatch(fetchMessages({ query: `?conversation=${selectedConversation.id}&limit=100` }));
+ };
+
+ return (
+
+
+
Nexus Chat | Messages
+
+
+ {/* Sidebar: Conversations List */}
+
+
+
+ {conversations?.map((conv: any) => (
+
setSelectedConversation(conv)}
+ className={`p-4 flex items-center space-x-3 cursor-pointer transition-all border-l-4 ${
+ selectedConversation?.id === conv.id
+ ? 'bg-indigo-50 dark:bg-indigo-900/20 border-indigo-600'
+ : 'hover:bg-gray-50 dark:hover:bg-dark-700 border-transparent'
+ }`}
+ >
+
+ {conv.title?.charAt(0) || 'C'}
+
+
+
+
{conv.title || 'Direct Message'}
+
+ {conv.updatedAt ? moment(conv.updatedAt).format('HH:mm') : ''}
+
+
+
+ {conv.last_message_preview || 'No messages yet...'}
+
+
+
+ ))}
+ {(!conversations || conversations.length === 0) && !loadingConversations && (
+
+ No conversations found.
+
+ )}
+
+
+
+ {/* Main Chat Area */}
+
+ {selectedConversation ? (
+ <>
+ {/* Chat Header */}
+
+
+
+ {selectedConversation.title?.charAt(0) || 'C'}
+
+
+
{selectedConversation.title || 'Conversation'}
+
Online
+
+
+
+
+
+
+
+
+ {/* Messages List */}
+
+ {messages?.map((msg: any) => {
+ const isMe = msg.senderId === currentUser?.id;
+ return (
+
+
+
+
+
+ {moment(msg.createdAt).format('HH:mm')}
+
+ {isMe && (
+ Read
+ )}
+
+
+
+ );
+ })}
+ {!loadingMessages && (!messages || messages.length === 0) && (
+
+
+
No messages in this conversation yet.
+
+ )}
+
+
+
+ {/* Input Area */}
+
+ >
+ ) : (
+
+
+
+
+
Select a conversation
+
+ Pick one from the list or start a new conversation to begin chatting.
+
+
+ )}
+
+
+ );
+}
+
+ChatPage.getLayout = function getLayout(page: ReactElement) {
+ return {page};
+};
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 2fdd258..bc5cff7 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,112 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
-import BaseButton from '../components/BaseButton';
-import CardBox from '../components/CardBox';
-import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
-import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-
-
-export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('right');
- const textColor = useAppSelector((state) => state.style.linkColor);
-
- const title = 'App Draft'
-
- // Fetch Pexels image/video
- useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
- }
- fetchData();
- }, []);
-
- const imageBlock = (image) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
- }
- };
+import { mdiChatProcessingOutline, mdiVideoOutline, mdiShieldLockOutline, mdiArrowRight } from '@mdi/js';
+import BaseIcon from '../components/BaseIcon';
+export default function LandingPage() {
return (
-
+
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Nexus Chat - Next Gen Messaging')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
-
-
-
-
-
+ {/* Navigation */}
+
-
-
-
© 2026 {title}. All rights reserved
-
- Privacy Policy
-
-
+
+
+ Login
+
+
+ Get Started
+
+
+
+ {/* Hero Section */}
+
+
+
+ Connect beyond
+
+ boundaries.
+
+
+
+ Experience seamless messaging and HD video calls with military-grade security.
+ The future of communication is here.
+
+
+
+ Launch Chat
+
+
+
+ Admin UI
+
+
+
+
+ {/* Features Grid */}
+
+
+
+
+
+
+
Instant Messaging
+
+ Real-time message delivery with rich media support, read receipts, and typing indicators.
+
+
+
+
+
+
+
HD Video Calls
+
+ Crystal clear video and audio quality with low latency, anywhere in the world.
+
+
+
+
+
+
+
Private & Secure
+
+ End-to-end encryption ensures your conversations stay between you and your contacts.
+
+
+
+
+
+ {/* Footer */}
+
);
}
-Starter.getLayout = function getLayout(page: ReactElement) {
+LandingPage.getLayout = function getLayout(page: ReactElement) {
return {page};
};
-