Compare commits
No commits in common. "main" and "v0.1.0" have entirely different histories.
5
@types/assets.d.ts
vendored
5
@types/assets.d.ts
vendored
@ -1,5 +0,0 @@
|
|||||||
declare module '*.png' {
|
|
||||||
const imagePath: string;
|
|
||||||
|
|
||||||
export default imagePath;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
|
|
||||||
};
|
|
@ -11,7 +11,7 @@ module.exports = {
|
|||||||
/* use https://admin.bro-js.ru/ to create config, navigations and features */
|
/* use https://admin.bro-js.ru/ to create config, navigations and features */
|
||||||
navigations: {
|
navigations: {
|
||||||
'kfu-24-teacher.main': '/kfu-24-teacher',
|
'kfu-24-teacher.main': '/kfu-24-teacher',
|
||||||
'kfu-24-teacher.detail': '/kfu-24-teacher/:id'
|
'kfu-24-teacher.detail': '/kfu-24-teacher/detail'
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
'kfu-24-teacher': {
|
'kfu-24-teacher': {
|
||||||
|
@ -29,11 +29,5 @@ export default [
|
|||||||
...hooksPlugin.configs.recommended.rules
|
...hooksPlugin.configs.recommended.rules
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
files: ['stubs/api/**/*'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-require-imports': 'off'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pluginPrettier
|
pluginPrettier
|
||||||
];
|
];
|
||||||
|
190
jest.config.js
190
jest.config.js
@ -1,190 +0,0 @@
|
|||||||
/**
|
|
||||||
* For a detailed explanation regarding each configuration property, visit:
|
|
||||||
* https://jestjs.io/docs/configuration
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {import('jest').Config} */
|
|
||||||
const config = {
|
|
||||||
// All imported modules in your tests should be mocked automatically
|
|
||||||
// automock: false,
|
|
||||||
|
|
||||||
// Stop running tests after `n` failures
|
|
||||||
// bail: 0,
|
|
||||||
|
|
||||||
// The directory where Jest should store its cached dependency information
|
|
||||||
// cacheDirectory: "C:\\Users\\nekit\\AppData\\Local\\Temp\\jest",
|
|
||||||
|
|
||||||
// Automatically clear mock calls, instances, contexts and results before every test
|
|
||||||
clearMocks: true,
|
|
||||||
|
|
||||||
// Indicates whether the coverage information should be collected while executing the test
|
|
||||||
collectCoverage: true,
|
|
||||||
|
|
||||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
|
||||||
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!**/__tests__/**/*'],
|
|
||||||
|
|
||||||
// The directory where Jest should output its coverage files
|
|
||||||
coverageDirectory: 'coverage',
|
|
||||||
|
|
||||||
// An array of regexp pattern strings used to skip coverage collection
|
|
||||||
coveragePathIgnorePatterns: ['\\\\node_modules\\\\'],
|
|
||||||
|
|
||||||
// Indicates which provider should be used to instrument code for coverage
|
|
||||||
coverageProvider: 'v8',
|
|
||||||
|
|
||||||
// A list of reporter names that Jest uses when writing coverage reports
|
|
||||||
// coverageReporters: [
|
|
||||||
// "json",
|
|
||||||
// "text",
|
|
||||||
// "lcov",
|
|
||||||
// "clover"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An object that configures minimum threshold enforcement for coverage results
|
|
||||||
// coverageThreshold: undefined,
|
|
||||||
|
|
||||||
// A path to a custom dependency extractor
|
|
||||||
// dependencyExtractor: undefined,
|
|
||||||
|
|
||||||
// Make calling deprecated APIs throw helpful error messages
|
|
||||||
// errorOnDeprecated: false,
|
|
||||||
|
|
||||||
// The default configuration for fake timers
|
|
||||||
// fakeTimers: {
|
|
||||||
// "enableGlobally": false
|
|
||||||
// },
|
|
||||||
|
|
||||||
// Force coverage collection from ignored files using an array of glob patterns
|
|
||||||
// forceCoverageMatch: [],
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once before all test suites
|
|
||||||
// globalSetup: undefined,
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once after all test suites
|
|
||||||
// globalTeardown: undefined,
|
|
||||||
|
|
||||||
// A set of global variables that need to be available in all test environments
|
|
||||||
// globals: {},
|
|
||||||
|
|
||||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
|
||||||
// maxWorkers: "50%",
|
|
||||||
|
|
||||||
// An array of directory names to be searched recursively up from the requiring module's location
|
|
||||||
// moduleDirectories: [
|
|
||||||
// "node_modules"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An array of file extensions your modules use
|
|
||||||
// moduleFileExtensions: [
|
|
||||||
// "js",
|
|
||||||
// "mjs",
|
|
||||||
// "cjs",
|
|
||||||
// "jsx",
|
|
||||||
// "ts",
|
|
||||||
// "tsx",
|
|
||||||
// "json",
|
|
||||||
// "node"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
|
||||||
// moduleNameMapper: {},
|
|
||||||
|
|
||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
|
||||||
// modulePathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Activates notifications for test results
|
|
||||||
// notify: false,
|
|
||||||
|
|
||||||
// An enum that specifies notification mode. Requires { notify: true }
|
|
||||||
// notifyMode: "failure-change",
|
|
||||||
|
|
||||||
// A preset that is used as a base for Jest's configuration
|
|
||||||
// preset: undefined,
|
|
||||||
|
|
||||||
// Run tests from one or more projects
|
|
||||||
// projects: undefined,
|
|
||||||
|
|
||||||
// Use this configuration option to add custom reporters to Jest
|
|
||||||
// reporters: undefined,
|
|
||||||
|
|
||||||
// Automatically reset mock state before every test
|
|
||||||
// resetMocks: false,
|
|
||||||
|
|
||||||
// Reset the module registry before running each individual test
|
|
||||||
// resetModules: false,
|
|
||||||
|
|
||||||
// A path to a custom resolver
|
|
||||||
// resolver: undefined,
|
|
||||||
|
|
||||||
// Automatically restore mock state and implementation before every test
|
|
||||||
// restoreMocks: false,
|
|
||||||
|
|
||||||
// The root directory that Jest should scan for tests and modules within
|
|
||||||
// rootDir: undefined,
|
|
||||||
|
|
||||||
// A list of paths to directories that Jest should use to search for files in
|
|
||||||
// roots: [
|
|
||||||
// "<rootDir>"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// Allows you to use a custom runner instead of Jest's default test runner
|
|
||||||
// runner: "jest-runner",
|
|
||||||
|
|
||||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
|
||||||
// setupFiles: [],
|
|
||||||
|
|
||||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
|
|
||||||
|
|
||||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
|
||||||
// slowTestThreshold: 5,
|
|
||||||
|
|
||||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
|
||||||
// snapshotSerializers: [],
|
|
||||||
|
|
||||||
// The test environment that will be used for testing
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
|
||||||
// testEnvironmentOptions: {},
|
|
||||||
|
|
||||||
// Adds a location field to test results
|
|
||||||
// testLocationInResults: false,
|
|
||||||
|
|
||||||
// The glob patterns Jest uses to detect test files
|
|
||||||
testMatch: ['**/?(*.)+(test).[tj]s?(x)'],
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
|
||||||
// testPathIgnorePatterns: [
|
|
||||||
// "\\\\node_modules\\\\"
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
|
||||||
// testRegex: [],
|
|
||||||
|
|
||||||
// This option allows the use of a custom results processor
|
|
||||||
// testResultsProcessor: undefined,
|
|
||||||
|
|
||||||
// This option allows use of a custom test runner
|
|
||||||
// testRunner: "jest-circus/runner",
|
|
||||||
|
|
||||||
// A map from regular expressions to paths to transformers
|
|
||||||
// transform: undefined,
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
|
||||||
transformIgnorePatterns: ['\\\\node_modules\\\\(?!(@brojs)/)', '\\.pnp\\.[^\\\\]+$']
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
|
||||||
// unmockedModulePathPatterns: undefined,
|
|
||||||
|
|
||||||
// Indicates whether each individual test should be reported during the run
|
|
||||||
// verbose: undefined,
|
|
||||||
|
|
||||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
|
||||||
// watchPathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Whether to use watchman for file crawling
|
|
||||||
// watchman: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
5643
package-lock.json
generated
5643
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "./src/index.tsx",
|
"main": "./src/index.tsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"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",
|
||||||
@ -14,27 +14,14 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brojs/cli": "^1.6.3",
|
"@brojs/cli": "^1.6.1",
|
||||||
"@emotion/react": "^11.13.5",
|
|
||||||
"@emotion/styled": "^11.13.5",
|
|
||||||
"@reduxjs/toolkit": "^2.5.0",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"keycloak-connect": "^26.0.7",
|
|
||||||
"keycloak-js": "^26.0.7",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-redux": "^9.2.0",
|
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router-dom": "^6.28.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
|
||||||
"@testing-library/react": "^16.1.0",
|
|
||||||
"@types/react": "^18.3.12",
|
|
||||||
"@types/react-dom": "^18.3.1",
|
|
||||||
"@types/webpack-env": "^1.18.5",
|
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
@ -42,8 +29,6 @@
|
|||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.2",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.12.0",
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
|
||||||
"typescript-eslint": "^8.13.0"
|
"typescript-eslint": "^8.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { sum } from './mocks/sum';
|
|
||||||
import { describe, test, expect } from '@jest/globals';
|
|
||||||
|
|
||||||
describe('Тесты на функцию sum', () => {
|
|
||||||
test('1 + 2 = 3', () => {
|
|
||||||
const result = sum(1, 2);
|
|
||||||
|
|
||||||
expect(result).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,18 +0,0 @@
|
|||||||
import { jest } from '@jest/globals';
|
|
||||||
import { listService } from '../../../../service/list';
|
|
||||||
import { GetListResponse } from '../../../../service/list/types';
|
|
||||||
|
|
||||||
export const spyedGetList = jest.spyOn(listService, 'getList');
|
|
||||||
|
|
||||||
export const mockGetList = (data?: GetListResponse) => {
|
|
||||||
spyedGetList.mockResolvedValueOnce(
|
|
||||||
data ?? [
|
|
||||||
{ id: 1, title: 'title', description: 'description' },
|
|
||||||
{ id: 2, title: 'title', description: 'description' },
|
|
||||||
{ id: 3, title: 'title', description: 'description' },
|
|
||||||
{ id: 4, title: 'title', description: 'description' },
|
|
||||||
{ id: 5, title: 'title', description: 'description' }
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return spyedGetList;
|
|
||||||
};
|
|
@ -1,25 +0,0 @@
|
|||||||
import { jest } from '@jest/globals';
|
|
||||||
|
|
||||||
jest.mock<typeof import('@brojs/cli')>('@brojs/cli', () => {
|
|
||||||
global.System = {
|
|
||||||
get: () => ({
|
|
||||||
getConfig: jest.fn(),
|
|
||||||
getConfigValue: jest.fn(),
|
|
||||||
getNavigations: jest.fn(),
|
|
||||||
getNavigationsValue: jest.fn(),
|
|
||||||
getAllFeatures: jest.fn(),
|
|
||||||
getFeatures: jest.fn(),
|
|
||||||
getHistory: jest.fn(),
|
|
||||||
getNavigation: jest.fn(),
|
|
||||||
getNavigationValue: jest.fn()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const originalBroJsCli = jest.requireActual<typeof import('@brojs/cli')>('@brojs/cli');
|
|
||||||
|
|
||||||
return {
|
|
||||||
...originalBroJsCli,
|
|
||||||
getConfigValue: () => {
|
|
||||||
return 'mocked_value';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,3 +0,0 @@
|
|||||||
export function sum(a, b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
import '@testing-library/jest-dom/jest-globals';
|
|
||||||
|
|
||||||
import './mocks/brojs-cli';
|
|
@ -1,7 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { setupStore } from '../store';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
|
|
||||||
export const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
||||||
return <Provider store={setupStore()}>{children}</Provider>;
|
|
||||||
};
|
|
@ -1,38 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { describe, test, expect } from '@jest/globals';
|
|
||||||
import Heading from '../index';
|
|
||||||
import { HeadingVariant } from '../types';
|
|
||||||
|
|
||||||
describe('Heading', () => {
|
|
||||||
test('рендерит children', () => {
|
|
||||||
const children = 'Hello';
|
|
||||||
render(<Heading>{children}</Heading>);
|
|
||||||
|
|
||||||
const headingEl = screen.getByText(children);
|
|
||||||
expect(headingEl).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('может рендерится как h2, h3 и h4', () => {
|
|
||||||
const { rerender } = render(<Heading variant={HeadingVariant.h2}>Hello</Heading>);
|
|
||||||
|
|
||||||
let headingEl = screen.getByRole('heading', {
|
|
||||||
level: 2
|
|
||||||
});
|
|
||||||
expect(headingEl).toBeInTheDocument();
|
|
||||||
|
|
||||||
rerender(<Heading variant={HeadingVariant.h3}>Hello</Heading>);
|
|
||||||
|
|
||||||
headingEl = screen.getByRole('heading', {
|
|
||||||
level: 3
|
|
||||||
});
|
|
||||||
expect(headingEl).toBeInTheDocument();
|
|
||||||
|
|
||||||
rerender(<Heading variant={HeadingVariant.h4}>Hello</Heading>);
|
|
||||||
|
|
||||||
headingEl = screen.getByRole('heading', {
|
|
||||||
level: 4
|
|
||||||
});
|
|
||||||
expect(headingEl).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,35 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { HeadingVariant } from './types';
|
|
||||||
import { css } from '@emotion/react';
|
|
||||||
|
|
||||||
export const HeadingStyled = styled.h1<{ variant?: HeadingVariant }>`
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: blue;
|
|
||||||
width: 100%;
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
font-size: 38px;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
${({ variant }) => {
|
|
||||||
switch (variant) {
|
|
||||||
case HeadingVariant.h2:
|
|
||||||
return css`
|
|
||||||
font-size: 24px;
|
|
||||||
`;
|
|
||||||
case HeadingVariant.h3:
|
|
||||||
return css`
|
|
||||||
font-size: 18px;
|
|
||||||
`;
|
|
||||||
case HeadingVariant.h4:
|
|
||||||
return css`
|
|
||||||
font-size: 14px;
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return css``;
|
|
||||||
}}
|
|
||||||
`;
|
|
@ -1,13 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { HeadingStyled } from './index.style';
|
|
||||||
import { HeadingProps, HeadingVariant } from './types';
|
|
||||||
|
|
||||||
const Heading = ({ children, variant = HeadingVariant.h1, className }: HeadingProps) => {
|
|
||||||
return (
|
|
||||||
<HeadingStyled className={className} as={variant} variant={variant}>
|
|
||||||
{children}
|
|
||||||
</HeadingStyled>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Heading;
|
|
@ -1,14 +0,0 @@
|
|||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
export enum HeadingVariant {
|
|
||||||
h1 = 'h1',
|
|
||||||
h2 = 'h2',
|
|
||||||
h3 = 'h3',
|
|
||||||
h4 = 'h4'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HeadingProps {
|
|
||||||
children?: ReactNode;
|
|
||||||
variant?: HeadingVariant;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
@ -1,11 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Heading from '../../components/heading';
|
|
||||||
import { HeadingVariant } from '../../components/heading/types';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
const DetailPage = (): React.ReactElement => {
|
const DetailPage = (): React.ReactElement => {
|
||||||
const { id } = useParams();
|
return <h1>Detail Page</h1>;
|
||||||
return <Heading variant={HeadingVariant.h2}>Detail Page {id} </Heading>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DetailPage;
|
export default DetailPage;
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { describe, expect, test } from '@jest/globals';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import ListPage from '../index';
|
|
||||||
import { TestWrapper } from '../../../__tests__/test-wrapper';
|
|
||||||
import { mockGetList, spyedGetList } from '../../../__tests__/mocks/api/list/get-list';
|
|
||||||
|
|
||||||
describe('ListPage', () => {
|
|
||||||
test('renders', async () => {
|
|
||||||
const mockedGetList = mockGetList();
|
|
||||||
|
|
||||||
render(<ListPage />, {
|
|
||||||
wrapper: TestWrapper
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(screen.getByText('List Page New')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(await screen.findByText('1: title - description')).toBeInTheDocument();
|
|
||||||
expect(mockedGetList).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Отображается ошибка', async () => {
|
|
||||||
spyedGetList.mockRejectedValueOnce({
|
|
||||||
message: 'В доступе отказано'
|
|
||||||
});
|
|
||||||
|
|
||||||
render(<ListPage />, {
|
|
||||||
wrapper: TestWrapper
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(await screen.findByText('Произошла ошибка')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,24 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Heading from '../../components/heading';
|
|
||||||
import { useGetListQuery } from '../../store/api';
|
|
||||||
|
|
||||||
const ListPage = (): React.ReactElement => {
|
const ListPage = (): React.ReactElement => {
|
||||||
const { data, isLoading, error } = useGetListQuery(undefined);
|
return <h1>List Page New</h1>;
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Heading>List Page New</Heading>
|
|
||||||
{isLoading && <div>Loading...</div>}
|
|
||||||
{error && <div>Произошла ошибка</div>}
|
|
||||||
{data?.map((item) => {
|
|
||||||
return (
|
|
||||||
<div key={item.id}>
|
|
||||||
{item.id}: {item.title} - {item.description}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListPage;
|
export default ListPage;
|
||||||
|
@ -1,35 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getNavigationValue } from '@brojs/cli';
|
import { Link } from 'react-router-dom';
|
||||||
import { HeaderContainer, NavContainer, NavItem, NavLink } from './index.style';
|
import { getNavigationsValue } from '@brojs/cli';
|
||||||
import Logo from './logo/logo';
|
|
||||||
|
|
||||||
const navigations: Array<{ name: string; href: string }> = [
|
const navigations: Array<{ name: string; href: string }> = [
|
||||||
{
|
{
|
||||||
name: 'Главная',
|
name: 'Главная',
|
||||||
href: getNavigationValue('kfu-24-teacher.main')
|
href: getNavigationsValue('kfu-24-teacher.main')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Детальная информация',
|
name: 'Детальная информация',
|
||||||
href: getNavigationValue('kfu-24-teacher.detail')
|
href: getNavigationsValue('kfu-24-teacher.detail')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const Header = (): React.ReactElement => {
|
const Header = (): React.ReactElement => {
|
||||||
return (
|
return (
|
||||||
<HeaderContainer>
|
<header>
|
||||||
<Logo />
|
<ul>
|
||||||
<NavContainer>
|
|
||||||
{navigations.map((item) => {
|
{navigations.map((item) => {
|
||||||
return (
|
return (
|
||||||
<NavItem key={item.name}>
|
<li key={item.name}>
|
||||||
<NavLink to={item.href} end>
|
<Link to={item.href}>{item.name}</Link>
|
||||||
{item.name}
|
</li>
|
||||||
</NavLink>
|
|
||||||
</NavItem>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</NavContainer>
|
</ul>
|
||||||
</HeaderContainer>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { NavLink as NavLinkBase } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const HeaderContainer = styled('header')`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const NavContainer = styled.nav`
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
padding: 12px 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const NavItem = styled.div``;
|
|
||||||
|
|
||||||
export const NavLink = styled(NavLinkBase)`
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-bottom: 2px solid black;
|
|
||||||
}
|
|
||||||
`;
|
|
@ -1,17 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import logoPng from './logo2.png';
|
|
||||||
|
|
||||||
const LogoStyled = styled.img`
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
// ${__webpack_public_path__}/remote-assets/logo.png
|
|
||||||
|
|
||||||
const Logo = () => {
|
|
||||||
return <LogoStyled src={logoPng} alt={'logo'} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Logo;
|
|
Binary file not shown.
Before Width: | Height: | Size: 90 KiB |
@ -1,24 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
import { setupStore } from '../../store';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { useKeycloak } from './keycloak';
|
|
||||||
|
|
||||||
const store = setupStore();
|
const Main = (): React.ReactElement => {
|
||||||
|
return <RouterProvider router={router} />;
|
||||||
const Main = (): React.ReactElement | string => {
|
|
||||||
const { isLoading } = useKeycloak();
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return 'Loading...';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Provider store={store}>
|
|
||||||
<RouterProvider router={router} />
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Main;
|
export default Main;
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import Keycloak from 'keycloak-js';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { setToken } from '../../service/network';
|
|
||||||
|
|
||||||
const keycloak = new Keycloak({
|
|
||||||
url: 'https://kc.bro-js.ru/',
|
|
||||||
realm: 'open',
|
|
||||||
clientId: 'kfu-m-24-1'
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
email: string;
|
|
||||||
email_verified: boolean;
|
|
||||||
family_name: string;
|
|
||||||
given_name: string;
|
|
||||||
name: string;
|
|
||||||
preferred_username: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useKeycloak = () => {
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [user, setUser] = useState();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handle = async () => {
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const authenticated = await keycloak.init({ onLoad: 'login-required' });
|
|
||||||
if (authenticated) {
|
|
||||||
setToken(keycloak.token);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const { sub, ...user } = (await keycloak.loadUserInfo()) as any;
|
|
||||||
console.log(user);
|
|
||||||
setUser({ ...user, id: sub });
|
|
||||||
console.log('User is authenticated');
|
|
||||||
} else {
|
|
||||||
console.log('User is not authenticated');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize adapter:', error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handle();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
user
|
|
||||||
};
|
|
||||||
};
|
|
@ -2,23 +2,22 @@ import React from 'react';
|
|||||||
import { createBrowserRouter } from 'react-router-dom';
|
import { createBrowserRouter } from 'react-router-dom';
|
||||||
import ListPage from '../list';
|
import ListPage from '../list';
|
||||||
import DetailPage from '../detail';
|
import DetailPage from '../detail';
|
||||||
import { getNavigationValue } from '@brojs/cli';
|
import { getNavigationsValue } from '@brojs/cli';
|
||||||
import Layout from './components/layout';
|
import Layout from './components/layout';
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: getNavigationValue('kfu-24-teacher.main'),
|
path: getNavigationsValue('kfu-24-teacher.main'),
|
||||||
element: <Layout />,
|
element: <Layout />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: getNavigationValue('kfu-24-teacher.main'),
|
path: getNavigationsValue('kfu-24-teacher.main'),
|
||||||
element: <ListPage />
|
element: <ListPage />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: getNavigationValue('kfu-24-teacher.detail'),
|
path: getNavigationsValue('kfu-24-teacher.detail'),
|
||||||
element: <DetailPage />
|
element: <DetailPage />
|
||||||
},
|
}
|
||||||
{ path: '*', element: <h1>404</h1> }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { network } from '../network';
|
|
||||||
import { GetListResponse } from './types';
|
|
||||||
|
|
||||||
class ListService {
|
|
||||||
async getList() {
|
|
||||||
const res = await network.get<GetListResponse>('/list');
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const listService = new ListService();
|
|
@ -1,7 +0,0 @@
|
|||||||
export interface ListItem {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetListResponse = Array<ListItem>;
|
|
@ -1,10 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import { getConfigValue } from '@brojs/cli';
|
|
||||||
|
|
||||||
const baseUrl = getConfigValue('kfu-24-teacher.api');
|
|
||||||
|
|
||||||
export const network = axios.create({ baseURL: baseUrl });
|
|
||||||
|
|
||||||
export const setToken = (token: string) => {
|
|
||||||
network.defaults.headers.authorization = `Bearer ${token}`;
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
// Need to use the React-specific entry point to import createApi
|
|
||||||
import { createApi, fetchBaseQuery, QueryReturnValue } from '@reduxjs/toolkit/query/react';
|
|
||||||
import { GetListResponse } from '../service/list/types';
|
|
||||||
import { listService } from '../service/list';
|
|
||||||
|
|
||||||
const createQueryFromPromise =
|
|
||||||
<ARGS, RES>(fn: (...args: Array<ARGS>) => Promise<RES>) =>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
async (...args): Promise<QueryReturnValue<RES, any, any>> => {
|
|
||||||
try {
|
|
||||||
const data = await fn(...args);
|
|
||||||
return { data };
|
|
||||||
} catch (e: unknown) {
|
|
||||||
return { error: e };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define a service using a base URL and expected endpoints
|
|
||||||
export const api = createApi({
|
|
||||||
reducerPath: 'api',
|
|
||||||
baseQuery: fetchBaseQuery({ baseUrl: '' }),
|
|
||||||
endpoints: (builder) => ({
|
|
||||||
getList: builder.query<GetListResponse, undefined>({
|
|
||||||
queryFn: createQueryFromPromise(() => listService.getList())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { useGetListQuery } = api;
|
|
@ -1,24 +0,0 @@
|
|||||||
import { combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
||||||
import { api } from './api';
|
|
||||||
import { setupListeners } from '@reduxjs/toolkit/query';
|
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
|
||||||
[api.reducerPath]: api.reducer
|
|
||||||
});
|
|
||||||
|
|
||||||
export function setupStore(preloadedState?: Partial<RootState>) {
|
|
||||||
const store = configureStore({
|
|
||||||
reducer: rootReducer,
|
|
||||||
preloadedState: preloadedState,
|
|
||||||
// Adding the api middleware enables caching, invalidation, polling,
|
|
||||||
// and other useful features of `rtk-query`.
|
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware)
|
|
||||||
});
|
|
||||||
setupListeners(store.dispatch);
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
|
||||||
export type AppStore = ReturnType<typeof setupStore>;
|
|
||||||
export type AppDispatch = AppStore['dispatch'];
|
|
@ -1,14 +1,3 @@
|
|||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const listRouter = require('./list');
|
|
||||||
const keycloak = require('./keycloak');
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
const delay =
|
|
||||||
(ms = 1000) =>
|
|
||||||
(req, res, next) => {
|
|
||||||
setTimeout(next, ms);
|
|
||||||
};
|
|
||||||
|
|
||||||
router.use(keycloak.middleware());
|
|
||||||
router.use(delay());
|
|
||||||
router.use('/list', listRouter);
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
const Keycloak = require('keycloak-connect');
|
|
||||||
|
|
||||||
const kcConfig = {
|
|
||||||
clientId: 'kfu-m-24-1',
|
|
||||||
bearerOnly: true,
|
|
||||||
serverUrl: 'https://kc.bro-js.ru/',
|
|
||||||
realm: 'open'
|
|
||||||
};
|
|
||||||
|
|
||||||
const keycloak = new Keycloak({}, kcConfig);
|
|
||||||
|
|
||||||
module.exports = keycloak;
|
|
@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
{ "id": 1, "title": "title", "description": "description" },
|
|
||||||
{ "id": 2, "title": "title", "description": "description" },
|
|
||||||
{ "id": 3, "title": "title", "description": "description" },
|
|
||||||
{ "id": 4, "title": "title", "description": "description" },
|
|
||||||
{ "id": 5, "title": "title", "description": "description" }
|
|
||||||
]
|
|
@ -1,13 +0,0 @@
|
|||||||
const router = require('express').Router();
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
|
|
||||||
const data = require('./data/list.json');
|
|
||||||
const keycloak = require('../keycloak');
|
|
||||||
|
|
||||||
router.get('/', keycloak.protect(), (req, res) => {
|
|
||||||
res.send(data);
|
|
||||||
// res.status(500).send({
|
|
||||||
// message: 'Internal server error'
|
|
||||||
// });
|
|
||||||
});
|
|
@ -5,25 +5,21 @@
|
|||||||
"es2017"
|
"es2017"
|
||||||
],
|
],
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
"skipLibCheck": true,
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "node",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"typeRoots": ["node_modules/@types", "@types"],
|
"typeRoots": ["node_modules/@types", "src/typings"],
|
||||||
|
"types" : ["webpack-env", "node"],
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": [
|
|
||||||
"src",
|
|
||||||
"@types/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
// "**/*.test.ts",
|
"**/*.test.ts",
|
||||||
// "**/*.test.tsx",
|
"**/*.test.tsx",
|
||||||
"node_modules/@types/jest"
|
"node_modules/@types/jest"
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user