[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"
}
},
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

View File

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

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<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="theme-color" content="#000000" />
<meta name="description" content="Document Server Admin Panel" />

View File

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

View File

@ -1,7 +1,8 @@
const BACKEND_URL = process.env.REACT_APP_BACKEND_URL ?? '';
const API_BASE_PATH = '/api/v1/admin';
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) {
throw new Error('Failed to fetch statistics');
}
@ -9,7 +10,7 @@ export const fetchStatistics = 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'
});
if (!response.ok) {
@ -19,7 +20,7 @@ export const fetchConfiguration = 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'
});
if (!response.ok) {
@ -29,7 +30,7 @@ export const fetchConfigurationSchema = async () => {
};
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',
headers: {
'Content-Type': 'application/json'
@ -54,7 +55,7 @@ export const updateConfiguration = async configData => {
};
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',
credentials: 'include' // Include cookies in the request
});
@ -70,7 +71,7 @@ export const fetchCurrentUser = async () => {
};
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',
headers: {
'Content-Type': 'application/json'
@ -90,7 +91,7 @@ export const login = async ({tenantName, secret}) => {
};
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',
headers: {
'Content-Type': 'application/json'
@ -106,7 +107,7 @@ export const logout = 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',
headers: {
'Content-Type': 'application/json',

View File

@ -1,6 +1,5 @@
import {StrictMode} from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import App from './App';
@ -18,9 +17,7 @@ const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
<App />
</QueryClientProvider>
</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 CopyPlugin = require('copy-webpack-plugin');
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 = {
entry: './src/index.js',
@ -39,7 +52,8 @@ module.exports = {
]
}),
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 || '')
})
],