Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
eacb625
Set up basic project structure
Galane-dev Feb 10, 2026
ffc2019
Merge pull request #2 from Galane-dev/project/structure
Galane-dev Feb 10, 2026
458e403
Created basic local storage service
Galane-dev Feb 10, 2026
6a85e5e
Merge pull request #5 from Galane-dev/feature/local-storage
Galane-dev Feb 10, 2026
de29e6c
Added basic user model
Galane-dev Feb 10, 2026
a8683a4
Added models for chat, and message
Galane-dev Feb 10, 2026
eae0b37
Added session manager for log in and log out
Galane-dev Feb 10, 2026
20bbbcb
Merge pull request #7 from Galane-dev/feature/models
Galane-dev Feb 10, 2026
02314d3
added html for landing page
Galane-dev Feb 10, 2026
fbb8eb6
Added html code for landing page
Galane-dev Feb 10, 2026
122ce4e
Implemented auth functionality
Galane-dev Feb 11, 2026
5d93d12
Merge pull request #9 from Galane-dev/page/landing-page
Galane-dev Feb 11, 2026
e9f0173
added redirect to main page after login
Galane-dev Feb 11, 2026
2449b55
Merge pull request #11 from Galane-dev/page/landing-page
Galane-dev Feb 11, 2026
b39f107
Added users list and open chat functionality to main page
Galane-dev Feb 11, 2026
06fab54
Added list of users and open chat functionality
Galane-dev Feb 11, 2026
a35bc7d
Added send message functionality
Galane-dev Feb 11, 2026
846527d
Added send message functionality
Galane-dev Feb 11, 2026
5865d5a
Implemented show hide for messages
Galane-dev Feb 11, 2026
585ad2d
Basic HTML structure complete
Galane-dev Feb 11, 2026
5037d41
Added user search
Galane-dev Feb 11, 2026
0dfd80c
Added user search to main page
Galane-dev Feb 11, 2026
21c5ecf
Added online offline checks
Galane-dev Feb 11, 2026
52f3b69
Implemented edit profile functionality
Galane-dev Feb 11, 2026
01b313f
implemented logout functionality
Galane-dev Feb 11, 2026
7936d45
Added logout functionality
Galane-dev Feb 11, 2026
45d1e4d
implemented user side profile
Galane-dev Feb 11, 2026
b301a61
added groups
Galane-dev Feb 12, 2026
f243f3b
Merge pull request #17 from Galane-dev/page/main
Galane-dev Feb 12, 2026
0f13d7e
Added nav section styling
Galane-dev Feb 12, 2026
141e3a0
Added nav section styling
Galane-dev Feb 12, 2026
6b17572
Added chat styling
Galane-dev Feb 12, 2026
e6868d1
Added chat styling
Galane-dev Feb 12, 2026
69e9a50
added user profile edit styling
Galane-dev Feb 12, 2026
1b377cb
Merge pull request #20 from Galane-dev/page/main
Galane-dev Feb 12, 2026
ec03258
Added navigation
Galane-dev Feb 12, 2026
3cd593c
Added navigation for mobile clients
Galane-dev Feb 12, 2026
8bc39c8
refactored main page js
Galane-dev Feb 12, 2026
213b2c9
Merge pull request #23 from Galane-dev/process/refactoring
Galane-dev Feb 12, 2026
17bd8fd
refactored models and landing page js
Galane-dev Feb 12, 2026
a6d3f8f
Merge dev branch code into main
Galane-dev Feb 12, 2026
d728795
Revise README for clarity and updated instructions
Galane-dev Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 8 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,19 @@ LocalChat is a modern local chat app that allows users to create accounts, chat
* users are able to see and message groups
* users are able to recieve and send messages in real time

# Design

## [UI Design](https://www.figma.com/design/97l0hfe6ogeRn0mjbfe4Gy/Untitled?node-id=0-1&m=dev&t=j02J3MH5C7mYCmF1-1)

# running application
## FRONTEND
npm install
*I nstall Live Server VS Code extension
* Run index.html with Live Server

## Development
npm run dev
*I nstall Live Server VS Code extension
* Run index.html with Live Server

## Production
* npm run build
* npm start

## Docker frontend (if you have environement setup)

* npm run docker
* npm run docker-start

docker currently running in detach mode so you will need to add the following under environment variables

* ENV NEXT_PUBLIC_API_BASE_URI ACTUAL_BASE_URL

# BACKEND

Visual Studio
* select web.host as startup project
* build application
* run application under IIS Express

# FRONTEND-CI

* npm run ci
* visit galane-dev.github.io/LocalChat/

# Setup for husky
In the client directory run the following command
* npm run prepare

