[feature] Refactor env, routing

This commit is contained in:
PauI Ostrovckij
2025-09-18 14:13:39 +03:00
parent 470e857e6b
commit 498a955e16
10 changed files with 85 additions and 34 deletions

View File

@ -1 +1,2 @@
REACT_APP_BACKEND_URL=http://localhost:9000 REACT_APP_BACKEND_URL=http://localhost:9000
REACT_APP_BASE_PATH=/admin

View File

@ -1 +1,12 @@
REACT_APP_BACKEND_URL=http://localhost:9000 # Admin Panel Environment Variables
# Copy this file to .env for local development
# Backend URL for API calls
REACT_APP_BACKEND_URL=http://localhost:9000
# Base path for the admin panel (empty for root deployment)
# Examples:
# REACT_APP_BASE_PATH= # Root deployment: http://localhost:3000/
# REACT_APP_BASE_PATH=/admin # Under /admin: http://localhost:3000/admin/
# REACT_APP_BASE_PATH=/docserver-admin # Under /docserver-admin: http://localhost:3000/docserver-admin/
REACT_APP_BASE_PATH=/admin

View File

@ -2768,6 +2768,12 @@
"is-obj": "^2.0.0" "is-obj": "^2.0.0"
} }
}, },
"dotenv": {
"version": "17.2.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
"dev": true
},
"dunder-proto": { "dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

@ -3,7 +3,7 @@
"version": "1.3.0", "version": "1.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "set \"REACT_APP_BACKEND_URL=http://localhost:9000\" && webpack serve --mode=development", "start": "webpack serve --mode=development",
"build": "webpack --mode=production" "build": "webpack --mode=production"
}, },
"dependencies": { "dependencies": {
@ -29,6 +29,7 @@
"babel-loader": "8.2.0", "babel-loader": "8.2.0",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"dotenv": "^17.2.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "5.5.0", "html-webpack-plugin": "5.5.0",
"sass": "^1.77.0", "sass": "^1.77.0",

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="images/favicon.ico" /> <link rel="icon" href="./images/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="description" content="Document Server Admin Panel" /> <meta name="description" content="Document Server Admin Panel" />

View File

@ -1,32 +1,37 @@
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import {Routes, Route, Navigate} from 'react-router-dom'; import {Routes, Route, Navigate, BrowserRouter} from 'react-router-dom';
import './App.css'; import './App.css';
import {store} from './store'; import {store} from './store';
import AuthWrapper from './components/AuthWrapper/AuthWrapper'; import AuthWrapper from './components/AuthWrapper/AuthWrapper';
import ConfigLoader from './components/ConfigLoader/ConfigLoader'; import ConfigLoader from './components/ConfigLoader/ConfigLoader';
import Menu from './components/Menu/Menu'; import Menu from './components/Menu/Menu';
import {menuItems} from './config/menuItems'; import {menuItems} from './config/menuItems';
import {getBasePath} from './utils/basePath';
function App() { function App() {
const basePath = getBasePath();
return ( return (
<Provider store={store}> <Provider store={store}>
<div className='app'> <BrowserRouter basename={basePath}>
<AuthWrapper> <div className='app'>
<div className='appLayout'> <AuthWrapper>
<Menu /> <div className='appLayout'>
<div className='mainContent'> <Menu />
<ConfigLoader> <div className='mainContent'>
<Routes> <ConfigLoader>
<Route path='/' element={<Navigate to='/statistics' replace />} /> <Routes>
{menuItems.map(item => ( <Route path='/' element={<Navigate to='/statistics' replace />} />
<Route key={item.key} path={item.path} element={<item.component />} /> {menuItems.map(item => (
))} <Route key={item.key} path={item.path} element={<item.component />} />
</Routes> ))}
</ConfigLoader> </Routes>
</ConfigLoader>
</div>
</div> </div>
</div> </AuthWrapper>
</AuthWrapper> </div>
</div> </BrowserRouter>
</Provider> </Provider>
); );
} }

View File

@ -1,7 +1,8 @@
const BACKEND_URL = process.env.REACT_APP_BACKEND_URL ?? ''; const BACKEND_URL = process.env.REACT_APP_BACKEND_URL ?? '';
const API_BASE_PATH = '/api/v1/admin';
export const fetchStatistics = async () => { export const fetchStatistics = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/stat`); const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/stat`);
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch statistics'); throw new Error('Failed to fetch statistics');
} }
@ -9,7 +10,7 @@ export const fetchStatistics = async () => {
}; };
export const fetchConfiguration = async () => { export const fetchConfiguration = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/config`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config`, {
credentials: 'include' credentials: 'include'
}); });
if (!response.ok) { if (!response.ok) {
@ -19,7 +20,7 @@ export const fetchConfiguration = async () => {
}; };
export const fetchConfigurationSchema = async () => { export const fetchConfigurationSchema = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/config/schema`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config/schema`, {
credentials: 'include' credentials: 'include'
}); });
if (!response.ok) { if (!response.ok) {
@ -29,7 +30,7 @@ export const fetchConfigurationSchema = async () => {
}; };
export const updateConfiguration = async configData => { export const updateConfiguration = async configData => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/config`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/config`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -54,7 +55,7 @@ export const updateConfiguration = async configData => {
}; };
export const fetchCurrentUser = async () => { export const fetchCurrentUser = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/me`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/me`, {
method: 'GET', method: 'GET',
credentials: 'include' // Include cookies in the request credentials: 'include' // Include cookies in the request
}); });
@ -70,7 +71,7 @@ export const fetchCurrentUser = async () => {
}; };
export const login = async ({tenantName, secret}) => { export const login = async ({tenantName, secret}) => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/login`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/login`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -90,7 +91,7 @@ export const login = async ({tenantName, secret}) => {
}; };
export const logout = async () => { export const logout = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/logout`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/logout`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -106,7 +107,7 @@ export const logout = async () => {
}; };
export const rotateWopiKeys = async () => { export const rotateWopiKeys = async () => {
const response = await fetch(`${BACKEND_URL}/api/v1/admin/wopi/rotate-keys`, { const response = await fetch(`${BACKEND_URL}${API_BASE_PATH}/wopi/rotate-keys`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -1,6 +1,5 @@
import {StrictMode} from 'react'; import {StrictMode} from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import App from './App'; import App from './App';
@ -18,9 +17,7 @@ const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( root.render(
<StrictMode> <StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter> <App />
<App />
</BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</StrictMode> </StrictMode>
); );

View File

@ -0,0 +1,15 @@
/**
* Utility functions for handling BASE_PATH environment variable
*/
// Get the base path from environment variable, with fallback to empty string
export const getBasePath = () => {
return process.env.REACT_APP_BASE_PATH || '';
};
// Create a full path by combining base path with the given path
export const createPath = (path) => {
const basePath = getBasePath();
const cleanPath = path.startsWith('/') ? path : `/${path}`;
return `${basePath}${cleanPath}`;
};

View File

@ -2,6 +2,19 @@ const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const webpack = require('webpack'); const webpack = require('webpack');
const dotenv = require('dotenv');
// Load environment variables from .env files
// Priority: .env.local > .env.development/.env.production > .env
const envFiles = [
'.env.local',
process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development',
'.env'
];
envFiles.forEach(file => {
dotenv.config({ path: file });
});
module.exports = { module.exports = {
entry: './src/index.js', entry: './src/index.js',
@ -39,7 +52,8 @@ module.exports = {
] ]
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.REACT_APP_BACKEND_URL': JSON.stringify(process.env.REACT_APP_BACKEND_URL) 'process.env.REACT_APP_BACKEND_URL': JSON.stringify(process.env.REACT_APP_BACKEND_URL),
'process.env.REACT_APP_BASE_PATH': JSON.stringify(process.env.REACT_APP_BASE_PATH || '')
}) })
], ],