This commit is contained in:
Primakov Alexandr Alexandrovich 2024-02-28 23:43:36 +03:00
parent db6c735fdc
commit 5134d44e39
19 changed files with 2328 additions and 1793 deletions

3684
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,9 @@
"description": "", "description": "",
"main": "./src/index.tsx", "main": "./src/index.tsx",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "start": "ijl-cli server --port=8099 --with-open-browser",
"start": "ijl-cli --server --port=8099 --with-open-browser", "build": "npm run clean && ijl-cli build --dev",
"build": "npm run clean && ijl-cli --build --dev", "build:prod": "npm run clean && ijl-cli build",
"build:prod": "npm run clean && ijl-cli --build",
"clean": "rimraf dist" "clean": "rimraf dist"
}, },
"repository": { "repository": {
@ -18,23 +17,26 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@ijl/cli": "^4.1.6" "@ijl/cli": "^5.0.3"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.11.0",
"@types/react": "^17.0.52", "@reduxjs/toolkit": "^2.2.1",
"@types/react-dom": "^17.0.18", "@types/react": "^18.2.60",
"dayjs": "^1.11.7", "@types/react-dom": "^18.2.19",
"dayjs": "^1.11.10",
"express": "^4.18.2", "express": "^4.18.2",
"keycloak-js": "^23.0.6", "js-sha256": "^0.11.0",
"keycloak-js": "^23.0.7",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"qrcode": "^1.5.1", "qrcode": "^1.5.3",
"react": "^17.0.2", "react": "^18.2.0",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-router-dom": "^6.10.0", "react-redux": "^9.1.0",
"socket.io-client": "^4.5.4", "react-router-dom": "^6.22.1",
"typescript": "^4.9.3" "redux": "^5.0.1",
"typescript": "^5.3.3"
} }
} }

48
src/__data__/api/api.ts Normal file
View File

@ -0,0 +1,48 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { getConfigValue } from "@ijl/cli";
import { keycloak } from "../kc";
export const api = createApi({
reducerPath: "auth",
baseQuery: fetchBaseQuery({
baseUrl: getConfigValue("journal.back.url"),
headers: {
Authorization: `Bearer ${keycloak.token}`,
"Content-Type": "application/json;charset=utf-8",
},
}),
endpoints: (builder) => ({
lessonList: builder.query({
query: () => '/lesson/list'
})
// signIn: builder.mutation<SignInResponce, SignInRequestBody>({
// query: ({ login, password }) => ({
// url: URLs.queryApi.login,
// method: 'POST',
// body: { login, password },
// }),
// }),
// recoverPassword: builder.mutation<{ error?: string }, { email: string }>({
// query: ({ email }) => ({
// url: URLs.queryApi.revoverPassword,
// method: 'POST',
// body: { email }
// })
// }),
// recoverPasswordConfirm: builder.mutation<{ error?: string }, { code: string }>({
// query: ({ code }) => ({
// url: URLs.queryApi.revoverPasswordConfirm,
// method: 'POST',
// body: { code }
// })
// }),
// recoverPasswordNewPassword: builder.mutation<{ error?: string }, { newPassword: string }>({
// query: ({ newPassword }) => ({
// url: URLs.queryApi.revoverPasswordNew,
// method: 'POST',
// body: { newPassword }
// })
// })
}),
});

View File

@ -3,7 +3,7 @@ import Keycloak from 'keycloak-js';
export const keycloak = new Keycloak({ export const keycloak = new Keycloak({
url: 'https://kc.inno-js.ru', url: 'https://kc.inno-js.ru',
realm: 'inno-js', realm: 'inno-js',
clientId: 'jurnal' clientId: 'journal'
}); });
window.keycloak = keycloak; window.keycloak = keycloak;

49
src/__data__/model.ts Normal file
View File

@ -0,0 +1,49 @@
interface TokenData {
exp: number;
iat: number;
auth_time: number;
jti: string;
iss: string;
aud: string[];
sub: string;
typ: string;
azp: string;
nonce: string;
session_state: string;
acr: string;
'allowed-origins': string[];
realm_access: Realmaccess;
resource_access: Resourceaccess;
scope: string;
sid: string;
email_verified: boolean;
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
}
interface Resourceaccess {
'realm-management': Realmaccess;
jurnal: Realmaccess;
broker: Realmaccess;
account: Realmaccess;
'microfrontend-admin': Realmaccess
}
interface Realmaccess {
roles: string[];
}
export interface UserData extends TokenData {
sub: string;
gravatar: string;
email_verified: boolean;
attributes: Record<string, string[]>
name: string;
preferred_username: string;
given_name: string;
family_name: string;
email: string;
}