Binary file added assets/images/icons/add.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icons/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icons/image1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icons/profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icons/search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/icons/send.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>Authenticate | LocalChat</title>
<link href="./styles/landing-page.css" rel="stylesheet">
</head>
<body>
<main>
<article>
<div id="landing-message">
<img id="logo-image" src="./assets/images/logo.png" alt="logo image">
<h1 id="welcome-heading">Welcome Back...</h1>
<p id="welcome-message">LocalChat is a local chat app<br>
Your data is stored securely in your own <br>computer, no one but you will be able to access the data</p>
</div>
</article>
<!--The login form on the side-->
<aside>
<section id="landing-form">
<h3 id="text-logo">Local<span>Chat</span></h3>
<form>
<input id="username-input" name="username-input" type="text" placeholder="Enter username">
<input id="password-input" name="password-input" type="password" placeholder="Enter password">
<button id="submit-button" name="submit-button" type="button">Sign In</button>
</form>
<p id="toggle-question">Don't have an account?</p>
<p id="toggle-action">Sign Up</p>
</section>
</aside>
</main>
<footer></footer>
<script type="module" src="./js/landing-page.js"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions js/helpers/create-message-tile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const createMessageTile = (message, isCurrentUser) => {
const tile=document.createElement('li');
tile.id='message-tile';

const messageText=document.createElement('p');
messageText.id='message-tile-text';
messageText.innerText=message.content;

const messageDate=document.createElement('p');
messageDate.id='message-date';
messageDate.textContent=new Date(message.timestamp).toDateString();

tile.style.backgroundColor=isCurrentUser?'rgb(244, 107, 107)'
:'rgb(169, 169, 169)';
tile.append(messageText, messageDate);

return tile;
};

export default createMessageTile;
29 changes: 29 additions & 0 deletions js/helpers/create-user-tile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import getLastText from "./get-last-text.js";
import openChat from "../main-page.js"

const createUserTile=(currentUser, user)=>{
const isGroup=user.participants!==undefined;
const tile=document.createElement('li');
const profilePicture=document.createElement('img');
const username=document.createElement('h3');
const lastMessage=document.createElement('p');
const onlineBadge=document.createElement('h4');

profilePicture.src='../assets/images/icons/image.png';
username.textContent=isGroup?user.name:user.username;
lastMessage.textContent=getLastText(isGroup,user.id);
onlineBadge.textContent = '•';
onlineBadge.style.color = 'green';
onlineBadge.style.display = (user.isOnline && !isGroup) ? 'inline' : 'none';

tile.addEventListener('click',()=>{
isGroup?openChat(user.id,null,'group')
:openChat(currentUser.id,user.id,'private');
});
tile.append(profilePicture, username, lastMessage, onlineBadge);

return tile;

}

export default createUserTile;
24 changes: 24 additions & 0 deletions js/helpers/display-messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import createMessageTile from "./create-message-tile.js";

const displayMessages=(chat,messagesCount,currentUser)=>{
const chatsList=document.getElementById('chats-list');
const noMessages=document.getElementById('no-messages');

if(messagesCount>0){
chatsList.style.display='inline';
noMessages.style.display='none';
}
else{
chatsList.style.display='none';
noMessages.style.display='inline';
return;
}

chat.messages.forEach(currentMessage => {
const message= currentMessage;
const messageTile = createMessageTile(message, message.senderId === currentUser);
chatsList.appendChild(messageTile);
});
}

export default displayMessages;
25 changes: 25 additions & 0 deletions js/helpers/get-chat-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Chat from "../models/chat.js";
import User from "../models/user.js";
import LocalStorageService from "../services/local-storage.js";

const getChatData=(user1,user2,type)=>{
const chatsList=document.getElementById('chats-list');
chatsList.innerHTML='';
let chatId,currentChat,messagesCount;
if(type==='private'){
chatId=Chat.generateChatId(user1, user2);
const allChats=LocalStorageService.getChats();
currentChat=allChats.find(chat=>chat.id===chatId);
}
else{
const groups=LocalStorageService.getGroups();
currentChat=groups.find(group=>group.id===user1);
chatId=user1;
}

messagesCount=currentChat?.messages?.length||0;

return {chatId,currentChat,messagesCount};
}

export default getChatData;
32 changes: 32 additions & 0 deletions js/helpers/get-last-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import LocalStorageService from "../services/local-storage.js";
import SessionManager from "../services/session-manager.js";
import Chat from "../models/chat.js";

