Compare commits

...

8 Commits
v0.5.1 ... main

Author SHA1 Message Date
Nikolai Petukhov
3822ad0dce fixed exit button 2024-10-22 19:25:49 +03:00
Nikolai Petukhov
2829c11e4c 0.5.4 2024-10-16 23:48:44 +03:00
Nikolai Petukhov
0bcdb95b57 change to dev api 2024-10-16 23:48:26 +03:00
Nikolai Petukhov
cabe02be57 0.5.3 2024-10-16 23:43:37 +03:00
Nikolai Petukhov
59d4a44079 integrated redux library 2024-10-16 23:40:34 +03:00
Nikolai Petukhov
fde1f8ecfe fix chats sorting 2024-10-16 23:16:06 +03:00
Nikolai Petukhov
9b4870995f 0.5.2 2024-10-12 12:44:15 +03:00
Nikolai Petukhov
2f447cef1a fixed init routes 2024-10-12 12:44:04 +03:00
9 changed files with 235 additions and 58 deletions

99
package-lock.json generated
View File

@ -1,16 +1,17 @@
{
"name": "enterfront",
"version": "0.5.1",
"version": "0.5.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "enterfront",
"version": "0.5.1",
"version": "0.5.4",
"dependencies": {
"@brojs/cli": "^1.0.0",
"@brojs/create": "^1.0.0",
"@ijl/cli": "^5.1.0",
"@reduxjs/toolkit": "^2.3.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"dotenv": "^16.4.5",
@ -21,6 +22,7 @@
"react-dom": "^18.3.1",
"react-emoji-picker": "^1.0.13",
"react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.26.1",
"react-toastify": "^10.0.5",
"socket.io": "^4.8.0",
@ -3057,6 +3059,40 @@
"node": ">=14"
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz",
"integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==",
"license": "MIT",
"dependencies": {
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@reduxjs/toolkit/node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/@remix-run/router": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
@ -3174,6 +3210,12 @@
"source-map": "^0.6.1"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==",
"license": "MIT"
},
"node_modules/@types/webpack": {
"version": "4.41.39",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.39.tgz",
@ -8288,6 +8330,29 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-redux": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz",
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^18.2.25",
"react": "^18.0",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-router": {
"version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
@ -8411,6 +8476,21 @@
"recursive-watch": "bin.js"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -8519,6 +8599,12 @@
"node": ">=0.10.0"
}
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -9829,6 +9915,15 @@
"node": ">=0.10.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -3,6 +3,7 @@
"@brojs/cli": "^1.0.0",
"@brojs/create": "^1.0.0",
"@ijl/cli": "^5.1.0",
"@reduxjs/toolkit": "^2.3.0",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"dotenv": "^16.4.5",
@ -13,6 +14,7 @@
"react-dom": "^18.3.1",
"react-emoji-picker": "^1.0.13",
"react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.26.1",
"react-toastify": "^10.0.5",
"socket.io": "^4.8.0",
@ -29,5 +31,5 @@
"clean": "rimraf dist"
},
"name": "enterfront",
"version": "0.5.1"
"version": "0.5.4"
}

View File

@ -6,6 +6,9 @@ import { Dashboard } from './dashboard';
import {ToastContainer} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';
import { Provider } from 'react-redux';
import store from './backend/redux/store.js'; // Import your store
import './index.css'
import {displayMessage} from "./backend/notifications/notifications.js";
@ -26,13 +29,13 @@ const App = () => {
}, []);
return(
<div>
<Provider store={store}>
<BrowserRouter>
<Dashboard />
</BrowserRouter>
<ToastContainer/>
</div>
</Provider>
)
}

View File

@ -0,0 +1,34 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import {getConfigValue} from "@brojs/cli";
import { BASE_API_URL } from "../api.js";
const baseQuery = fetchBaseQuery({
baseUrl: BASE_API_URL,
prepareHeaders: (headers) => {
const token = localStorage.getItem('token');
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
});
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery,
endpoints: (builder) => ({
getChats: builder.query({
query: (username) => `/chat/list/${username}`,
}),
postChat: builder.mutation({
query: ({ id1, id2 }) => ({
url: `/chat/item/${id1}/${id2}`,
method: 'POST',
}),
}),
}),
});
// Export hooks for usage in functional components
export const { useGetChatsQuery, usePostChatMutation } = apiSlice;

View File

@ -0,0 +1,12 @@
import { configureStore } from '@reduxjs/toolkit';
import { apiSlice } from './api_slice';
const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
});
export default store;

View File

@ -13,7 +13,7 @@ const Account = () => {
localStorage.removeItem("token");
localStorage.setItem("message", "Exited successfully!");
window.location.href = "/";
window.location.href = URLs.baseUrl;
}
const [nickname, setNickname] = useState("");

View File

