diff --git a/assets/images/icons/add.png b/assets/images/icons/add.png new file mode 100644 index 0000000..dc1d6d0 Binary files /dev/null and b/assets/images/icons/add.png differ diff --git a/assets/images/icons/image.png b/assets/images/icons/image.png new file mode 100644 index 0000000..a698876 Binary files /dev/null and b/assets/images/icons/image.png differ diff --git a/assets/images/icons/profile.png b/assets/images/icons/profile.png new file mode 100644 index 0000000..44f169b Binary files /dev/null and b/assets/images/icons/profile.png differ diff --git a/assets/images/icons/search.png b/assets/images/icons/search.png new file mode 100644 index 0000000..1e7d0dd Binary files /dev/null and b/assets/images/icons/search.png differ diff --git a/assets/images/icons/send.png b/assets/images/icons/send.png new file mode 100644 index 0000000..1ca4cb0 Binary files /dev/null and b/assets/images/icons/send.png differ diff --git a/assets/images/icons/user.png b/assets/images/icons/user.png new file mode 100644 index 0000000..bd07199 Binary files /dev/null and b/assets/images/icons/user.png differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..e084b2a Binary files /dev/null and b/assets/images/logo.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..3f3212b --- /dev/null +++ b/index.html @@ -0,0 +1,43 @@ + + + + Authenticate | LocalChat + + + + +
+
+
+ logo image +

Welcome Back...

+

LocalChat is a local chat app
+ Your data is stored securely in your own
computer, no one but you will be able to access the data