const getLastText=(isGroup,userId)=>{
let allChats = LocalStorageService.getChats();
let allGroups = LocalStorageService.getGroups();
let currentUserId = SessionManager.getUser().id;
let lastMsgText='No message yet'

if (isGroup) {
let group = allGroups.find(g=>g.id===userId);

if (group && group.messages && group.messages.length > 0) {
let lastMsg = group.messages[group.messages.length - 1];
lastMsgText = lastMsg.content;

}
}
else {
let chatId = Chat.generateChatId(currentUserId, userId);
let chat = allChats.find(c => c.id === chatId);

if (chat && chat.messages && chat.messages.length > 0) {
let lastMsg = chat.messages[chat.messages.length - 1];
lastMsgText = lastMsg.content;
}
}
return lastMsgText;
}

export default getLastText;
21 changes: 21 additions & 0 deletions js/helpers/set-up-send-message-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import LocalStorageService from "../services/local-storage.js";
import openChat from "../main-page.js";

const setupSendMessageHandler = (userId1, userId2, type, chatId) => {
const sendIcon=document.getElementById('message-send');
sendIcon.onclick = null;
sendIcon.onclick=()=>{
const content= document.getElementById('message-text').value.trim();
if (content==='') return;

if (type==='group') {
LocalStorageService.messageGroup(chatId, content, 'none');
} else {
LocalStorageService.sendMessage(chatId, content, 'none');
}
document.getElementById('message-text').value = '';
openChat(userId1, userId2, type);
};
};

export default setupSendMessageHandler;
27 changes: 27 additions & 0 deletions js/helpers/switch-views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const switchMobileView=(view)=>{
const nav = document.querySelector('nav');
const main = document.querySelector('main');
const aside = document.querySelector('aside');

const isMobile = window.matchMedia("(max-width:600px)").matches;

if(!isMobile){
return;
}

nav.style.display = 'none';
main.style.display = 'none';
aside.style.display = 'none';

if(view === 'nav'){
nav.style.display = 'block';
}
else if(view === 'chat'){
main.style.display = 'flex';
}
else if(view === 'profile'){
aside.style.display = 'block';
}
}

export default switchMobileView;
25 changes: 25 additions & 0 deletions js/helpers/update-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import LocalStorageService from "../services/local-storage.js";

const updateChatHeader=(chateeId,chatData,type)=>{
const chateeName=document.getElementById('chatee-name');
const chateeSideName=document.getElementById('chatee-side-name');
const chateeStatus=document.getElementById('chatee-status');
const chateeSideStatus=document.getElementById('chatee-side-status');

if(type==='private'){
const chatee=LocalStorageService.getUser(chateeId);
chateeName.textContent=chatee.username;
chateeSideName.textContent=chatee.username;
chateeStatus.textContent=chatee.isOnline?'Online':'Offline';
chateeSideStatus.textContent=chateeStatus.textContent;
}
else{
chateeName.textContent=chatData.currentChat.name;
chateeSideName.textContent=chatData.currentChat.name;
chateeStatus.textContent='Group';
chateeSideStatus.textContent='Group';
}

}

export default updateChatHeader;
63 changes: 63 additions & 0 deletions js/landing-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import LocalStorageService from "./services/local-storage.js";
import SessionManager from "./services/session-manager.js";
import User from "./models/user.js";

let isSignUp=true;
const defaultView=document.getElementById('toggle-action');
let authButton=document.getElementById('submit-button');

//Toggle view for toggling between login and signup
const toggleView=()=>{
let heading=document.getElementById('welcome-heading');
let toggleQuestion=document.getElementById('toggle-question');
let toggleView=document.getElementById('toggle-action');
let toggleMessage=document.getElementById('welcome-message');
let submitButton=document.getElementById('submit-button');
if(isSignUp){
heading.textContent='Hey newbie...';
toggleQuestion.textContent='Already have an account?';
toggleMessage.innerHTML='LocalChat is a local chat app.<br>Your data is stored securely in your own <br>computer, no one but you will be able to access the data.';
toggleView.textContent="Sign In";
submitButton.textContent='Sign Up';
}
else{
heading.textContent='Welcome Back...';
toggleQuestion.textContent='Don’t have an account?';
toggleMessage.innerHTML='LocalChat is a local chat app.<br>Your data is stored securely in your own <br>computer, no one but you will be able to access the data.';
toggleView.textContent='Sign Up';

}
isSignUp=!isSignUp;
}


const authenticate=()=>{
let username=document.getElementById('username-input').value;
let password=document.getElementById('password-input').value;
if(!isSignUp){
if(User.isUserNameUnique(username)){
LocalStorageService.createUser(username,password);
}
else{
alert('Please enter a new username, make it unique');
return;
}
}
//Login
let user=SessionManager.login(username,password);
if(user){
window.location.replace('./pages/main.html');
}
else{
alert('Login failed, ensure you have entered correct credentials');
return;
}
}


const main=()=>{
defaultView.addEventListener('click',()=>toggleView());
authButton.addEventListener('click', ()=>authenticate());
}

main();
Loading