init
This commit is contained in:
parent
db6c735fdc
commit
5134d44e39
3692
package-lock.json
generated
3692
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@ -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
48
src/__data__/api/api.ts
Normal 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 }
|
||||
// })
|
||||
// })
|
||||
}),
|
||||
});
|
@ -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
49
src/__data__/model.ts
Normal 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;
|
||||
}
|
10
src/__data__/slices/user.ts
Normal file
10
src/__data__/slices/user.ts
Normal 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
16
src/__data__/store.ts
Normal 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']>;
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 = () => (
|
||||
<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>
|
||||
)
|
||||
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>
|
||||
);
|
||||
|
@ -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);
|
||||
keycloak.login();
|
||||
console.error("Failed to initialize adapter:", error);
|
||||
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) {
|
||||
module.hot.accept('./app', ()=> {
|
||||
ReactDom.render(
|
||||
<App/>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
})
|
||||
}
|
||||
if (module.hot) {
|
||||
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"));
|
||||
};
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
11
stubs/mocks/lessons/create/success.json
Normal file
11
stubs/mocks/lessons/create/success.json
Normal 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
|
||||
}
|
||||
}
|
11
stubs/mocks/lessons/id/success.json
Normal file
11
stubs/mocks/lessons/id/success.json
Normal 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
|
||||
}
|
||||
}
|
13
stubs/mocks/lessons/list/success.json
Normal file
13
stubs/mocks/lessons/list/success.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user