@ -4,39 +4,61 @@ import ChatsList from "../components/home/ChatsList.jsx";
import Header from "../components/home/Header.jsx";
import { displayMessage } from "../backend/notifications/notifications";
import { MessageType } from "../backend/notifications/message";
import { get, post } from "../backend/api";
import { useGetChatsQuery, usePostChatMutation } from "../backend/redux/api_slice"; // Update the import based on your API slice
import InputField from "../components/reg/InputField.jsx";
import Search from "../components/home/Search.jsx";
import { URLs } from "../__data__/urls";
const Home = () => {
const [chats, setChats] = useState([]);
const [chats, setChats] = useState([]); // Retained original variable name
const [interlocutor, setInterlocutor] = useState("");
async function retrieveChats() {
const username = localStorage.getItem("username");
if (!username) {
displayMessage("You're not logged in!", MessageType.WARN);
return;
const username = localStorage.getItem("username");
// Use Redux Queries
const { data: chatsData, error: getError, isLoading: isGetting } = useGetChatsQuery(username, {
skip: !username
});
console.log('From Redux:', chatsData);
const [createChat, { error: postError }] = usePostChatMutation();
useEffect(() => {
if (getError) {
displayMessage(getError.message, MessageType.ERROR);
}
const { ok, data } = await get("/chat/list/" + username);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
return;
if (getError) {
displayMessage(getError.message, MessageType.ERROR);
}
}, [getError, postError]);
const sortedChats = data.chats.sort((a, b) => {
const lastMessageA = new Date(a.lastMessageTimestamp);
const lastMessageB = new Date(b.lastMessageTimestamp);
return lastMessageB - lastMessageA;
});
useEffect(() => {
if (chatsData) {
// setChats(chatsData.chats);
setChats(sortedChats);
}
let data = chatsData.chats;
async function createChat(alias) {
const username = localStorage.getItem("username");
try {
const sortedChats = [...data].sort((a, b) => {
const lastMessageA = a.messages[a.messages.length - 1];
const lastMessageB = b.messages[b.messages.length - 1];
const dateA = new Date(lastMessageA.timestamp);
const dateB = new Date(lastMessageB.timestamp);
return dateB - dateA;
});
setChats(sortedChats);
} catch (e) {
console.error(e);
}
}
}, [chatsData]);
const createChatHandler = async (alias) => {
if (!username) {
displayMessage("You're not logged in!", MessageType.WARN);
return;
@ -44,42 +66,49 @@ const Home = () => {
displayMessage("Sent", MessageType.INFO);
const { ok, data } = await post("/chat/item/" + username + "/" + alias);
if (!ok) {
displayMessage(data.message, MessageType.ERROR);
} else {
try {
const data = await createChat({ id1: alias, id2: username }).unwrap(); // Using unwrap to handle promise rejection
localStorage.setItem("message", "Successfully opened chat!");
localStorage.setItem("interlocutorId", alias);
window.location.href = URLs.chat.url;
} catch (error) {
displayMessage(error.data.message, MessageType.ERROR);
}
}
useEffect(() => {
retrieveChats();
}, []);
};
return (
<div className="homeWrapper">
<div className="headerPos">
<Header />
<div className="homeWrapper">
<div className="headerPos">
<Header />
</div>
<HomeTitle />
<div className="search-input">
<InputField
title="Create new chat"
value={interlocutor}
setValue={setInterlocutor}
placeholder="Enter the username (id)"
enter={createChatHandler}
submit={interlocutor}
/>
</div>
{isGetting ? (
<div>Loading...</div>
) : (
<>
<Search search={createChatHandler} item={interlocutor} />
<p>Your chats</p>
<ChatsList chats={chats} />
</>
)}
</div>
<HomeTitle />
<div className="search-input">
<InputField
title="Create new chat"
value={interlocutor}
setValue={setInterlocutor}
placeholder="Enter the username (id)"
/>
</div>
<Search search={createChat} item={interlocutor} />
<p>Your chats</p>
<ChatsList chats={chats} />
</div>
);
};

View File

@ -6,6 +6,7 @@ import LoginTitle from "../components/reg/loginTitle.jsx";
import {MessageType} from "../backend/notifications/message.tsx";
import {displayMessage} from "../backend/notifications/notifications.js";
import {post} from "../backend/api.js";
import {URLs} from "../__data__/urls";
const SignIn = () => {
const [name, setName] = useState("");
@ -37,7 +38,7 @@ const SignIn = () => {
setNameErrorsCounter(0);
localStorage.setItem('message', 'Successfully logged in!');
window.location.href = "/";
window.location.href = URLs.baseUrl;
}
return (

View File

@ -5,6 +5,7 @@ import LoginTitle from "../components/reg/loginTitle.jsx";
import {post} from "../backend/api";
import {displayMessage} from "../backend/notifications/notifications";
import {MessageType} from "../backend/notifications/message";
import { URLs } from "../__data__/urls";
const SignUp = () => {
@ -47,7 +48,7 @@ const SignUp = () => {
localStorage.setItem('username', name);
localStorage.setItem('message', 'Successfully signed up!');
window.location.href = "/";
window.location.href = URLs.baseUrl;
}
return (