- initial;

This commit is contained in:
Vitalii Kiiko
2024-08-09 08:51:20 +02:00
commit 987b1b0110
46 changed files with 30261 additions and 0 deletions

30
src/App.js Normal file
View File

@@ -0,0 +1,30 @@
import { useState, useEffect } from 'react';
import { BrowserRouter } from 'react-router-dom';
import Routes from './routes';
import { createI18n, setLocaleData } from '@wordpress/i18n';
import { I18nProvider } from '@wordpress/react-i18n';
const i18n = createI18n();
function App() {
const [messages, setMessages] = useState({});
useEffect(() => {
/*import('../languages/en_US.json').then((translations) => {
setLocaleData(translations, 'gepafin');
});*/
fetch('/languages/en_US.json')
.then((res) => res.json())
.then(res => console.log(res))
}, []);
return (
<I18nProvider i18n={i18n}>
<BrowserRouter>
<Routes/>
</BrowserRouter>
</I18nProvider>
);
}
export default App;

View File

@@ -0,0 +1,3 @@
body {
background-color: moccasin;
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
// tools
import AuthenticationService from '../../service/authentication-service';
const ProtectedRoute = () => {
if (!AuthenticationService.wasLoggedIn()) {
return (<Navigate to={'/login'} replace/>);
}
if (AuthenticationService.isExpired()) {
return (<Navigate to={'/login?redirectReason=expired'} replace/>);
}
if (!AuthenticationService.isLoggedIn()) {
return (<Navigate to={'/login?redirectReason=auth_required'} replace/>);
}
if (window.location.pathname === '/') {
return (<Navigate to={'/pages/dashboard'} replace/>);
}
return <Outlet/>;
}
export default ProtectedRoute;

1
src/index.css Normal file
View File

@@ -0,0 +1 @@
@import './assets/scss/theme.scss';

15
src/index.js Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
const rootEl = document.getElementById('root');
const rootReact = createRoot(rootEl);
rootReact.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);

View File

@@ -0,0 +1,14 @@
import React from 'react';
const DefaultLayout = ({ children }) => {
return(
<div>
<div>
Top bar
</div>
{children}
</div>
)
}
export default DefaultLayout;

View File

@@ -0,0 +1,11 @@
import React from 'react';
const Dashboard = () => {
return(
<div>
Dashboard - main data
</div>
)
}
export default Dashboard;

12
src/pages/Login/index.js Normal file
View File

@@ -0,0 +1,12 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
const Login = () => {
return(
<div>
{__('Login page', 'gepafin')}
</div>
)
}
export default Login;

View File

@@ -0,0 +1,11 @@
import React from 'react';
const PageNotFound = () => {
return(
<div>
Page not found
</div>
)
}
export default PageNotFound;

20
src/routes.js Normal file
View File

@@ -0,0 +1,20 @@
import { Route, Routes } from 'react-router-dom';
// components
import PageNotFound from './pages/PageNotFound';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';
import Dashboard from './pages/Dashboard';
import DefaultLayout from './layouts/DefaultLayout';
const routes = () => (
<Routes>
<Route element={<ProtectedRoute/>}>
<Route path="/" element={<DefaultLayout><Dashboard/></DefaultLayout>}/>
</Route>
<Route exact path="/login" element={<Login/>}/>
{/*<Route exact path="/forgot-password" element={<ForgotPassword/>}/>*/}
<Route path="*" element={<PageNotFound/>}/>
</Routes>);
export default routes;

View File

