Compare commits
15 Commits
9c1c670ccb
...
ilias-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 79289457c9 | |||
| 5f386c0f4e | |||
| 33c8f863a1 | |||
|
|
dd16f42995 | ||
| f3e93bae19 | |||
| 07728cbbb0 | |||
| 17697d7f77 | |||
|
|
2829c11e4c | ||
|
|
0bcdb95b57 | ||
|
|
cabe02be57 | ||
|
|
59d4a44079 | ||
|
|
fde1f8ecfe | ||
|
|
9b4870995f | ||
|
|
2f447cef1a | ||
|
|
964ca236e8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.idea
|
.idea
|
||||||
|
coverage/
|
||||||
1
__mocks__/fileMock.js
Normal file
1
__mocks__/fileMock.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = 'test-file-stub';
|
||||||
11
__tests__/AccountButtons.test.tsx
Normal file
11
__tests__/AccountButtons.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import AccountButtons from '../src/components/account/AccountButtons.jsx';
|
||||||
|
|
||||||
|
describe('AccountButtons Component', () => {
|
||||||
|
it('should render the Back link', () => {
|
||||||
|
render(<AccountButtons registered={false} />);
|
||||||
|
const backLinkElement = screen.getByRole('link', { name: /back/i });
|
||||||
|
expect(backLinkElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
18
__tests__/ActionButton.test.tsx
Normal file
18
__tests__/ActionButton.test.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import ActionButton from '../src/components/account/ActionButton.jsx';
|
||||||
|
|
||||||
|
describe('ActionButton Component', () => {
|
||||||
|
it('should render with the correct title and call the action when clicked', () => {
|
||||||
|
const mockAction = jest.fn();
|
||||||
|
const title = 'Click Me';
|
||||||
|
|
||||||
|
render(<ActionButton action={mockAction} title={title} />);
|
||||||
|
|
||||||
|
const buttonElement = screen.getByText(title);
|
||||||
|
expect(buttonElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(buttonElement);
|
||||||
|
expect(mockAction).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
23
__tests__/Card.test.tsx
Normal file
23
__tests__/Card.test.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import Card from '../src/components/home/Card.jsx';
|
||||||
|
|
||||||
|
describe('Card Component', () => {
|
||||||
|
it('should render the Card component with the given ID', () => {
|
||||||
|
const testId = '123';
|
||||||
|
render(<Card id={testId} color={"FFA500FF"}/>);
|
||||||
|
|
||||||
|
const cardElement = screen.getByText(/123/i);
|
||||||
|
expect(cardElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store the ID in local storage when clicked', () => {
|
||||||
|
const testId = '456';
|
||||||
|
render(<Card id={testId} color={"FFA500FF"}/>);
|
||||||
|
|
||||||
|
const cardElement = screen.getByText(/456/i);
|
||||||
|
fireEvent.click(cardElement);
|
||||||
|
|
||||||
|
expect(localStorage.setItem).toHaveBeenCalledWith('selectedId', testId);
|
||||||
|
});
|
||||||
|
});
|
||||||
24
__tests__/Helloitem.test.tsx
Normal file
24
__tests__/Helloitem.test.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import HelloItem from '../src/components/account/HelloItem.jsx';
|
||||||
|
|
||||||
|
describe('HelloItem Component', () => {
|
||||||
|
it('should display a personalized greeting when nickname is provided', () => {
|
||||||
|
const nickname = 'JohnDoe';
|
||||||
|
const id = '12345';
|
||||||
|
|
||||||
|
render(<HelloItem nickname={nickname} id={id} />);
|
||||||
|
|
||||||
|
const greetingElement = screen.getByText(`Hello, ${nickname}!`);
|
||||||
|
const idElement = screen.getByText(`Your ID: ${id}`);
|
||||||
|
expect(greetingElement).toBeInTheDocument();
|
||||||
|
expect(idElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a default message when nickname is not provided', () => {
|
||||||
|
render(<HelloItem nickname="" id="12345" />);
|
||||||
|
|
||||||
|
const defaultMessage = screen.getByText("You don't have an account :(");
|
||||||
|
expect(defaultMessage).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
60
__tests__/Home.test.jsx
Normal file
60
__tests__/Home.test.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import configureStore from "redux-mock-store";
|
||||||
|
import Home from "src/pages/Home";
|
||||||
|
import { useGetChatsQuery } from "src/backend/redux/api_slice";
|
||||||
|
|
||||||
|
// Mock Redux store
|
||||||
|
const mockStore = configureStore([]);
|
||||||
|
|
||||||
|
// Mock the useGetChatsQuery hook
|
||||||
|
jest.mock("src/backend/redux/api_slice", () => ({
|
||||||
|
useGetChatsQuery: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Home Page", () => {
|
||||||
|
let store;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders Home page with loading state", () => {
|
||||||
|
useGetChatsQuery.mockReturnValue({ isLoading: true });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<Home />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders Home page with chat data", () => {
|
||||||
|
const chatsData = [{ id: 1, name: "Chat 1" }, { id: 2, name: "Chat 2" }];
|
||||||
|
useGetChatsQuery.mockReturnValue({ data: chatsData, isLoading: false });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<Home />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Chat 1")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Chat 2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders error message when fetching chats fails", () => {
|
||||||
|
useGetChatsQuery.mockReturnValue({ error: true, isLoading: false });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<Home />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
41
__tests__/InputField.test.tsx
Normal file
41
__tests__/InputField.test.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import InputField from '../src/components/reg/InputField.jsx';
|
||||||
|
|
||||||
|
describe('InputField Component', () => {
|
||||||
|
it('should render with the correct title and placeholder', () => {
|
||||||
|
const title = 'Username';
|
||||||
|
const placeholder = 'Enter your username';
|
||||||
|
|
||||||
|
render(<InputField title={title} placeholder={placeholder} value="" setValue={() => {}} />);
|
||||||
|
|
||||||
|
const titleElement = screen.getByText(title);
|
||||||
|
const inputElement = screen.getByPlaceholderText(placeholder);
|
||||||
|
|
||||||
|
expect(titleElement).toBeInTheDocument();
|
||||||
|
expect(inputElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call setValue on input change', () => {
|
||||||
|
const mockSetValue = jest.fn();
|
||||||
|
const newValue = 'testUser';
|
||||||
|
|
||||||
|
render(<InputField title="Username" value="" setValue={mockSetValue} />);
|
||||||
|
|
||||||
|
const inputElement = screen.getByRole('textbox');
|
||||||
|
fireEvent.change(inputElement, { target: { value: newValue } });
|
||||||
|
|
||||||
|
expect(mockSetValue).toHaveBeenCalledWith(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call submit function when Enter key is pressed', () => {
|
||||||
|
const mockSubmit = jest.fn();
|
||||||
|
|
||||||
|
render(<InputField title="Username" value="" setValue={() => {}} submit={mockSubmit} />);
|
||||||
|
|
||||||
|
const inputElement = screen.getByRole('textbox');
|
||||||
|
fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter' });
|
||||||
|
|
||||||
|
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
16
__tests__/NavButton.test.tsx
Normal file
16
__tests__/NavButton.test.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import NavButton from '../src/components/init/NavButton.jsx';
|
||||||
|
|
||||||
|
describe('NavButton Component', () => {
|
||||||
|
it('should render the NavButton with the correct text and link', () => {
|
||||||
|
const navLink = '/home';
|
||||||
|
const buttonText = 'Go Home';
|
||||||
|
|
||||||
|
render(<NavButton nav={navLink} text={buttonText} />);
|
||||||
|
|
||||||
|
const linkElement = screen.getByText(buttonText);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
expect(linkElement.closest('a')).toHaveAttribute('href', navLink);
|
||||||
|
});
|
||||||
|
});
|
||||||
23
__tests__/Search.test.tsx
Normal file
23
__tests__/Search.test.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import Search from '../src/components/home/Search.jsx';
|
||||||
|
|
||||||
|
describe('Search Component', () => {
|
||||||
|
it('should render the Search button', () => {
|
||||||
|
render(<Search search={() => {}} item="testItem" />);
|
||||||
|
const searchButton = screen.getByText(/find/i);
|
||||||
|
expect(searchButton).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the search function with the correct item when clicked', () => {
|
||||||
|
const mockSearch = jest.fn();
|
||||||
|
const item = 'testItem';
|
||||||
|
|
||||||
|
render(<Search search={mockSearch} item={item} />);
|
||||||
|
|
||||||
|
const searchButton = screen.getByText(/find/i);
|
||||||
|
fireEvent.click(searchButton);
|
||||||
|
|
||||||
|
expect(mockSearch).toHaveBeenCalledWith(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
52
__tests__/SignIn.test.jsx
Normal file
52
__tests__/SignIn.test.jsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import SignIn from "src/pages/SignIn";
|
||||||
|
import { displayMessage } from "src/backend/notifications/notifications";
|
||||||
|
import { post } from "src/backend/api";
|
||||||
|
|
||||||
|
// Mock the displayMessage and post functions
|
||||||
|
jest.mock("src/backend/notifications/notifications", () => ({
|
||||||
|
displayMessage: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock("src/backend/api", () => ({
|
||||||
|
post: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("SignIn Page", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders SignIn form", () => {
|
||||||
|
render(<SignIn />);
|
||||||
|
expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/sign in/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays error message on failed login", async () => {
|
||||||
|
post.mockResolvedValueOnce({ ok: false, data: { message: "Invalid credentials" } });
|
||||||
|
|
||||||
|
render(<SignIn />);
|
||||||
|
fireEvent.change(screen.getByLabelText(/username/i), { target: { value: "user" } });
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: "password" } });
|
||||||
|
fireEvent.click(screen.getByText(/sign in/i));
|
||||||
|
|
||||||
|
expect(await screen.findByText(/invalid credentials/i)).toBeInTheDocument();
|
||||||
|
expect(displayMessage).toHaveBeenCalledWith("Invalid credentials", "error");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays additional info message after multiple login attempts", async () => {
|
||||||
|
post.mockResolvedValueOnce({ ok: false, data: { message: "Invalid credentials" } });
|
||||||
|
|
||||||
|
render(<SignIn />);
|
||||||
|
fireEvent.change(screen.getByLabelText(/username/i), { target: { value: "user" } });
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: "password" } });
|
||||||
|
|
||||||
|
// Simulate two failed login attempts
|
||||||
|
fireEvent.click(screen.getByText(/sign in/i));
|
||||||
|
fireEvent.click(screen.getByText(/sign in/i));
|
||||||
|
|
||||||
|
expect(displayMessage).toHaveBeenCalledWith("Note that you need to enter your ID name", "info");
|
||||||
|
});
|
||||||
|
});
|
||||||
13
__tests__/helloworld.test.tsx.no
Normal file
13
__tests__/helloworld.test.tsx.no
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
describe('Hello World Test', () => {
|
||||||
|
it('should display hello world', () => {
|
||||||
|
// Render a simple component
|
||||||
|
render(<div>Hello World</div>);
|
||||||
|
|
||||||
|
// Check if "Hello World" is in the document
|
||||||
|
const helloWorldElement = screen.getByText(/hello world/i);
|
||||||
|
expect(helloWorldElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
7
babel.config.js
Normal file
7
babel.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-env',
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript'],
|
||||||
|
};
|
||||||
|
|
||||||
28
jest.config.ts
Normal file
28
jest.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Config } from 'jest';
|
||||||
|
import { defaults } from 'jest-config';
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
clearMocks: true,
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
"src/components/**/*.{js,jsx,ts,tsx}", // Include all components
|
||||||
|
"src/pages/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"!src/**/*.test.{js,jsx,ts,tsx}", // Exclude test files
|
||||||
|
"!src/**/index.{js,jsx,ts,tsx}", // Optionally exclude index files
|
||||||
|
],
|
||||||
|
coverageDirectory: "coverage",
|
||||||
|
coverageProvider: "v8",
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
// transform: {
|
||||||
|
// '^.+\\.(ts|tsx|js|jsx)$': 'babel-jest',
|
||||||
|
// },
|
||||||
|
moduleNameMapper: {
|
||||||
|
"^@/src/(.*)$": "<rootDir>/src/$1", // Map '@/src' to the 'src' folder
|
||||||
|
"^src/(.*)$": "<rootDir>/src/$1", // Map 'src' to the 'src' folder
|
||||||
|
"\\.(svg|png|jpg|jpeg|gif)$": "<rootDir>/__mocks__/fileMock.js", // Add this line
|
||||||
|
},
|
||||||
|
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx', 'js', 'jsx'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
17
jest.setup.ts
Normal file
17
jest.setup.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import mockedConfig from './bro.config.js'
|
||||||
|
|
||||||
|
jest.mock('@brojs/cli', () => {
|
||||||
|
return {
|
||||||
|
getNavigations() {
|
||||||
|
return mockedConfig.navigations
|
||||||
|
},
|
||||||
|
getNavigationsValue(key) {
|
||||||
|
return mockedConfig.navigations[key]
|
||||||
|
},
|
||||||
|
getConfigValue(key) {
|
||||||
|
return mockedConfig.config[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
5099
package-lock.json
generated
5099
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -3,6 +3,7 @@
|
|||||||
"@brojs/cli": "^1.0.0",
|
"@brojs/cli": "^1.0.0",
|
||||||
"@brojs/create": "^1.0.0",
|
"@brojs/create": "^1.0.0",
|
||||||
"@ijl/cli": "^5.1.0",
|
"@ijl/cli": "^5.1.0",
|
||||||
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"@types/react": "^18.3.5",
|
"@types/react": "^18.3.5",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-emoji-picker": "^1.0.13",
|
"react-emoji-picker": "^1.0.13",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router-dom": "^6.26.1",
|
||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"socket.io": "^4.8.0",
|
"socket.io": "^4.8.0",
|
||||||
@@ -26,8 +28,25 @@
|
|||||||
"start": "brojs server --port=8099 --with-open-browser",
|
"start": "brojs server --port=8099 --with-open-browser",
|
||||||
"build": "npm run clean && brojs build --dev",
|
"build": "npm run clean && brojs build --dev",
|
||||||
"build:prod": "npm run clean && brojs build",
|
"build:prod": "npm run clean && brojs build",
|
||||||
"clean": "rimraf dist"
|
"clean": "rimraf dist",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"name": "enterfront",
|
"name": "enterfront",
|
||||||
"version": "0.5.0"
|
"version": "0.5.4",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.25.8",
|
||||||
|
"@babel/preset-env": "^7.25.8",
|
||||||
|
"@babel/preset-react": "^7.25.7",
|
||||||
|
"@babel/preset-typescript": "^7.25.7",
|
||||||
|
"@testing-library/jest-dom": "^6.6.2",
|
||||||
|
"@testing-library/react": "^16.0.1",
|
||||||
|
"@types/jest": "^29.5.13",
|
||||||
|
"babel-jest": "^29.7.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"redux-mock-store": "^1.5.4",
|
||||||
|
"ts-jest": "^29.2.5",
|
||||||
|
"ts-node": "^10.9.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import { Dashboard } from './dashboard';
|
|||||||
import {ToastContainer} from "react-toastify";
|
import {ToastContainer} from "react-toastify";
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
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 './index.css'
|
||||||
import {displayMessage} from "./backend/notifications/notifications.js";
|
import {displayMessage} from "./backend/notifications/notifications.js";
|
||||||
@@ -26,13 +29,13 @@ const App = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div>
|
<Provider store={store}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
||||||
<ToastContainer/>
|
<ToastContainer/>
|
||||||
</div>
|
</Provider>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {getConfigValue} from "@brojs/cli";
|
|||||||
const LOCAL = "http://localhost:8099";
|
const LOCAL = "http://localhost:8099";
|
||||||
const DEV = "https://dev.bro-js.ru";
|
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`)
|
// fetch(`${BASE_API_URL}/books/list`)
|
||||||
|
|
||||||
|
|||||||
34
src/backend/redux/api_slice.js
Normal file
34
src/backend/redux/api_slice.js
Normal 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;
|
||||||
12
src/backend/redux/store.js
Normal file
12
src/backend/redux/store.js
Normal 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;
|
||||||
@@ -13,7 +13,7 @@ const InputField = (props) => {
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
if (props.submit) {
|
if (props.submit) {
|
||||||
props.enter(props.submit);
|
props.submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,39 +4,61 @@ import ChatsList from "../components/home/ChatsList.jsx";
|
|||||||
import Header from "../components/home/Header.jsx";
|
import Header from "../components/home/Header.jsx";
|
||||||
import { displayMessage } from "../backend/notifications/notifications";
|
import { displayMessage } from "../backend/notifications/notifications";
|
||||||
import { MessageType } from "../backend/notifications/message";
|
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 InputField from "../components/reg/InputField.jsx";
|
||||||
import Search from "../components/home/Search.jsx";
|
import Search from "../components/home/Search.jsx";
|
||||||
import { URLs } from "../__data__/urls";
|
import { URLs } from "../__data__/urls";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [chats, setChats] = useState([]);
|
const [chats, setChats] = useState([]); // Retained original variable name
|
||||||
const [interlocutor, setInterlocutor] = useState("");
|
const [interlocutor, setInterlocutor] = useState("");
|
||||||
|
|
||||||
async function retrieveChats() {
|
|
||||||
const username = localStorage.getItem("username");
|
const username = localStorage.getItem("username");
|
||||||
if (!username) {
|
|
||||||
displayMessage("You're not logged in!", MessageType.WARN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ok, data } = await get("/chat/list/" + username);
|
// Use Redux Queries
|
||||||
if (!ok) {
|
const { data: chatsData, error: getError, isLoading: isGetting } = useGetChatsQuery(username, {
|
||||||
displayMessage(data.message, MessageType.ERROR);
|
skip: !username
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const sortedChats = data.chats.sort((a, b) => {
|
console.log('From Redux:', chatsData);
|
||||||
const lastMessageA = new Date(a.lastMessageTimestamp);
|
|
||||||
const lastMessageB = new Date(b.lastMessageTimestamp);
|
const [createChat, { error: postError }] = usePostChatMutation();
|
||||||
return lastMessageB - lastMessageA;
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getError) {
|
||||||
|
displayMessage(getError.message, MessageType.ERROR);
|
||||||
|
}
|
||||||
|
if (getError) {
|
||||||
|
displayMessage(getError.message, MessageType.ERROR);
|
||||||
|
}
|
||||||
|
}, [getError, postError]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatsData) {
|
||||||
|
// setChats(chatsData.chats);
|
||||||
|
|
||||||
|
let data = chatsData.chats;
|
||||||
|
|
||||||
|
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);
|
setChats(sortedChats);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, [chatsData]);
|
||||||
|
|
||||||
async function createChat(alias) {
|
|
||||||
const username = localStorage.getItem("username");
|
const createChatHandler = async (alias) => {
|
||||||
if (!username) {
|
if (!username) {
|
||||||
displayMessage("You're not logged in!", MessageType.WARN);
|
displayMessage("You're not logged in!", MessageType.WARN);
|
||||||
return;
|
return;
|
||||||
@@ -44,19 +66,15 @@ const Home = () => {
|
|||||||
|
|
||||||
displayMessage("Sent", MessageType.INFO);
|
displayMessage("Sent", MessageType.INFO);
|
||||||
|
|
||||||
const { ok, data } = await post("/chat/item/" + username + "/" + alias);
|
try {
|
||||||
if (!ok) {
|
const data = await createChat({ id1: alias, id2: username }).unwrap(); // Using unwrap to handle promise rejection
|
||||||
displayMessage(data.message, MessageType.ERROR);
|
|
||||||
} else {
|
|
||||||
localStorage.setItem("message", "Successfully opened chat!");
|
localStorage.setItem("message", "Successfully opened chat!");
|
||||||
localStorage.setItem("interlocutorId", alias);
|
localStorage.setItem("interlocutorId", alias);
|
||||||
window.location.href = URLs.chat.url;
|
window.location.href = URLs.chat.url;
|
||||||
|
} catch (error) {
|
||||||
|
displayMessage(error.data.message, MessageType.ERROR);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
retrieveChats();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="homeWrapper">
|
<div className="homeWrapper">
|
||||||
@@ -72,13 +90,24 @@ const Home = () => {
|
|||||||
value={interlocutor}
|
value={interlocutor}
|
||||||
setValue={setInterlocutor}
|
setValue={setInterlocutor}
|
||||||
placeholder="Enter the username (id)"
|
placeholder="Enter the username (id)"
|
||||||
|
|
||||||
|
enter={createChatHandler}
|
||||||
|
submit={interlocutor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Search search={createChat} item={interlocutor} />
|
{isGetting ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Search search={createChatHandler} item={interlocutor} />
|
||||||
|
|
||||||
<p>Your chats</p>
|
<p>Your chats</p>
|
||||||
<ChatsList chats={chats} />
|
<ChatsList chats={chats} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import LoginTitle from "../components/reg/loginTitle.jsx";
|
|||||||
import {MessageType} from "../backend/notifications/message.tsx";
|
import {MessageType} from "../backend/notifications/message.tsx";
|
||||||
import {displayMessage} from "../backend/notifications/notifications.js";
|
import {displayMessage} from "../backend/notifications/notifications.js";
|
||||||
import {post} from "../backend/api.js";
|
import {post} from "../backend/api.js";
|
||||||
|
import {URLs} from "../__data__/urls";
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@@ -37,7 +38,7 @@ const SignIn = () => {
|
|||||||
setNameErrorsCounter(0);
|
setNameErrorsCounter(0);
|
||||||
|
|
||||||
localStorage.setItem('message', 'Successfully logged in!');
|
localStorage.setItem('message', 'Successfully logged in!');
|
||||||
window.location.href = "/";
|
window.location.href = URLs.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import LoginTitle from "../components/reg/loginTitle.jsx";
|
|||||||
import {post} from "../backend/api";
|
import {post} from "../backend/api";
|
||||||
import {displayMessage} from "../backend/notifications/notifications";
|
import {displayMessage} from "../backend/notifications/notifications";
|
||||||
import {MessageType} from "../backend/notifications/message";
|
import {MessageType} from "../backend/notifications/message";
|
||||||
|
import { URLs } from "../__data__/urls";
|
||||||
|
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
@@ -47,7 +48,7 @@ const SignUp = () => {
|
|||||||
localStorage.setItem('username', name);
|
localStorage.setItem('username', name);
|
||||||
|
|
||||||
localStorage.setItem('message', 'Successfully signed up!');
|
localStorage.setItem('message', 'Successfully signed up!');
|
||||||
window.location.href = "/";
|
window.location.href = URLs.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"target": "es6",
|
"target": "es6",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"typeRoots": ["node_modules/@types", "src/typings"],
|
"typeRoots": ["node_modules/@types", "src/typings"],
|
||||||
"types" : ["webpack-env", "node"],
|
"types" : ["webpack-env", "node", "jest"],
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|||||||
Reference in New Issue
Block a user