19 Commits

Author SHA1 Message Date
Nikolai Petukhov
876ef28221 0.2.4 2024-09-28 10:37:42 +03:00
Nikolai Petukhov
e6231f86b4 account page is done 2024-09-28 10:34:06 +03:00
Nikolai Petukhov
9d13f571d9 0.2.3 2024-09-28 10:00:38 +03:00
Nikolai Petukhov
f654851e1a fix dependencies 2024-09-28 09:56:43 +03:00
Askar Akhmetkhanov
3fb107fd8b Commit 2024-09-28 00:12:00 +03:00
Nikolai Petukhov
2a881f3920 small fix 2024-09-21 17:25:03 +03:00
Nikolai Petukhov
762c4867b7 0.2.2 2024-09-21 17:05:58 +03:00
Nikolai Petukhov
2a56274ca9 login pages are done 2024-09-21 17:03:09 +03:00
Nikolai Petukhov
93ed4ce7fc home page is done 2024-09-21 15:53:37 +03:00
Nikolai Petukhov
2ce5bb8d7a upd init page structure 2024-09-21 13:53:46 +03:00
Nikolai Petukhov
e75288a6f0 init page is done 2024-09-21 13:32:52 +03:00
Nikolai Petukhov
963c94c262 last preparation 2024-09-21 12:42:07 +03:00
Nikolai Petukhov
d7de0c4353 fix 2024-09-20 16:10:14 +03:00
Nikolai Petukhov
a482be764b ijl to brojs 2024-09-20 15:05:14 +03:00
Nikolai Petukhov
6fc51fd45c try to fix 2024-09-20 12:18:35 +03:00
Nikolai Petukhov
59f5335e52 upd import of css 2024-09-20 12:13:47 +03:00
Nikolai Petukhov
8c5af38e3c upd imports in css 2024-09-20 11:58:13 +03:00
Nikolai Petukhov
811e9448b0 0.2.1 2024-09-20 11:01:45 +03:00
Nikolai Petukhov
5ef5ca35b2 init structure is completed 2024-09-20 11:01:23 +03:00
53 changed files with 3688 additions and 83 deletions

27
bro.config.js Normal file
View File

@@ -0,0 +1,27 @@
const pkg = require("./package");
module.exports = {
apiPath: "stubs/api",
webpackConfig: {
output: {
publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
},
},
/* use https://admin.bro-js.ru/ to create config, navigations and features */
navigations: {
'enterfront.main': '/enterfront',
'enterfront.home': '/enterfront/home',
'enterfront.auth': '/enterfront/auth',
'enterfront.reg': '/enterfront/reg',
'enterfront.account': '/enterfront/account',
'enterfront.chat': '/enterfront/chat',
},
features: {
"enterfront": {
// add your features here in the format [featureName]: { value: string }
},
},
config: {
"enterfront.api": "/api",
},
};

View File