@@ -0,0 +1,79 @@
import { NetworkService } from './network-service';
import { jwtDecode } from 'jwt-decode';
// store
import { storeGet } from '../store';
const API_BASE_URL = process.env.REACT_APP_API_EXECUTION_ADDRESS;
export default class AuthenticationService {
static wasLoggedIn = () => {
const token = storeGet.main.getToken();
return token ?? false;
};
static isExpired = () => {
const token = storeGet.main.getToken();
if (!token) {
return false;
}
let decoded = jwtDecode(token);
if (!decoded) {
return false;
}
let currentTs = new Date().getTime();
if (currentTs >= (decoded.exp * 1000)) {
return true; //FIXME: try refresh
}
return false;
};
static isLoggedIn = () => {
const token = storeGet.main.getToken();
if (!token) {
return false;
}
let decoded = jwtDecode(token);
if (!decoded) {
return false;
}
let currentTs = new Date().getTime();
if (currentTs >= decoded.exp * 1000) {
return false; //FIXME: try refresh
}
return true;
};
static login = (loginRequest, callback, errCallback) => {
NetworkService.unauthorizedPost(`${API_BASE_URL}/user/login/`, loginRequest, callback, errCallback);
};
static registerUser = (registerRequest, callback, errCallback) => {
NetworkService.post(`${API_BASE_URL}/user/register/`, registerRequest, callback, errCallback);
};
static forgotPassword = (request, callback, errCallback) => {
NetworkService.unauthorizedPost(`${API_BASE_URL}/user/reset_password_request/?email=` + request, {}, callback, errCallback);
}
static checkTokenForgotPassword = (request, callback, errCallback) => {
NetworkService.unauthorizedGet(`${API_BASE_URL}/user/reset_token_check/?token=` + request, {}, callback, errCallback);
}
static changePassword = (request, callback, errCallback) => {
NetworkService.unauthorizedPatch(`${API_BASE_URL}/user/reset_password/`, request, callback, errCallback);
}
}

View File

@@ -0,0 +1,374 @@
import { storeGet } from '../store';
export class NetworkService {
static TOKEN_KEY
static REFRESH_TOKEN_KEY
static postEmptyResponse = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': storeGet.main.getToken(),
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.status)
else
callback()
})
.catch(err => errorCallback(err));
};
static putEmptyResponse = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'PUT',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': storeGet.main.getToken(),
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.status)
else
callback()
})
.catch(err => errorCallback(err));
};
static post = (url, body, callback, errorCallback, queryParams) => {
if (queryParams) {
url += '?'
for (let i = 0; i < queryParams.length; i++) {
if (queryParams[i] && this.isNotBlank(queryParams[i][0]) && this.isNotBlank(queryParams[i][1])) {
let param = queryParams[i][0] + '=' + queryParams[i][1]
if (i !== queryParams.length - 1)
param += '&'
url += param;
}
}
if (url.charAt(url.length) === '&')
url = url.substring(0, url.length - 1);
}
fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + storeGet.main.getToken(),
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static unauthorizedPost = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static patch = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'PATCH',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static put = (url, body, callback, errorCallback, queryParams = null) => {
if (queryParams) {
url += '?'
for (let i = 0; i < queryParams.length; i++) {
if (queryParams[i] && this.isNotBlank(queryParams[i][0]) && this.isNotBlank(queryParams[i][1])) {
let param = queryParams[i][0] + '=' + queryParams[i][1]
if (i !== queryParams.length - 1)
param += '&'
url += param;
}
}
if (url.charAt(url.length) === '&')
url = url.substring(0, url.length - 1);
}
fetch(url, {
method: 'PUT',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + storeGet.main.getToken(),
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static unauthorizedPostEmptyResponse = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
})
.then(data => callback(data))
.catch(err => errorCallback(err));
};
static unauthorizedPutEmptyResponse = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(body)
})
.then(data => callback(data))
.catch(err => errorCallback(err));
};
static unauthorizedGet = (url, queryParams, callback, errorCallback) => {
fetch(url, {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
}
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static unauthorizedPatch = (url, body, callback, errorCallback) => {
fetch(url, {
method: 'PATCH',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
static isNotBlank(value) {
return value !== null && value !== undefined && value !== ''
}
static get = (url, callback, errorCallback, queryParams = null) => {
if (queryParams) {
url += '?'
for (let i = 0; i < queryParams.length; i++) {
if (queryParams[i] && this.isNotBlank(queryParams[i][0]) && this.isNotBlank(queryParams[i][1])) {
let param = queryParams[i][0] + '=' + queryParams[i][1]
if (i !== queryParams.length - 1)
param += '&'
url += param;
}
}
if (url.charAt(url.length) === '&')
url = url.substring(0, url.length - 1);
}
fetch(url, {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + storeGet.main.getToken(),
}
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599) {
errorCallback(data.response)
} else {
callback(data.response)
}
})
.catch(err => errorCallback(err));
};
static promiseGet = async (url, queryParams = null) => {
const response = await fetch(url, {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + storeGet.main.getToken(),
'Access-Control-Allow-Origin': '*'
}
});
const json = await response.json();
return json;
}
static deleteEmptyResponse = (url, callback, errorCallback, queryParams = null) => {
if (queryParams) {
let params = '?'
for (let i = 0; i < queryParams.length; i++) {
params += queryParams[i][0] + '=' + queryParams[i][1]
if (queryParams.length !== i + 1)
params += '&'
url += params
params = ''
}
}
fetch(url, {
method: 'DELETE',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': storeGet.main.getToken(),
'Access-Control-Allow-Origin': '*'
}
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.status)
else
callback()
})
.catch(err => errorCallback(err));
}
static delete = (url, body, callback, errorCallback, queryParams = null) => {
if (queryParams) {
let params = '?'
for (let i = 0; i < queryParams.length; i++) {
params += queryParams[i][0] + '=' + queryParams[i][1]
if (queryParams.length !== i + 1)
params += '&'
url += params
params = ''
}
}
fetch(url, {
method: 'DELETE',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + storeGet.main.getToken(),
},
body: JSON.stringify(body)
})
.then(async response => {
let status = response.status;
return { response: await response.json(), status: status }
})
.then(data => {
if (data.status >= 400 && data.status <= 599)
errorCallback(data.response)
else
callback(data.response)
})
.catch(err => errorCallback(err));
};
}