View File

@ -0,0 +1,10 @@
import { createSlice } from '@reduxjs/toolkit';
import { UserData } from '../model';
export const userSlice = createSlice({
name: 'user',
initialState: null as UserData,
reducers: {
}
})

16
src/__data__/store.ts Normal file
View File

@ -0,0 +1,16 @@
import { configureStore } from '@reduxjs/toolkit';
import { api } from './api/api';
import { userSlice } from './slices/user';
export const createStore= (preloadedState = {}) => configureStore({
preloadedState,
reducer: {
[api.reducerPath]: api.reducer,
[userSlice.name]: userSlice.reducer
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(api.middleware),
});
export type Store = ReturnType<ReturnType<typeof createStore>['getState']>;

View File

@ -9,7 +9,7 @@ import { Dashboard } from './dashboard';
dayjs.locale('ru', ruLocale); dayjs.locale('ru', ruLocale);
const App = () => { const App = ({ store }) => {
return( return(
<BrowserRouter> <BrowserRouter>
<Helmet> <Helmet>
@ -64,7 +64,7 @@ const App = () => {
} }
`} `}
/> />
<Dashboard /> <Dashboard store={store} />
</BrowserRouter> </BrowserRouter>
) )
} }

View File

@ -1,13 +1,10 @@
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { import { Routes, Route, useNavigate } from "react-router-dom";
Routes, import { Provider } from "react-redux";
Route,
useNavigate
} from 'react-router-dom';
import { MainPage } from './pages/main'; import { MainPage } from "./pages/main";
import { Lesson } from './pages/Lesson'; import { Lesson } from "./pages/Lesson";
import { UserPage } from './pages/UserPage'; import { UserPage } from "./pages/UserPage";
const Redirect = ({ path }) => { const Redirect = ({ path }) => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -17,13 +14,15 @@ const Redirect = ({ path }) => {
}, []); }, []);
return null; return null;
} };
export const Dashboard = () => ( export const Dashboard = ({ store }) => (
<Provider store={store}>
<Routes> <Routes>
<Route path="/journal" element={<Redirect path="/journal/main" />} /> <Route path="/journal" element={<Redirect path="/journal/main" />} />
<Route path="/journal/main" element={<MainPage />} /> <Route path="/journal/main" element={<MainPage />} />
<Route path="/journal/u/:lessonId" element={<UserPage />} /> <Route path="/journal/u/:lessonId" element={<UserPage />} />
<Route path="/journal/l/:lessonId" element={<Lesson />} /> <Route path="/journal/l/:lessonId" element={<Lesson />} />
</Routes> </Routes>
) </Provider>
);

View File

@ -1,42 +1,32 @@
import React from 'react'; import React from "react";
import ReactDom from 'react-dom'; import ReactDom from "react-dom";
import App from './app'; import App from "./app";
import { keycloak } from './__data__/const'; import { keycloak } from "./__data__/kc";
import { createStore } from "./__data__/store";
export default () => <App/>; export default (props) => <App {...props} />;
const start = async () => { export const mount = async (Сomponent) => {
// onLoad: 'check-sso', let user = null;
// silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.html`
try { try {
const authenticated = await keycloak.init({ onLoad: 'check-sso' }) // 'login-required' }); await keycloak.init({ onLoad: "login-required" }); // 'login-required' });
console.log(`User is ${authenticated ? 'authenticated' : 'not authenticated'}`); user = { ...(await keycloak.loadUserInfo()), ...keycloak.tokenParsed };
} catch (error) { } catch (error) {
console.error('Failed to initialize adapter:', error); console.error("Failed to initialize adapter:", error);
keycloak.login(); keycloak.login();
} }
} const store = createStore({ user });
start(); ReactDom.render(<Сomponent store={store} />, document.getElementById("app"));
export const mount = (Сomponent) => {
ReactDom.render(
<Сomponent/>,
document.getElementById('app')
);
if (module.hot) { if (module.hot) {
module.hot.accept('./app', ()=> { module.hot.accept("./app", () => {
ReactDom.render( ReactDom.render(<App store={store} />, document.getElementById("app"));
<App/>, });
document.getElementById('app')
);
})
} }
}; };
export const unmount = () => { export const unmount = () => {
ReactDom.unmountComponentAtNode(document.getElementById('app')); ReactDom.unmountComponentAtNode(document.getElementById("app"));
}; };

