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": "",
"main": "./src/index.tsx",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "ijl-cli --server --port=8099 --with-open-browser",
"build": "npm run clean && ijl-cli --build --dev",
"build:prod": "npm run clean && ijl-cli --build",
"start": "ijl-cli server --port=8099 --with-open-browser",
"build": "npm run clean && ijl-cli build --dev",
"build:prod": "npm run clean && ijl-cli build",
"clean": "rimraf dist"
},
"repository": {
@ -18,23 +17,26 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@ijl/cli": "^4.1.6"
"@ijl/cli": "^5.0.3"
},
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@types/react": "^17.0.52",
"@types/react-dom": "^17.0.18",
"dayjs": "^1.11.7",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@reduxjs/toolkit": "^2.2.1",
"@types/react": "^18.2.60",
"@types/react-dom": "^18.2.19",
"dayjs": "^1.11.10",
"express": "^4.18.2",
"keycloak-js": "^23.0.6",
"js-sha256": "^0.11.0",
"keycloak-js": "^23.0.7",
"prettier": "^3.2.5",
"qrcode": "^1.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-router-dom": "^6.10.0",
"socket.io-client": "^4.5.4",
"typescript": "^4.9.3"
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"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({
url: 'https://kc.inno-js.ru',
realm: 'inno-js',
clientId: 'jurnal'
clientId: 'journal'
});
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);
const App = () => {
const App = ({ store }) => {
return(
<BrowserRouter>
<Helmet>
@ -64,7 +64,7 @@ const App = () => {
}
`}
/>
<Dashboard />
<Dashboard store={store} />
</BrowserRouter>
)
}

View File

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

View File

@ -1,42 +1,32 @@
import React from 'react';
import ReactDom from 'react-dom';
import React from "react";
import ReactDom from "react-dom";
import App from './app';
import { keycloak } from './__data__/const';
import App from "./app";
import { keycloak } from "./__data__/kc";
import { createStore } from "./__data__/store";
export default () => <App/>;
export default (props) => <App {...props} />;
const start = async () => {
// onLoad: 'check-sso',
// silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.html`
export const mount = async (Сomponent) => {
let user = null;
try {
const authenticated = await keycloak.init({ onLoad: 'check-sso' }) // 'login-required' });
console.log(`User is ${authenticated ? 'authenticated' : 'not authenticated'}`);
await keycloak.init({ onLoad: "login-required" }); // 'login-required' });
user = { ...(await keycloak.loadUserInfo()), ...keycloak.tokenParsed };
} catch (error) {
console.error('Failed to initialize adapter:', error);
console.error("Failed to initialize adapter:", error);
keycloak.login();
}
}
const store = createStore({ user });
start();
export const mount = (Сomponent) => {
ReactDom.render(
<Сomponent/>,
document.getElementById('app')
);
ReactDom.render(<Сomponent store={store} />, document.getElementById("app"));
if (module.hot) {
module.hot.accept('./app', ()=> {
ReactDom.render(
<App/>,
document.getElementById('app')
);
})
module.hot.accept("./app", () => {
ReactDom.render(<App store={store} />, document.getElementById("app"));
});
}
};
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 dayjs from "dayjs";
import { Link } from "react-router-dom";
import { getConfigValue } from "@ijl/cli";
import {
ArrowImg,
@ -14,20 +15,12 @@ import {
} from "./style";
import arrow from "../assets/36-arrow-right.svg";
import { connect, getSocket } from "../socket";
import { getConfigValue } from "@ijl/cli";
import { keycloak } from "../__data__/const";
import { keycloak } from "../__data__/kc";
export const Journal = () => {
const [lessons, setLessons] = useState(null);
useEffect(() => {
connect();
const socket = getSocket();
socket.on("lessons", (data) => {
setLessons(data);
});
const check = async () => {
if (keycloak.authenticated) {
keycloak;
@ -79,8 +72,8 @@ export const Journal = () => {
(event) => {
event.preventDefault();
const socket = getSocket();
socket.emit("create-lesson", { lessonName: value });
// const socket = getSocket();
// socket.emit("create-lesson", { lessonName: value });
setValue("");
},
[value]

View File

@ -3,7 +3,6 @@ import { useParams } from 'react-router-dom';
import dayjs from 'dayjs';
import QRCode from 'qrcode';
import { connect, getSocket } from '../socket';
import { MainWrapper, StartWrapper, QRCanvas, LessonItem, Lessonname } from './style';
export const Lesson = () => {
@ -11,11 +10,9 @@ export const Lesson = () => {
const canvRef = useRef(null);
const [lesson, setLesson] = useState(null);
useEffect(() => {
connect();
const socket = getSocket();
socket.on('lessons', data => {
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) {
if (error) console.error(error)

View File

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

View File

@ -4,69 +4,12 @@ import arrow from '../assets/36-arrow-right.svg';
import {
MainWrapper,
InputElement,
InputLabel,
InputWrapper,
ArrowImg,
IconButton,
} from './style';
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 = () => {
return (
<MainWrapper>
{/* {!shoList && <Input onStart={() => setShoList(true)} />} */}
<Journal />
</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 })
})
router.get('/lesson/list', (req, res) => {
res.send(require('../mocks/lessons/list/success.json'))
})
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
}
]
}