@@ -1,23 +0,0 @@
const pkg = require('./package')
module.exports = {
apiPath: 'stubs/api',
webpackConfig: {
output: {
publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`
}
},
navigations: {
'enterfront.main': '/enterfront',
'link.enterfront.auth': '/enterfront/auth',
'link.enterfront.account': '/enterfront/account',
},
features: {
'undefined': {
// add your features here in the format [featureName]: { value: string }
},
},
config: {
'undefined.api': '/api',
}
}

18
images/chat.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 58 58" xml:space="preserve">
<g>
<path style="fill:#FFA500FF;" d="M25,9.586C11.193,9.586,0,19.621,0,32c0,4.562,1.524,8.803,4.135,12.343
C3.792,48.433,2.805,54.194,0,57c0,0,8.47-1.191,14.273-4.651c0.006-0.004,0.009-0.01,0.014-0.013
c1.794-1.106,3.809-2.397,4.302-2.783c0.301-0.417,0.879-0.543,1.328-0.271c0.298,0.181,0.487,0.512,0.488,0.86
c0.003,0.582-0.008,0.744-3.651,3.018c2.582,0.81,5.355,1.254,8.245,1.254c13.807,0,25-10.035,25-22.414S38.807,9.586,25,9.586z"/>
<path style="fill:#EF9400FF;" d="M58,23.414C58,11.035,46.807,1,33,1c-9.97,0-18.575,5.234-22.589,12.804
C14.518,11.153,19.553,9.586,25,9.586c13.807,0,25,10.035,25,22.414c0,4.735-1.642,9.124-4.437,12.743
C51.162,47.448,58,48.414,58,48.414c-2.805-2.805-3.792-8.566-4.135-12.657C56.476,32.217,58,27.976,58,23.414z"/>
<path style="fill:#FFFFFF;" d="M32.5,26h-14c-0.552,0-1-0.447-1-1s0.448-1,1-1h14c0.552,0,1,0.447,1,1S33.052,26,32.5,26z"/>
<path style="fill:#FFFFFF;" d="M38,33H13c-0.552,0-1-0.447-1-1s0.448-1,1-1h25c0.552,0,1,0.447,1,1S38.552,33,38,33z"/>
<path style="fill:#FFFFFF;" d="M38,40H13c-0.552,0-1-0.447-1-1s0.448-1,1-1h25c0.552,0,1,0.447,1,1S38.552,40,38,40z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

18
images/chat2.svg Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 58 58" xml:space="preserve">
<g>
<path style="fill:#0391FD;" d="M25,9.586C11.193,9.586,0,19.621,0,32c0,4.562,1.524,8.803,4.135,12.343
C3.792,48.433,2.805,54.194,0,57c0,0,8.47-1.191,14.273-4.651c0.006-0.004,0.009-0.01,0.014-0.013
c1.794-1.106,3.809-2.397,4.302-2.783c0.301-0.417,0.879-0.543,1.328-0.271c0.298,0.181,0.487,0.512,0.488,0.86
c0.003,0.582-0.008,0.744-3.651,3.018c2.582,0.81,5.355,1.254,8.245,1.254c13.807,0,25-10.035,25-22.414S38.807,9.586,25,9.586z"/>
<path style="fill:#0F71D3;" d="M58,23.414C58,11.035,46.807,1,33,1c-9.97,0-18.575,5.234-22.589,12.804
C14.518,11.153,19.553,9.586,25,9.586c13.807,0,25,10.035,25,22.414c0,4.735-1.642,9.124-4.437,12.743
C51.162,47.448,58,48.414,58,48.414c-2.805-2.805-3.792-8.566-4.135-12.657C56.476,32.217,58,27.976,58,23.414z"/>
<path style="fill:#FFFFFF;" d="M32.5,26h-14c-0.552,0-1-0.447-1-1s0.448-1,1-1h14c0.552,0,1,0.447,1,1S33.052,26,32.5,26z"/>
<path style="fill:#FFFFFF;" d="M38,33H13c-0.552,0-1-0.447-1-1s0.448-1,1-1h25c0.552,0,1,0.447,1,1S38.552,33,38,33z"/>
<path style="fill:#FFFFFF;" d="M38,40H13c-0.552,0-1-0.447-1-1s0.448-1,1-1h25c0.552,0,1,0.447,1,1S38.552,40,38,40z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/home_wp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

9
images/user.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

2280
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,28 @@
{
"dependencies": {
"@brojs/cli": "^1.0.0",
"@brojs/create": "^1.0.0",
"@ijl/cli": "^5.1.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"emoji-mart": "^5.6.0",
"express": "^4.19.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-emoji-picker": "^1.0.13",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.1",
"typescript": "^5.5.4"
"styled-components": "^6.1.13",
"typescript": "^5.5.4",
"ws": "^8.18.0"
},
"main": "./src/index.tsx",
"scripts": {
"start": "ijl-cli server --port=8099 --with-open-browser",
"build": "npm run clean && ijl-cli build --dev",
"build:prod": "npm run clean && ijl-cli build",
"start": "brojs server --port=8099 --with-open-browser",
"build": "npm run clean && brojs build --dev",
"build:prod": "npm run clean && brojs build",
"clean": "rimraf dist"
},
"name": "enterfront",
"version": "0.2.0"
"version": "0.2.4"
}

View File

@@ -1,16 +1,24 @@
import { getNavigations, getNavigationsValue } from '@ijl/cli';
import { getNavigations, getNavigationsValue } from '@brojs/cli';
import pkg from '../../package.json';
const baseUrl = getNavigationsValue(`${(pkg as any).name}.main`);
const navs = getNavigations();
const makeUrl = url => url;
export const URLs = {
baseUrl,
baseUrl, // init
home: {
url: navs[`${(pkg as any).name}.home`],
},
chat: {
url: navs[`${(pkg as any).name}.chat`],
},
auth: {
url: makeUrl(navs[`link.${(pkg as any).name}.auth`]),
url: navs[`${(pkg as any).name}.auth`], // sign in
},
reg: {
url: navs[`${(pkg as any).name}.reg`], // sign up
},
account: {
url: makeUrl(navs[`link.${(pkg as any).name}.account`]),
}
url: navs[`${(pkg as any).name}.account`],
},
};

View File

@@ -3,13 +3,14 @@ import { BrowserRouter } from 'react-router-dom';
import { Dashboard } from './dashboard';
import './index.css'
const App = () => {
return(
<BrowserRouter>
<Dashboard />
</BrowserRouter>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
)
}
export default App;

View File

@@ -0,0 +1,43 @@
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Map();
wss.on("connection", (ws, req) => {
console.log("New client connected");
ws.on("message", (message) => {
try {
const parsedMessage = JSON.parse(message);
if (parsedMessage.type === "register") {
clients.set(parsedMessage.userId, ws);
console.log(`User registered: ${parsedMessage.userId}`);
} else if (parsedMessage.type === "message") {
const recipientWs = clients.get(parsedMessage.recipientId);
if (recipientWs) {
recipientWs.send(
JSON.stringify({
senderId: parsedMessage.senderId,
message: parsedMessage.message,
timestamp: new Date().toISOString(),
})
);
} else {
console.error(`User ${parsedMessage.recipientId} is not connected.`);
}
}
} catch (err) {
console.error("Error processing message:", err.message);
}
});
ws.on("close", () => {
console.log("Client disconnected");
[...clients.entries()].forEach(([userId, clientWs]) => {
if (clientWs === ws) {
clients.delete(userId);
console.log(`User disconnected: ${userId}`);
}
});
});
});

View File

@@ -0,0 +1,8 @@
export default class Interlocutor {
constructor(id, name) {
this.name = name;
this.id = id;
}
static name;
static id;
}

7
src/backend/user/User.js Normal file
View File

@@ -0,0 +1,7 @@
export default class User {
constructor(id, name) {
this.id = id;
this.name = name;
this.status = "online";
}
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { URLs } from "../../__data__/urls";
import ActionButton from "./ActionButton.jsx";
const AccountButtons = (props) => {
return (
<div className="account-buttons">
<ActionButton title={"Exit"} action={props.exitHandler}/>
<ActionButton title={"Change Name"} action={props.changeNameHandler}/>
<ActionButton title={"Change Pass"} action={props.changePassHandler}/>
<a className="MyButton mclaren-regular" href={URLs.home.url}>Back</a>
</div>
);
};
export default AccountButtons;

View File

@@ -0,0 +1,11 @@
import React from 'react';
const ActionButton = (props) => {
return (
<a className="MyButton mclaren-regular" onClick={() => {
props.action()
}}>{props.title}</a>
);
};
export default ActionButton;

View File

@@ -0,0 +1,29 @@
.account-buttons {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.account-buttons a {
display: flex;
font-size: 1.5vw;
transition: color 0.2s ease-in;
width: 20vw;
margin-top: 2vw;
}
.account-buttons a:hover {
color: black;
}
@media only screen and (max-width: 800px) {
.account-buttons a {
font-size: 2.5vh;
width: 60vw;
margin-top: 3vh;
}
}

View File

@@ -0,0 +1,81 @@
import React from 'react';
import { URLs } from "../../__data__/urls";
import styled from "styled-components";
const Card = (props) => {
const interlocutorId = props.id
const hexToRgb = (hex) => {
hex = hex.replace(/^#/, '');
let r = parseInt(hex.slice(0, 2), 16);
let g = parseInt(hex.slice(2, 4), 16);
let b = parseInt(hex.slice(4, 6), 16);
return `${r}, ${g}, ${b}`;
}
const handleClick = (event, id) => {
localStorage.setItem('interlocutorId', id);
};
const StyledCard = styled.div`
box-shadow: 0 10px 40px rgba(176, 175, 189, 0.85);
padding: 2vw;
display: flex;
flex-direction: column;
border-radius: 2vw;
align-items: flex-start;
justify-content: space-between;
background-color: ${props => props.color
? `rgba(${hexToRgb(props.color)}, 0.63)`
: 'rgba(255, 255, 255, 0.63)'};
transition: box-shadow 0.3s ease-in;
div, h2 {
transition: color 0.3s ease-in;
}
h2 {
margin-top: 0;
}
&:hover {
box-shadow: 0 10px 40px rgb(${hexToRgb(props.color)});
cursor: pointer;
div, h2 {
color: white;
}
}
@media only screen and (max-width: 800px) {
flex-direction: column;
border-radius: 2vh;
padding: 5vw;
}
`
return (
<a
href={URLs.chat.url}
className="ChatCard"
onClick={(event) => handleClick(event, interlocutorId)}
>
<StyledCard color={props.color}>
<h2>{props.name}</h2>
<div className="lastMessageStyle">{props.lastMessage}</div>
</StyledCard>
</a>
);
};
export default Card;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import Card from "./Card.jsx";
const ChatsList = (props) => {
const { chats } = props;
const colorMap = {
orange: 'FFA500FF',
aqua: '00FFFFFF',
crimson: 'DC143CFF',
red: 'FF0000FF',
violet: '8A2BE2FF',
seagreen: '20B2AAFF',
green: 'ADFF2FFF',
blue: '0000FFFF',
pink: 'FF1493FF',
cyan: '72FAFAFF'
}
function getColor(chatId) {
const keys = Object.keys(colorMap);
const index = chatId % keys.length;
return colorMap[keys[index]];
}
return (
<div className="ChatsList">
{chats.map((item, index) => (
<Card
key={index}
name={item.name}
lastMessage={item.lastMessage}
id={item.id}
color={getColor(item.id)}
/>
))}
</div>
);
};
export default ChatsList;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { URLs } from "../../__data__/urls";
import { FaHome } from 'react-icons/fa';
const Header = () => {
return (
<a href={URLs.account.url} className="accountLink">
<FaHome className="houseIcon"/>
<p>My profile</p>
</a>
);
};
export default Header;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import chatIcon from './../../../images/chat.svg';
import { URLs } from "../../__data__/urls";
const HomeTitle = () => {
return (
<a className="homeTitleWrapper" href={URLs.baseUrl}>
<h1 className="montecarlo-regular homeTitle">Enterfront</h1>
<img src={chatIcon} alt="Chat Icon" className="chatIcon"/>
</a>
);
};
export default HomeTitle;

View File

@@ -0,0 +1,38 @@
.houseIcon {
transition: color 0.3s ease-in;
}
.accountLink {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.accountLink p {
font-size: 1.5vw;
transition: color 0.3s ease-out;
}
.accountLink svg {
font-size: 3vw;
transition: fill 0.3s ease-in;
}
.accountLink:hover p {
color: white;
}
.accountLink:hover svg {
fill: orange;
}
@media only screen and (max-width: 800px) {
.accountLink p {
font-size: 1.8vh;
}
.accountLink svg {
font-size: 4vh;
}
}

View File

@@ -0,0 +1,48 @@
.ChatsList {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 3rem;
}
.ChatCard {
margin-bottom: 40px;
max-width: 30vw;
min-width: 20vw;
margin-left: 1.2vw;
margin-right: 1.2vw;
border-radius: 2vw;
transition: transform 0.2s ease-in;
}
.lastMessageStyle {
width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media only screen and (max-width: 800px) {
.ChatCard {
width: 30vw;
max-width: 30vw;
margin-left: 4vw;
margin-right: 4vw;
padding-top: 0;
}
.ChatsList {
margin: 10vw;
}
}
.ChatCard:hover {
transform: translate(2px, -10px);
}

View File

@@ -0,0 +1,25 @@
.homeTitleWrapper {
display: flex;
flex-direction: row;
}
.homeTitle {
font-size: 5vw;
transition: color 0.3s ease-in;
}
.homeTitleWrapper:hover h1 {
color: orange;
}
@media only screen and (max-width: 800px) {
.homeTitle {
font-size: 8vh;
}
}
.chatIcon {
width: 6vw;
margin-left: 3vw;
}

View File

@@ -0,0 +1,3 @@
@import url("./css/card.css");
@import url("./css/homeTitle.css");
@import url("./css/accountLink.css");

28
src/components/index.css Normal file
View File

@@ -0,0 +1,28 @@
@import url("reg/index.css");
@import url("init/index.css");
@import url("home/index.css");
@import url("account/index.css");
.MyButton {
text-decoration: none;
border: 2px solid black;
border-radius: 10px;
background-color: black;
color: white;
padding: 15px;
justify-content: center;
width: 9rem;
transition: background-color 0.2s ease-in, border 0.2s ease-in;
}
.MyButton:hover {
background-color: orange;
border: 2px solid orange;
cursor: pointer;
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
const InitTitle = () => {
return (
<h1 className="metal-mania-regular initTitle">Enterfront</h1>
);
};
export default InitTitle;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const NavButton = (props) => {
return (
<a className="MyButton singleNavButton" href={props.nav}>
<p className="mclaren-regular">{props.text}</p>
</a>
);
};
export default NavButton;

View File

@@ -0,0 +1,13 @@
.initTitle {
font-size: 7vw;
margin: 0;
color: white;
text-align: center;
}
@media only screen and (max-width: 800px) {
.initTitle {
font-size: 8vh;
}
}

View File

@@ -0,0 +1,32 @@
.MyButton.singleNavButton {
display: flex;
align-items: center;
margin-top: 1.5vw;
padding: 0;
background-color: white;
border-color: white;
border-radius: 3vw;
min-width: 20vw;
}
.MyButton.singleNavButton p {
font-size: 1.4vw;
font-weight: bold;
}
@media only screen and (max-width: 800px) {
.MyButton.singleNavButton p {
font-size: 2.5vh;
}
.MyButton.singleNavButton {
min-width: 60vw;
border-radius: 30vh;
margin-top: 3vh;
}
}

View File

@@ -0,0 +1,2 @@
@import url("./css/navButton.css");
@import url("./css/initTitle.css");

View File

@@ -0,0 +1,16 @@
import React from 'react';
const InputField = (props) => {
return (
<div>
<p>{props.title}</p>
<input
onChange={(e) => props.setValue(e.target.value)}
value={props.value}
className="Input"
/>
</div>
);
};
export default InputField;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { URLs } from "../../__data__/urls";
const LoginButtons = (props) => {
const ref = props.isAuth ? URLs.reg.url : URLs.auth.url
return (
<div>
<div className="LoginButtons">
<a className="MyButton loginButton" onClick={() => (
props.submitHandler()
)}>
{props.isAuth
? <p className="mclaren-regular">Login</p>
: <p className="mclaren-regular">Register</p>}
</a>
<a className="MyButton loginButton" href={ref}>
{props.isAuth
? <p className="mclaren-regular">Registration</p>
: <p className="mclaren-regular">I have an account</p>}
</a>
</div>
</div>
);
};
export default LoginButtons;

View File

@@ -0,0 +1,58 @@
.LoginButtons {
margin-top: 5rex;
display: flex;
flex-direction: column;
align-items: center;
}
.LoginButtons a {
margin-bottom: 15px;
width: 12rem;
display: flex;
}
.Input {
border: 0;
background-color: black;
color: orange;
border-radius: 5px;
padding: 10px;
width: 18rem;
}
.MyButton.loginButton {
padding: 0;
}
.MyButton.loginButton p {
color: orange;
transition: color 0.3s ease-in;
font-size: 1.3vw;
font-weight: 500;
}
.loginTitle {
font-size: 4vw;
transition: color 0.3s ease-in;
}
.loginTitle:hover {
color: orange;
}
@media only screen and (max-width: 800px) {
.MyButton.loginButton p {
font-size: 1.5vh;
}
}
.loginButton:hover p {
color: black;
}

View File

@@ -0,0 +1 @@
@import url("./css/login.css");

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { URLs } from "../../__data__/urls";
const LoginTitle = () => {
return (
<a href={URLs.baseUrl}>
<h1 className="loginTitle mclaren-regular">Enterfront</h1>
</a>
);
};
export default LoginTitle;

View File

@@ -1,18 +1,25 @@
import React from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import React from "react";
import { Routes, Route } from "react-router-dom";
import { URLs } from './__data__/urls';
import { URLs } from "./__data__/urls";
import HomePage from './pages/HomePage.jsx'
import Hello from './pages/Hello.jsx'
import Account from './pages/Account.jsx'
import Home from "./pages/Home.jsx";
import Init from "./pages/Init.jsx";
import Account from "./pages/Account.jsx";
import Chat from "./pages/Chat.jsx";
import SignIn from "./pages/SignIn.jsx";
import SignUp from "./pages/SignUp.jsx";
export const Dashboard = () => {
return (
<Routes>
<Route path={URLs.baseUrl} element={<HomePage/>}/>
<Route path={URLs.auth.url} element={<Hello/>} />
<Route path={URLs.account.url} element={<Account/>}/>
<Route path={URLs.baseUrl} element={<Init />} />
<Route path={URLs.home.url} element={<Home />} />
<Route path={URLs.chat.url} element={<Chat />} />
<Route path={URLs.auth.url} element={<SignIn />} />
<Route path={URLs.reg.url} element={<SignUp />} />
<Route path={URLs.account.url} element={<Account />} />
<Route path="*" element={<h1>404 page not found</h1>} />
</Routes>
);
};

60
src/index.css Normal file
View File

@@ -0,0 +1,60 @@
@import url('https://fonts.googleapis.com/css2?family=MonteCarlo&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Metal+Mania&display=swap');
@import url('https://fonts.googleapis.com/css2?family=McLaren&display=swap');
@import url('https://fonts.googleapis.com/css2?family=MonteCarlo&display=swap');
@import url("pages/index.css");
@import url("components/index.css");
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
* {
font-size: 1.2vw;
font-family: "Montserrat", 'sans-serif';
color: black;
text-decoration: none;
}
/* Mobile devices */
@media only screen and (max-width: 800px) {
* {
font-size: 1.5vh;
}
}
html {
scroll-behavior: smooth;
}
.metal-mania-regular {
font-family: "Metal Mania", system-ui;
font-weight: 400;
font-style: normal;
}
.mclaren-regular {
font-family: "McLaren", system-ui;
font-weight: 400;
font-style: normal;
}
.montecarlo-regular {
font-family: "MonteCarlo", cursive;
font-weight: 400;
font-style: normal;
}

View File

@@ -1,9 +1,22 @@
import React from "react";
import AccountButtons from "../components/account/AccountButtons.jsx";
import userIcon from "../../images/user.svg";
const Account = () => {
return (
<h1>Account</h1>
)
}
const exitHandler = () => {}
const changeNameHandler = () => {}
const changePassHandler = () => {}
export default Account;
return (
<div className="account-items">
<img src={userIcon} alt="user" />
<AccountButtons
exitHandler={exitHandler}
changeNameHandler={changeNameHandler}
changePassHandler={changePassHandler}
/>
</div>
);
};
export default Account;

213
src/pages/Chat.jsx Normal file
View File

@@ -0,0 +1,213 @@
import React, { useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import "./css/Chat.css";
import { FaPaperPlane, FaSmile } from "react-icons/fa";
const emojis = [
"😀",
"😁",
"😂",
"🤣",
"😃",
"😄",
"😅",
"😆",
"😉",
"😊",
"😋",
"😎",
"😍",
"😘",
"🥰",
"😗",
"😙",
"😚",
"🙂",
"🤗",
"🤩",
"🤔",
"😐",
"😑",
"😶",
"🙄",
"😏",
"😣",
"😥",
"😮",
"🤐",
"😯",
"😪",
"😫",
"😴",
"😌",
"😛",
"😜",
"🤪",
"🤨",
"😝",
"🤑",
"😒",
"😓",
"😔",
"😕",
"😖",
"😞",
"😟",
"😠",
"😡",
"🤬",
"😱",
"😨",
"😧",
"😇",
"🥳",
"🥺",
"😻",
"😼",
"😽",
"🙈",
"🙉",
"🙊",
"💀",
"👻",
"👽",
];
const Chat = () => {
const [interlocutorId, setInterlocutorId] = useState(0);
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const socket = useRef(null);
const chatRef = useRef(null);
const navigate = useNavigate();
useEffect(() => {
const id = parseInt(localStorage.getItem("interlocutorId"), 10) || 0;
setInterlocutorId(id);
socket.current = new WebSocket("ws://localhost:8080");
socket.current.onopen = () => {
console.log("WebSocket connected");
socket.current.send(
JSON.stringify({ type: "register", userId: "yourUserId" })
);
};
socket.current.onmessage = (event) => {
const receivedData = JSON.parse(event.data);
setMessages((prev) => [...prev, receivedData]);
};
socket.current.onerror = (event) => {
console.error("WebSocket error observed:", event);
};
socket.current.onclose = () => {
console.log("WebSocket closed");
};
return () => {
socket.current.close();
};
}, []);
useEffect(() => {
if (chatRef.current) {
chatRef.current.scrollTop = chatRef.current.scrollHeight;
}
}, [messages]);
const sendMessage = () => {
if (newMessage.trim()) {
const messageData = {
type: "message",
senderId: "yourUserId",
recipientId: interlocutorId,
message: newMessage,
timestamp: new Date().toLocaleTimeString(),
};
socket.current.send(JSON.stringify(messageData));
setMessages((prev) => [...prev, messageData]);
setNewMessage("");
}
};
const handleKeyPress = (e) => {
if (e.key === "Enter") {
sendMessage();
}
};
const handleEmojiSelect = (emoji) => {
setNewMessage((prev) => prev + emoji);
setShowEmojiPicker(false);
};
return (
<div className="chat-container">
<div className="chat-header">
<h2>Chat with ... (id = {interlocutorId})</h2>
<button
onClick={() => navigate("/enterfront/home")}
className="home-button"
>
Home
</button>{" "}
{}
</div>
<div className="chat-messages" ref={chatRef}>
{messages.map((msg, index) => (
<div
key={index}
className={`message-bubble ${
msg.senderId === "yourUserId" ? "sent" : "received"
}`}
>
<div className="message-content">
<b>{msg.senderId === "yourUserId" ? "You" : "Interlocutor"}:</b>{" "}
{msg.message}
</div>
<span className="message-timestamp">{msg.timestamp}</span>
</div>
))}
</div>
<div className="chat-input-container">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type a message..."
className="chat-input"
onKeyPress={handleKeyPress}
/>
<button
className="emoji-button"
onClick={() => setShowEmojiPicker((prev) => !prev)}
>
<FaSmile />
</button>
<button onClick={sendMessage} className="send-button">
<FaPaperPlane />
</button>
{showEmojiPicker && (
<div className="emoji-picker">
{emojis.map((emoji, index) => (
<span
key={index}
className="emoji"
onClick={() => handleEmojiSelect(emoji)}
style={{ cursor: "pointer", padding: "5px" }}
>
{emoji}
</span>
))}
</div>
)}
</div>
</div>
);
};
export default Chat;

View File

@@ -1,9 +0,0 @@
import React from "react";
const Hello = () => {
return (
<h1>Hello!</h1>
)
}
export default Hello;

76
src/pages/Home.jsx Normal file
View File

@@ -0,0 +1,76 @@
import React from "react";
import HomeTitle from "../components/home/HomeTitle.jsx";
import ChatsList from "../components/home/ChatsList.jsx";
import Header from "../components/home/Header.jsx";
const Home = () => {
// temp for testing
const chats = [
{
name: "Alice Johnson",
id: 123456,
lastMessage: "See you later!"
},
{
name: "Bob Smith",
id: 654321,
lastMessage: "Got it, thanks!"
},
{
name: "Charlie Brown",
id: 234567,
lastMessage: "How's the project going? How's the project going? How's the project going?" +
"How's the project going? How's the project going?"
},
{
name: "David Clark",
id: 765432,
lastMessage: "I'll send the files."
},
{
name: "Eve Adams",
id: 345678,
lastMessage: "Let's meet tomorrow."
},
{
name: "Frank Wright",
id: 876543,
lastMessage: "Can you review this?"
},
{
name: "Grace Lee",
id: 456789,
lastMessage: "Thanks for your help!"
},
{
name: "Hannah Scott",
id: 987654,
lastMessage: "See you at the meeting."
},
{
name: "Ian Davis",
id: 567890,
lastMessage: "Let me know when you're free."
},
{
name: "Jill Thompson",
id: 678901,
lastMessage: "I'll catch up with you later."
}
];
return (
<div className="homeWrapper">
<div className="headerPos">
<Header/>
</div>
<HomeTitle/>
<p>Your chats</p>
<ChatsList chats={chats} />
</div>
)
}
export default Home

View File

@@ -1,9 +0,0 @@
import React from "react";
const HomePage = () => {
return (
<h1>Home</h1>
)
}
export default HomePage

22
src/pages/Init.jsx Normal file
View File

@@ -0,0 +1,22 @@
import React from "react";
import NavButton from "../components/init/NavButton.jsx";
import {URLs} from "../__data__/urls";
import InitTitle from "../components/init/InitTitle.jsx";
const Init = () => {
return (
<div className="backgroundImage">
<div className="blurEffect"></div>
<div className="overBlur initTitlePos">
<InitTitle/>
</div>
<div className="navButtons overBlur">
<NavButton nav={URLs.home.url} text={"Start chatting"}/>
<NavButton nav={URLs.auth.url} text={"Sign In"}/>
<NavButton nav={URLs.reg.url} text={"Sign Up"}/>
</div>
</div>
)
}
export default Init;

28
src/pages/SignIn.jsx Normal file
View File

@@ -0,0 +1,28 @@
import React, {useState} from 'react';
import InputField from "../components/reg/InputField.jsx";
import LoginButtons from "../components/reg/LoginButtons.jsx";
import LoginTitle from "../components/reg/loginTitle.jsx";
const SignIn = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const submit = (e) => {
console.log('Sign In!')
}
return (
<div className="LoginList">
<LoginTitle/>
<InputField title="Name" setValue={setName} value={name}/>
<InputField title="Password" setValue={setPassword} value={password}/>
<LoginButtons
submitHandler={submit}
isAuth={true}
/>
</div>
);
};
export default SignIn;

30
src/pages/SignUp.jsx Normal file
View File

@@ -0,0 +1,30 @@
import React, {useState} from 'react';
import InputField from "../components/reg/InputField.jsx";
import LoginButtons from "../components/reg/LoginButtons.jsx";
import LoginTitle from "../components/reg/loginTitle.jsx";
const SignUp = () => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [repeatPassword, setRepeatPassword] = useState("");
const submit = (e) => {
console.log('Sign Up!')
}
return (
<div className="LoginList">
<LoginTitle/>
<InputField title="Name" setValue={setName} value={name}/>
<InputField title="Password" setValue={setPassword} value={password}/>
<InputField title="Repeat Password" setValue={setRepeatPassword} value={repeatPassword}/>
<LoginButtons
submitHandler={submit}
isAuth={false}
/>
</div>
);
};
export default SignUp;

145
src/pages/css/Chat.css Normal file
View File

@@ -0,0 +1,145 @@
.chat-container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 600px;
margin: auto;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background-color: #f9f9f9;
}
.chat-header {
padding: 10px;
background-color: #007bff;
color: white;
text-align: center;
}
.chat-messages {
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 10px;
overflow-y: auto;
background-color: #fff;
}
.message-bubble {
margin: 5px;
padding: 10px 15px;
border-radius: 10px;
max-width: 70%;
word-wrap: break-word;
display: inline-block;
}
.sent {
background-color: #007bff;
color: white;
align-self: flex-end;
text-align: right;
margin-left: auto;
}
.received {
background-color: #f1f0f0;
align-self: flex-start;
text-align: left;
margin-right: auto;
}
.message-content {
font-size: 14px;
margin: 0;
padding: 0;
}
.message-timestamp {
font-size: 10px;
color: #999;
margin-top: 5px;
display: block;
}
.chat-input-container {
display: flex;
padding: 10px;
border-top: 1px solid #ddd;
background-color: #f0f0f0;
}
.chat-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 14px;
outline: none;
}
.send-button,
.emoji-button {
background-color: transparent;
border: none;
font-size: 20px;
margin-left: 10px;
cursor: pointer;
transition: color 0.3s;
}
.send-button:hover,
.emoji-button:hover {
color: #0056b3;
}
.send-button {
color: #007bff;
}
.emoji-button {
color: #f0c040;
}
.chat-input:focus {
border-color: #007bff;
}
.emoji-picker {
position: absolute;
bottom: 60px;
right: 10px;
z-index: 10;
background-color: white;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
display: flex;
flex-wrap: wrap;
max-width: 200px;
}
.emoji {
font-size: 24px;
cursor: pointer;
transition: transform 0.2s;
}
.emoji:hover {
transform: scale(1.2);
}
.home-button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: auto;
}
.home-button:hover {
background-color: #45a049;
}

12
src/pages/css/account.css Normal file
View File

@@ -0,0 +1,12 @@
.account-items {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 5vw;
}
.account-items img {
margin-bottom: 1vw;
}

31
src/pages/css/home.css Normal file
View File

@@ -0,0 +1,31 @@
.homeWrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.homeWrapper p {
font-size: 2.5vw;
font-weight: 500;
margin-top: 0;
}
@media only screen and (max-width: 800px) {
.homeWrapper p {
font-size: 3vh;
}
}
.ChatsList {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 3rem;
}
.headerPos {
position: absolute;
top: 3vw;
right: 3vw;
}

45
src/pages/css/init.css Normal file
View File

@@ -0,0 +1,45 @@
.backgroundImage {
width: 100%;
height: 100vh;
background-color: darkgray;
background-size: cover;
background-position: center;
position: relative;
}
.blurEffect {
width: 100%;
height: 100%;
background-image: url("./../../../images/home_wp.jpg");
background-size: cover;
background-position: center;
filter: blur(10px);
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.overBlur {
position: relative;
z-index: 2;
}
.initTitlePos {
top: 10%;
}
.navButtons {
display: flex;
flex-direction: column;
align-items: center;
top: 17%;
}

8
src/pages/css/input.css Normal file
View File

@@ -0,0 +1,8 @@
.LoginList {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 2rem;
}

4
src/pages/index.css Normal file
View File

@@ -0,0 +1,4 @@
@import url("css/init.css");
@import url("css/home.css");
@import url("css/input.css");
@import url("css/account.css");

View File

@@ -22,4 +22,4 @@
"**/*.test.tsx",
"node_modules/@types/jest"
]
}
}

18
versions.md Normal file
View File

@@ -0,0 +1,18 @@
# Instruction to create tags and versions
```
npm version <type>
```
Here <type> can be:
- `patch` - the smallest update, 1.2.3 -> 1.2.**4**
- `minor` - 1.**2**.3 -> 1.**3**.0
- `major` - the greatest, **1**.2.3 -> **2**.0.0
To submit tag (previous command is treated as a commit):
```
git push --tags
```
The version in `package.json` will be updated automatically\
After that add new version to *brojs-admin*