View File

@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { getConfigValue } from "@ijl/cli";
import { import {
ArrowImg, ArrowImg,
@ -14,20 +15,12 @@ import {
} from "./style"; } from "./style";
import arrow from "../assets/36-arrow-right.svg"; import arrow from "../assets/36-arrow-right.svg";
import { connect, getSocket } from "../socket"; import { keycloak } from "../__data__/kc";
import { getConfigValue } from "@ijl/cli";
import { keycloak } from "../__data__/const";
export const Journal = () => { export const Journal = () => {
const [lessons, setLessons] = useState(null); const [lessons, setLessons] = useState(null);
useEffect(() => { useEffect(() => {
connect();
const socket = getSocket();
socket.on("lessons", (data) => {
setLessons(data);
});
const check = async () => { const check = async () => {
if (keycloak.authenticated) { if (keycloak.authenticated) {
keycloak; keycloak;
@ -79,8 +72,8 @@ export const Journal = () => {
(event) => { (event) => {
event.preventDefault(); event.preventDefault();
const socket = getSocket(); // const socket = getSocket();
socket.emit("create-lesson", { lessonName: value }); // socket.emit("create-lesson", { lessonName: value });
setValue(""); setValue("");
}, },
[value] [value]

View File

@ -3,7 +3,6 @@ import { useParams } from 'react-router-dom';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import { connect, getSocket } from '../socket';
import { MainWrapper, StartWrapper, QRCanvas, LessonItem, Lessonname } from './style'; import { MainWrapper, StartWrapper, QRCanvas, LessonItem, Lessonname } from './style';
export const Lesson = () => { export const Lesson = () => {
@ -11,11 +10,9 @@ export const Lesson = () => {
const canvRef = useRef(null); const canvRef = useRef(null);
const [lesson, setLesson] = useState(null); const [lesson, setLesson] = useState(null);
useEffect(() => { useEffect(() => {
connect(); // socket.on('lessons', data => {
const socket = getSocket(); // setLesson(data.find(lesson => lesson.id === lessonId));
socket.on('lessons', data => { // })
setLesson(data.find(lesson => lesson.id === lessonId));
})
QRCode.toCanvas(canvRef.current, `${location.origin}/journal/u/${lessonId}` , function (error) { QRCode.toCanvas(canvRef.current, `${location.origin}/journal/u/${lessonId}` , function (error) {
if (error) console.error(error) if (error) console.error(error)

View File

@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { connect, getSocket } from '../socket';
import { ArrowImg, IconButton, InputElement, InputLabel, InputWrapper, MainWrapper, StartWrapper } from './style'; import { ArrowImg, IconButton, InputElement, InputLabel, InputWrapper, MainWrapper, StartWrapper } from './style';
import arrow from '../assets/36-arrow-right.svg'; import arrow from '../assets/36-arrow-right.svg';
@ -10,26 +9,24 @@ export const UserPage = () => {
const [error, setError] = useState(null); const [error, setError] = useState(null);
const { lessonId } = useParams(); const { lessonId } = useParams();
useEffect(() => { useEffect(() => {
connect(); // socket.on('connect', () => {
const socket = getSocket(); // const id = localStorage.getItem('socketId');
socket.on('connect', () => { // if (!id) {
const id = localStorage.getItem('socketId'); // localStorage.setItem('socketId', socket.id);
if (!id) { // setSocketId(socket.id);
localStorage.setItem('socketId', socket.id); // } else {
setSocketId(socket.id); // setSocketId(id);
} else { // }
setSocketId(id);
}
const name = localStorage.getItem('name'); // const name = localStorage.getItem('name');
if (name) { // if (name) {
const socket = getSocket(); // const socket = getSocket();
socket.emit('add', { socketId: id || socket.id, name, lessonid: lessonId }); // socket.emit('add', { socketId: id || socket.id, name, lessonid: lessonId });
} // }
}) // })
socket.on('error', data => { // socket.on('error', data => {
setError(data) // setError(data)
}) // })
}, []); }, []);
const [value, setValue] = useState(localStorage.getItem('name') || ''); const [value, setValue] = useState(localStorage.getItem('name') || '');
@ -40,9 +37,8 @@ export const UserPage = () => {
const handleSubmit = useCallback((event) => { const handleSubmit = useCallback((event) => {
event.preventDefault(); event.preventDefault();
const socket = getSocket();
localStorage.setItem('name', value) localStorage.setItem('name', value)
socket.emit('add', { socketId: localStorage.getItem('socketId') || socketId, name: value, lessonid: lessonId }); // socket.emit('add', { socketId: localStorage.getItem('socketId') || socketId, name: value, lessonid: lessonId });
}, [value]) }, [value])
return ( return (

View File

@ -4,69 +4,12 @@ import arrow from '../assets/36-arrow-right.svg';
import { import {
MainWrapper, MainWrapper,
InputElement,
InputLabel,
InputWrapper,
ArrowImg,
IconButton,
} from './style'; } from './style';
import { Journal } from './Journal'; import { Journal } from './Journal';
const Input = ({ onStart }) => {
const [value, setValue] = useState('');
const [inFocuse, setInfocuse] = useState(false);
const handleChange = useCallback(event => {
setValue(event.target.value.toUpperCase())
}, [setValue]);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const pass = localStorage.getItem('pass')
if (pass) {
onStart();
}
}, []);
const handleSubmit = useCallback((event) => {
event.preventDefault();
localStorage.setItem('pass', 'true')
if (value === 'OYB0Y') {
onStart()
}
}, [value])
return (
<>
<form onSubmit={handleSubmit}>
<InputWrapper>
<InputLabel
htmlFor='input'
>
Введите пароль:
</InputLabel>
<InputElement
value={value}
onChange={handleChange}
onFocus={() => setInfocuse(true)}
ref={inputRef}
id="input"
type="password"
autoComplete="off"
/>
<IconButton type="submit">
<ArrowImg src={arrow} />
</IconButton>
</InputWrapper>
</form>
</>
)
}
export const MainPage = () => { export const MainPage = () => {
return ( return (
<MainWrapper> <MainWrapper>
{/* {!shoList && <Input onStart={() => setShoList(true)} />} */}
<Journal /> <Journal />
</MainWrapper> </MainWrapper>
); );

View File

@ -1,17 +0,0 @@
import { getConfigValue } from "@ijl/cli";
import { io } from "socket.io-client";
let socket = null;
export const getSocket = () => socket;
export const connect = () => {
socket = io(getConfigValue('journal.socket.url') + '/lessons', { path: getConfigValue('journal.socket.path')});
socket.on("connect", () => {
console.log('Socket connected', socket.id)
})
}

View File

@ -4,4 +4,8 @@ router.get('/check', function (req, res){
res.send({ ok: true }) res.send({ ok: true })
}) })
router.get('/lesson/list', (req, res) => {
res.send(require('../mocks/lessons/list/success.json'))
})
module.exports = router; module.exports = router;

View File

@ -0,0 +1,11 @@
{
"success": true,
"body": {
"name": "Проверочное занятие",
"students": [],
"_id": "65df996c584b172772d69706",
"date": "2024-02-28T20:37:00.057Z",
"created": "2024-02-28T20:37:00.057Z",
"__v": 0
}
}

View File

@ -0,0 +1,11 @@
{
"success": true,
"body": {
"_id": "65df996c584b172772d69706",
"name": "Проверочное занятие",
"students": [],
"date": "2024-02-28T20:37:00.057Z",
"created": "2024-02-28T20:37:00.057Z",
"__v": 0
}
}

View File

@ -0,0 +1,13 @@
{
"success": true,
"body": [
{
"_id": "65df996c584b172772d69706",
"name": "Проверочное занятие",
"students": [],
"date": "2024-02-28T20:37:00.057Z",
"created": "2024-02-28T20:37:00.057Z",
"__v": 0
}
]
}