diff --git a/package-lock.json b/package-lock.json index 4173424..f028909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,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", @@ -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", diff --git a/package.json b/package.json index 770f18f..30d07f5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.tsx b/src/app.tsx index fc17077..16d80e3 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -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( -
+ -
+ ) } diff --git a/src/backend/api.js b/src/backend/api.js index 15ec334..040f11a 100644 --- a/src/backend/api.js +++ b/src/backend/api.js @@ -4,7 +4,7 @@ import {getConfigValue} from "@brojs/cli"; const LOCAL = "http://localhost:8099"; const DEV = "https://dev.bro-js.ru"; -export const BASE_API_URL = DEV + getConfigValue("enterfront.api"); +export const BASE_API_URL = LOCAL + getConfigValue("enterfront.api"); // fetch(`${BASE_API_URL}/books/list`) diff --git a/src/backend/redux/api_slice.js b/src/backend/redux/api_slice.js new file mode 100644 index 0000000..14bfe0a --- /dev/null +++ b/src/backend/redux/api_slice.js @@ -0,0 +1,39 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import {getConfigValue} from "@brojs/cli"; + +const LOCAL = "http://localhost:8099"; +const DEV = "https://dev.bro-js.ru"; + +export const BASE_API_URL = LOCAL + getConfigValue("enterfront.api"); + +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: (body) => ({ + url: '/chat/post', + method: 'POST', + body, + }), + }), + // Add more endpoints as needed + }), +}); + +// Export hooks for usage in functional components +export const { useGetChatsQuery, usePostChatMutation } = apiSlice; \ No newline at end of file diff --git a/src/backend/redux/store.js b/src/backend/redux/store.js new file mode 100644 index 0000000..936e0b8 --- /dev/null +++ b/src/backend/redux/store.js @@ -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; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 3b8fed1..8a3e610 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -4,39 +4,54 @@ 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); } + }, [getError]); - const { ok, data } = await get("/chat/list/" + username); - if (!ok) { - displayMessage(data.message, MessageType.ERROR); - return; + useEffect(() => { + if (chatsData) { + setChats(chatsData.chats); + + try { + // const sortedChats = chatsData.chats.sort((a, b) => { + // const lastMessageA = new Date(a.timestamp); + // const lastMessageB = new Date(b.timestamp); + // return lastMessageB - lastMessageA; + // }); + // // + // // console.log('sorted:', sortedChats); + // + // setChats(sortedChats); + } catch (e) { + console.error(e); + } } + }, [chatsData]); - const sortedChats = data.chats.sort((a, b) => { - const lastMessageA = new Date(a.lastMessageTimestamp); - const lastMessageB = new Date(b.lastMessageTimestamp); - return lastMessageB - lastMessageA; - }); - setChats(sortedChats); - } - - async function createChat(alias) { - const username = localStorage.getItem("username"); + const createChatHandler = async (alias) => { if (!username) { displayMessage("You're not logged in!", MessageType.WARN); return; @@ -44,42 +59,40 @@ 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({ alias, 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(); - }, []); + if (isGetting) return
Loading...
; return ( -
-
-
+
+
+
+
+ + + +
+ +
+ + + +

Your chats

+ {/* Retained original variable name */}
- - - -
- -
- - - -

Your chats

- -
); };