16
src/setupProxy.js Normal file
View File

@@ -0,0 +1,16 @@
//made available by http-proxy-middleware
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
createProxyMiddleware(
"/api",
{
target: 'https://localhost:8000/',
changeOrigin: true,
}
)
);
};

15
src/store/actions.js Normal file
View File

@@ -0,0 +1,15 @@
export const actionsAlpha = (set, get, api) => ({
setAsyncRequest: () => {
const num = get.isAsyncRequest();
set.isAsyncRequest(num + 1);
},
unsetAsyncRequest: () => {
const num = get.isAsyncRequest();
set.isAsyncRequest(num - 1 < 0 ? 0 : num - 1);
},
})
export const actionsBeta = (set, get, api) => ({
});

20
src/store/index.js Normal file
View File

@@ -0,0 +1,20 @@
import { mapValuesKey } from 'zustand-x';
// stores
import { mainStore } from './main';
// Global store - initial data
const dashboardStore = {
main: mainStore
};
// Global hook selectors
export const useStore = () => mapValuesKey('use', dashboardStore);
// Global tracked hook selectors
export const useTrackedStore = () => mapValuesKey('useTracked', dashboardStore);
// Global getter selectors
export const storeGet = mapValuesKey('get', dashboardStore);
// Global actions
export const storeSet = mapValuesKey('set', dashboardStore);
export default dashboardStore;

6
src/store/initial.js Normal file
View File

@@ -0,0 +1,6 @@
const initialStore = {
// ui related
isAsyncRequest: 0, // number
}
export default initialStore;

11
src/store/main.js Normal file
View File

@@ -0,0 +1,11 @@
import { createStore } from 'zustand-x';
import zustandXOpts from './zustand-x-opts';
import initialStore from './initial';
import selectors from './selectors';
import { actionsAlpha, actionsBeta } from './actions';
export const mainStore = createStore('main')(initialStore, zustandXOpts)
.extendSelectors(selectors)
.extendActions(actionsAlpha)
.extendActions(actionsBeta);

10
src/store/selectors.js Normal file
View File

@@ -0,0 +1,10 @@
import { isEmpty } from 'ramda';
const selectors = (state, get, api) => ({
getToken: () => {
const userData = get.userData();
return userData.access && !isEmpty(userData.access) ? userData.access : null;
},
})
export default selectors;

View File

@@ -0,0 +1,16 @@
const zustandXOpts = {
devtools: {
enabled: true
},
persist: {
enabled: true,
partialize: (state) => ({
userData: state.userData,
hubsList: state.hubsList,
chosenHub: state.chosenHub,
groups: state.groups,
}),
}
}
export default zustandXOpts;