+
+
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/js/landing-page.js b/js/landing-page.js new file mode 100644 index 0000000..07c51a3 --- /dev/null +++ b/js/landing-page.js @@ -0,0 +1,86 @@ +import LocalStorageService from "./services/local-storage.js"; +import SessionManager from "./services/session-manager.js"; +import User from "./models/user.js"; + + +console.log(LocalStorageService.getUsers()); +let isSignUp=true; +const defaultView=document.getElementById('toggle-action'); +let authButton=document.getElementById('submit-button'); + + +function 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.
Your data is stored securely in your own
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.
Your data is stored securely in your own
computer, no one but you will be able to access the data.'; + toggleView.textContent='Sign Up'; + submitButton.textContent='Sign In'; + + } + + isSignUp=!isSignUp; + console.log('done'); +} + + +function authenticate(){ + let username=document.getElementById('username-input').value; + let password=document.getElementById('password-input').value; + if(!isSignUp){ + if(User.isUserNameUnique(username)){ + //Create an account + LocalStorageService.createUser(username,password); + console.log(LocalStorageService.getUsers()); + } + else{ + alert('Please enter a new username, make it unique'); + return; + } + } + //Login + let user=SessionManager.login(username,password); + console.log(user); + if(user){ + console.log('logged in as '+ user.username); + //Navigate to the main page + window.location.replace('./pages/main.html'); + } + else{ + alert('Login failed, ensure you have entered correct credentials'); + return; + } +} + + + + + + + + + + +function main(){ + console.log('Main method test'); + defaultView.addEventListener('click',()=>toggleView()); + authButton.addEventListener('click', ()=>authenticate()); +} + +main(); \ No newline at end of file diff --git a/js/main-page.js b/js/main-page.js new file mode 100644 index 0000000..88b89b4 --- /dev/null +++ b/js/main-page.js @@ -0,0 +1,283 @@ +import LocalStorageService from "./services/local-storage.js"; +import SessionManager from "./services/session-manager.js"; +import Chat from "./models/chat.js"; + + +let currentChatId=null; +let currentChatUser1=null; +let currentChatUser2=null; +let currentChatType='private'; + + + +function populateUsersList(users){ + let userList=document.getElementById('users-list'); + let noUsersText=document.getElementById('no-users'); + userList.innerHTML=''; + + + if(users.length===0){ + userList.style.display='none'; + noUsersText.style.display='block'; + } + else{ + userList.style.display='block'; + noUsersText.style.display='none'; + } + + + let currentUserId=SessionManager.getUser().id; + + console.log('Users are '+users); + + + for(let i=0;i{ + if(isGroup){ + openChat(users[i].id,null,'group'); + } + else{ + openChat(currentUserId,users[i].id); + } + }); + + //Append tile elements to the tile/list item + userTile.append(userProfilePicture); + userTile.append(username); + userTile.append(lastMessage); + userTile.append(onlineBadge); + + //Append the tile/list item to the list; + userList.append(userTile); + + console.warn('Added user: '+users[i].username); + + } +} + + + +function openChat(userId1,userId2,type='private'){ + + let chatsList=document.getElementById('chats-list'); + let messagesBox=document.getElementById('messages-box'); + let sendIcon=document.getElementById('message-send'); + let noMessages=document.getElementById('no-messages'); + let chateeName=document.getElementById('chatee-name'); + let chateeStatus=document.getElementById('chatee-status'); + let chateeSideName=document.getElementById('chatee-side-name'); + let chateeSideStatus=document.getElementById('chatee-side-status'); + let chatId; + let currentChat; + let messagesCount=0; + + chatsList.innerHTML=''; + + if(type==='private'){ + + let allChats=LocalStorageService.getChats(); + chatId=Chat.generateChatId(userId1,userId2); + currentChat=allChats?.find(chat=>chat.id===chatId); + + let chatee=LocalStorageService.getUser(userId2); + + chateeName.textContent=chatee.username; + chateeSideName.textContent=chatee.username; + + if(chatee.isOnline){ + chateeStatus.textContent='Online'; + chateeSideStatus.textContent='Online'; + } + else{ + chateeStatus.textContent='Offline'; + chateeSideStatus.textContent='Offline'; + } + + messagesCount=currentChat?.messages?.length || 0; + } + + else if(type==='group'){ + let groups=LocalStorageService.getGroups(); + currentChat=groups.find(group=>group.id===userId1); + chatId=userId1; + + chateeName.textContent=currentChat.name; + chateeSideName.textContent=currentChat.name; + + chateeStatus.textContent='Group'; + chateeSideStatus.textContent='Group'; + + messagesCount=currentChat?.messages?.length || 0; + } + + + sendIcon.onclick=()=>{ + let content=document.getElementById('message-text').value.trim(); + console.log(content); + 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); + }; + + + if(messagesCount>0){ + noMessages.style.display='none'; + chatsList.style.display='inline'; + } + else{ + chatsList.style.display='none'; + noMessages.style.display='inline'; + } + + for(let i=0;iuser.username.toLowerCase().includes(textToSearch)); + console.error(usersToReturn); + + populateUsersList(usersToReturn); +} + +function applyFilters(){ + //This is a nice to have, implement if there's time +} + + +function updateProfile(){ + let username=document.getElementById('username-input').value; + let password=document.getElementById('password-input').value; + LocalStorageService.updateProfile(username,password); +} + + +function createGroup(){ + let groupPrompt=prompt('Enter group name and member Ids(e.g "Group Name":id1,id2,id3...)'); + groupPrompt=groupPrompt.split(':'); + let participants=groupPrompt[1].split(','); + let groupName=groupPrompt[0]; + + LocalStorageService.createGroup(groupName,participants); + +} + +function showUserProfile(){ + let userProfile=document.getElementById('current-user'); + let chateeProfile=document.getElementById('chatee'); + + userProfile.style.display='inline'; + chateeProfile.style.display='none'; +} + +function showChateeProfile(){ + let userProfile=document.getElementById('current-user'); + let chateeProfile=document.getElementById('chatee'); + + userProfile.style.display='none'; + chateeProfile.style.display='inline'; +} + + +function main(){ + let searchIcon=document.getElementById('search-icon'); + searchIcon.addEventListener('click',()=>searchUsers()); + + let saveEdits=document.getElementById('save-edits'); + saveEdits.addEventListener('click',()=>updateProfile()); + + let logout=document.getElementById('logout'); + logout.addEventListener('click',()=>SessionManager.logout()); + + let addGroup=document.getElementById('add-group-icon'); + addGroup.addEventListener('click',()=>createGroup()); + + let userProfile=document.getElementById('profile-icon'); + userProfile.addEventListener('click',()=>showUserProfile()); + + let chateeProfile=document.getElementById('chatee-info'); + chateeProfile.addEventListener('click',()=>showChateeProfile()); + + let users=LocalStorageService.getUsers(); + let groups=LocalStorageService.getGroups(); + let currentUser=SessionManager.getUser(); + users=users.filter(user=>user.id!==currentUser.id); + groups=groups.filter(group=>group.participants.includes(currentUser.username)); + + + window.addEventListener('storage',function(event){ + if(event.key==='chats' || event.key==='groups'){ + if(currentChatId!==null){ + openChat(currentChatUser1,currentChatUser2,currentChatType); + } + } + populateUsersList([...users,...groups]); + }); + + + populateUsersList([...users,...groups]); +} + +main(); \ No newline at end of file diff --git a/js/models/chat.js b/js/models/chat.js new file mode 100644 index 0000000..f8a20f2 --- /dev/null +++ b/js/models/chat.js @@ -0,0 +1,19 @@ +class Chat{ + constructor(chatId){ + this.id=chatId; + this.user1Typing=false; + this.user2Typing=false; + this.messages=[]; + } + + static generateChatId(userId1,userId2){ + if(userId1>userId2){ + return userId1+'-'+userId2; + } + else{ + return userId1+'-'+userId2; + } + } +} + +export default Chat; \ No newline at end of file diff --git a/js/models/group.js b/js/models/group.js new file mode 100644 index 0000000..651c9e4 --- /dev/null +++ b/js/models/group.js @@ -0,0 +1,10 @@ +class Group{ + constructor(name,participants){ + this.id=Math.floor(Math.random()*99999); + this.name=name; + this.messages=[]; + this.participants=participants; + } +} + +export default Group; \ No newline at end of file diff --git a/js/models/message.js b/js/models/message.js new file mode 100644 index 0000000..6d0b28e --- /dev/null +++ b/js/models/message.js @@ -0,0 +1,12 @@ +import SessionManager from "../services/session-manager.js"; +class Message{ + constructor(content,replyTo){ + this.id=Math.floor(Math.random()*99999); + this.content=content; + this.senderId=SessionManager.getUser().id; + this.replyTo=replyTo; + this.timestamp=Date.now(); + } +} + +export default Message; diff --git a/js/models/user.js b/js/models/user.js new file mode 100644 index 0000000..4d79e3a --- /dev/null +++ b/js/models/user.js @@ -0,0 +1,24 @@ +import LocalStorageService from "../services/local-storage.js"; + +class User{ + constructor(username,password){ + + this.id=User.generateUserId(); + this.username=username; + this.password=password; + this.isLoggedIn=false; + this.isOnline=false; + this.lastSeen=Date.now(); + } + + static isUserNameUnique(username){ + let users=LocalStorageService.getUsers(); + return !(users.find(user=>user.username===username)); + } + + static generateUserId(){ + return LocalStorageService.getUsers().length+1; + } +} + +export default User; \ No newline at end of file diff --git a/js/services/local-storage.js b/js/services/local-storage.js new file mode 100644 index 0000000..77ef712 --- /dev/null +++ b/js/services/local-storage.js @@ -0,0 +1,106 @@ +import Message from "../models/message.js"; +import User from "../models/user.js"; +import Chat from "../models/chat.js"; +import SessionManager from "./session-manager.js"; +import Group from "../models/group.js"; +class LocalStorageService{ + constructor(){ + + } + + static createUser(username,password){ + let users=JSON.parse(localStorage.getItem('users'))||[]; + const user=new User(username,password); + users.push(user); + localStorage.setItem('users',JSON.stringify(users)); + } + + static createChat(userId1,userId2){ + let chats=JSON.parse(localStorage.getItem('chats')); + let chat=new Chat(userId1,userId2); + chats.push(chat); + localStorage.setItem('chats',JSON.stringify(chats)); + } + + static sendMessage(chatId,content,replyTo){ + let message=new Message(content,replyTo); + let chats=LocalStorageService.getChats(); + let currentChat=chats.find(chat=>chat.id===chatId); + if(!currentChat){ + console.log('Creating chat'); + currentChat=new Chat(chatId); + chats.push(currentChat); + } + + currentChat.messages.push(message); + + for(let i=0;iuser.id===userId); + } + + + static updateProfile(username,password){ + let users=LocalStorageService.getUsers(); + let user=SessionManager.getUser(); + + if(user){ + for(let i=0;i(user.username===username && user.password===password)); + if(user){ + for(let i=0;i + + + Main | LocalChat + + + +
+ +
+ + + +
+
+ chatee profile picture +

+

+
+ +
+
    +

    No messages yet...

    +
    + +
    + + send-icon + +
    + +
    + + + + + + + + \ No newline at end of file diff --git a/styles/landing-page.css b/styles/landing-page.css new file mode 100644 index 0000000..bfe13d7 --- /dev/null +++ b/styles/landing-page.css @@ -0,0 +1,112 @@ +*{ + margin: 0; + padding: 0; + font-family: Arial, Helvetica, sans-serif; +} + +main{ + display: flex; + height: 100dvh; +} + +article{ + flex: 2; + display: flex; + flex-direction: column; + justify-content: center; + background-color: #ff383c; + color: white; + border-top-right-radius: 20px; + border-bottom-right-radius: 20px; + padding-top: 3%; + padding-left:2%; +} + +aside{ + flex: 1; + display: flex; + justify-content: center; + margin-top: 15%; + padding: 8px; + height: fit-content; +} + +#logo-image{ + width: 30%; + margin-bottom: 50px; +} + + +#welcome-heading{ + font-size: 3rem; + margin-bottom: 30px; +} + +#welcome-message{ + font-size: 1rem; +} + +#text-logo{ + font-size: 35px; + margin-bottom: 100px; +} + +#text-logo span{ + color:#ff383c +} + +input{ + display: block; + margin-bottom: 40px; + width: 100%; + height: 30px; + border: #d9d9d9 solid 1px; + border-radius: 6px; + background-color: #ffffff; + padding-left: 10px; +} + +button{ + display: block; + width: 100%; + background-color: #2c2c2c; + border: 6px solid #2c2c2c; + color: white; + margin-bottom: 60px; + height:40px; + border-radius: 6px; +} + + +#toggle-question{ + display: inline; + font-weight: bold; +} + +#toggle-action{ + display: inline; + color:#ff383c; + font-weight: bold; +} + +#landing-form{ + width: 55%; + text-align: center; +} + + + +@media (max-width:600px) { + article{ + display: none; + } + aside{ + flex: 1; + display: flex; + justify-content: center; + margin-top: 45%; + padding: 8px; + height: fit-content; +} + +} \ No newline at end of file diff --git a/styles/main-page.css b/styles/main-page.css new file mode 100644 index 0000000..e23ecf3 --- /dev/null +++ b/styles/main-page.css @@ -0,0 +1,374 @@ +*{ + margin: 0; + padding: 0; + font-family: Arial, Helvetica, sans-serif; +} + +html{ + height: 100%; +} + +body{ + display: flex; + flex-direction: row; + height: 100vh; + overflow-x: hidden; +} + +nav{ + flex:0.6; + background-color: #f5f5f5; + height: 100vh; + overflow-y: auto; +} + +main{ + flex: 1; + height: 100vh; +} + + +aside{ + flex:1; + height: 100vh; + overflow-y: hidden; + overflow-x: hidden; +} + +#text-logo{ + font-size: 25px; + display: inline; +} + +#text-logo span{ + color:#ff383c; +} + +img{ + display: inline; +} + +#nav-appbar{ + display: flex; + justify-content: space-between; + align-items: center; + position: sticky; + background-color: white; + padding: 10px; + top: 0; + z-index: 2; + height: 50px; +} + +.icon{ + height: 15px; + width: 15px; + color: grey; + margin-right: 5px; +} + +#search-container{ + display: flex; + justify-content: space-between; + margin-top: 10px; + background-color: white; + margin-left: 10px; + margin-right: 10px; + border-radius: 6px; + +} + +#search-container img{ + justify-content: flex-end; + padding: 10px; + +} + +input{ + border: none; + height: max-content; + padding: 10px; + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; + width: 100%; + overflow: hidden; +} + + + +#filter-tags{ + margin-left: 10px; +} + +#filter-tags li{ + display: inline-flex; + align-items: center; + background-color: #dedede; + border-radius: 6px; + height: 15px; + color: #757575; + text-align: center; + padding: 8px; + margin-top: 10px; + margin-bottom: 10px; +} + +#filter-tag-all{ + background-color: #2c2c2c; + color: rgb(209, 209, 209); +} + + + +#users-list li{ + background-color: #ffffff; + margin: 5px 10px; + display: flex; + flex-direction: column; + height: 65px; + border-radius: 6px; + position: relative; +} + +#users-list li p{ + color: #636262; + display: block; + margin-left: 65px; + position: absolute; + margin-top: 36px; + font-size: 15px; +} + + +#users-list li h3{ + color: #636262; + display: block; + margin-left: 65px; + position: absolute; + font-size: 18px; + margin-top: 12px; +} + +#users-list li h4{ + color: green; + display: block; + margin-left: 40px; + position: absolute; + margin-top: 30px; + font-size: 40px; + height: 2px; +} + + +main{ + display: flex; + flex-direction: column; + background-color: #f5f5f5; +} + +#users-list li img{ + width: 50px; + height: 50px; + margin-left: 5px; + margin-top: 8px; +} + + +#chatee-info{ + display: flex; + justify-content: start; + flex-direction: column; + margin-left: 0px; + background-color: white; + padding-left: 10px; + height: 78px; + +} + +#messages-box{ + margin-top: 30px; + justify-content: center; + text-align: center; + overflow-y: scroll; +} + +#message-container{ + margin-top: auto; + justify-content:space-between ; + background-color: white; + display: flex; + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; + border-radius: 6px; + align-items: center; +} + +#message-send{ + height: 30px; + width: 30px; + margin: 10px; +} + +#no-messages{ + font-weight: bold; + color: #cdcdcd; +} + +#chatee-profile-picture{ + height: 55px; + width: 55px; +} + +#chatee-name{ + color:#636262; + margin-top: 5px; + margin-left: 8px; + position: absolute; + margin-top: 7px; + margin-left: 70px; + font-size: 20px; +} + +#chatee-status{ + position: absolute; + margin-top: 33px; + margin-left: 70px; + font-size: 13px; +} + + +#message-tile{ + background-color: rgb(244, 107, 107); + text-align: left; + border-radius: 8px; + margin-left: 10px; + margin-right: 20%; + padding-left: 10px; + padding-top: 10px; + list-style-type: none; + margin-bottom: 15px; +} + +#message-tile-text{ + color: white; +} + +#message-date{ + color:#eeeeee; + margin-top: 10px; + text-align: end; + font-size: 13px; + margin-right: 10px; + padding-bottom: 10px; +} + + +#profile-appbar{ + display: flex; + align-items: center; + height: 60px; +} + +#chatee{ + display: flex; + display: none; +} + +#chatee-details{ + justify-content: center; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-top: 20%; +} + +#chatee-side-status{ + margin-top: 20px; +} + +#chatee-side-name{ + margin-top: 30px; + font-size: 30px; + color: #636262; +} + +#chatee-side-profile-picture{ + height: 80px; + width: 80px; +} + +#current-user{ + height: 100%; +} + +#current-user-details{ + display: flex; + flex-direction: column; + margin-left: 20px; + margin-right: 20px; + height: 100%; +} + +#logout{ + display: inline; + width: 100%; + background-color: #ff383c; + border: 1px solid #cbcbcb; + color: white; + height:40px; + border-radius: 6px; + margin-right: 60px; +} + +#save-edits{ + display: inline; + width: 45%; + background-color: #2c2c2c; + border: 6px solid #2c2c2c; + color: white; + height:40px; + border-radius: 6px; + margin-right: 4%; + +} + +#cancel-edits{ + display: inline; + width: 45%; + background-color: #ffffff; + border: 1px solid #d3d3d3; + color: black; + margin-bottom: 10px; + height:40px; + border-radius: 6px; + margin-left: 4%; +} + +#user-profile-picture{ + height: 50px; + width: 50px; + margin-bottom: 20px; +} + +#username-input{ + margin-bottom: 20px; + border:1px solid #d3d3d3; + width: 80%; + border-radius: 6px; +} + +#password-input{ + margin-bottom: 20px; + border:1px solid #d3d3d3; + width: 80%; + border-radius: 6px; +} + + +#profile-actions{ + margin-top: auto; + justify-content: flex-end; + margin-bottom: 100px; +} \ No newline at end of file