diff --git a/frontend/.env b/frontend/.env
index ddaae9b..ef95ff8 100644
--- a/frontend/.env
+++ b/frontend/.env
@@ -1,2 +1,5 @@
-PUBLIC_URL=/video_query
-REACT_APP_BASE_URL=/video_query
\ No newline at end of file
+# Removed hardcoded PUBLIC_URL to enable flexible deployment paths
+# The app now uses runtime configuration from config.json for base path detection
+
+# Optional: Set this if you want to override the runtime base path detection
+# REACT_APP_BASE_PATH_OVERRIDE=/your-custom-path
\ No newline at end of file
diff --git a/frontend/build.sh b/frontend/build.sh
index 8ce39f3..726b8d0 100755
--- a/frontend/build.sh
+++ b/frontend/build.sh
@@ -1,18 +1,31 @@
#!/bin/bash
+# Environment-agnostic build script
+# The app now detects its base path at runtime from config.json
+
# Clean any previous build
rm -rf build
-# Make sure the .env file exists
-if [ ! -f .env ]; then
- echo "Creating .env file with correct PUBLIC_URL"
- echo "PUBLIC_URL=/video_query" > .env
- echo "REACT_APP_BASE_URL=/video_query" >> .env
-fi
+echo "Building application with dynamic base path support..."
+echo "The app will detect its base path at runtime from config.json"
-# Build with the public URL explicitly set
-PUBLIC_URL="/video_query" npm run build
+# Build without hardcoded paths
+npm run build
-echo "Build complete. The 'build' directory now contains files ready for deployment."
-echo "Copy these files to your web server's /video_query directory."
-echo "Example: scp -r build/* user@your-server:/var/www/html/video_query/"
\ No newline at end of file
+echo ""
+echo "✅ Build complete! The 'build' directory contains files ready for deployment."
+echo ""
+echo "📝 DEPLOYMENT INSTRUCTIONS:"
+echo "1. Copy build files to your web server at any path:"
+echo " Example: scp -r build/* user@server:/var/www/html/your-app-path/"
+echo ""
+echo "2. Update config.json on your server:"
+echo " - Set 'basePath' to match your deployment path (e.g., '/video_query', '/video-query')"
+echo " - Set 'domain' to your server's domain"
+echo " - Update MSAL and API endpoints as needed"
+echo ""
+echo "3. The app will automatically use the correct paths at runtime!"
+echo ""
+echo "📋 Example config.json for different deployments:"
+echo " Subdirectory: {\"basePath\": \"/video_query\", \"domain\": \"https://myserver.com\"}"
+echo " Root deploy: {\"basePath\": \"\", \"domain\": \"https://video-app.com\"}"
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
index 35bb679..c0068cc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -14,9 +14,10 @@
"react-scripts": "5.0.1",
"showdown": "^2.1.0"
},
+ "homepage": ".",
"scripts": {
"start": "react-scripts start",
- "build": "PUBLIC_URL=/video_query react-scripts build",
+ "build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
diff --git a/frontend/public/config.js b/frontend/public/config.js
new file mode 100644
index 0000000..13f23c6
--- /dev/null
+++ b/frontend/public/config.js
@@ -0,0 +1,16 @@
+window.__APP_CONFIG__ = {
+ "_comment": "Dynamic base path configuration - set basePath to override auto-detection",
+ "basePath": "/video-query",
+ "domain": "https://brandtechsandbox.oliver.solutions",
+ "msal": {
+ "clientId": "01d33c7c-5640-4986-b4db-06af63a7d285",
+ "authority": "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
+ "redirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
+ "postLogoutRedirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
+ "tenantId": "e519c2e6-bc6d-4fdf-8d9c-923c2f002385"
+ },
+ "api": {
+ "videoProcessingEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back/api/process",
+ "chunkedUploadEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back"
+ }
+};
\ No newline at end of file
diff --git a/frontend/public/config.json b/frontend/public/config.json
new file mode 100644
index 0000000..9d29a28
--- /dev/null
+++ b/frontend/public/config.json
@@ -0,0 +1,16 @@
+{
+ "_comment": "Dynamic base path configuration - set basePath to override auto-detection",
+ "basePath": "/video-query",
+ "domain": "https://brandtechsandbox.oliver.solutions",
+ "msal": {
+ "clientId": "01d33c7c-5640-4986-b4db-06af63a7d285",
+ "authority": "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
+ "redirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
+ "postLogoutRedirectUri": "https://brandtechsandbox.oliver.solutions/video-query/",
+ "tenantId": "e519c2e6-bc6d-4fdf-8d9c-923c2f002385"
+ },
+ "api": {
+ "videoProcessingEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back/api/process",
+ "chunkedUploadEndpoint": "https://brandtechsandbox.oliver.solutions/video_query_back"
+ }
+}
\ No newline at end of file
diff --git a/frontend/public/config.template.json b/frontend/public/config.template.json
new file mode 100644
index 0000000..685255c
--- /dev/null
+++ b/frontend/public/config.template.json
@@ -0,0 +1,39 @@
+{
+ "_note": "This is a template configuration file. Copy this to config.json and update values for your environment.",
+ "_instructions": {
+ "basePath": "Set to your app's base path (e.g., '/video_query', '/video-query', '/app'). Leave empty or set to '/' for root deployment. If omitted, the app will auto-detect from URL.",
+ "domain": "Your application's domain (used for constructing redirect URIs)",
+ "msal": "Azure AD application registration settings",
+ "api": "Backend API endpoints"
+ },
+ "basePath": "/your-app-path",
+ "domain": "https://your-domain.com",
+ "msal": {
+ "clientId": "YOUR_AZURE_AD_CLIENT_ID",
+ "authority": "https://login.microsoftonline.com/YOUR_TENANT_ID",
+ "tenantId": "YOUR_TENANT_ID"
+ },
+ "api": {
+ "videoProcessingEndpoint": "https://your-api-domain.com/api/process",
+ "chunkedUploadEndpoint": "https://your-api-domain.com"
+ },
+ "_examples": {
+ "deployment_scenarios": {
+ "subdirectory_deployment": {
+ "basePath": "/video_query",
+ "domain": "https://mycompany.com",
+ "resulting_app_url": "https://mycompany.com/video_query/"
+ },
+ "subdirectory_with_different_name": {
+ "basePath": "/video-analysis-tool",
+ "domain": "https://tools.example.org",
+ "resulting_app_url": "https://tools.example.org/video-analysis-tool/"
+ },
+ "root_deployment": {
+ "basePath": "",
+ "domain": "https://video-tool.mycompany.com",
+ "resulting_app_url": "https://video-tool.mycompany.com/"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 6156b1d..8a594b6 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -2,6 +2,7 @@
+
@@ -15,6 +16,29 @@
Video Query Tool
+
+
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 1d894a2..cbfbcae 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -6,6 +6,7 @@ import AuthenticatedContent from './components/AuthenticatedContent';
import Login from './components/Login';
import ChunkedUploader from './utils/chunkedUploader';
import { loginRequest } from './auth/authConfig';
+import { getApiConfig } from './utils/configLoader';
function App() {
// MSAL authentication hook
@@ -55,25 +56,26 @@ function App() {
// Always use chunked upload regardless of file size
console.log('Using chunked upload for all files');
- // Create chunked uploader
+ // Create chunked uploader with runtime config
+ const apiConfigForUpload = getApiConfig();
const uploader = new ChunkedUploader(selectedFile, (progress) => {
console.log(`Upload progress: ${progress}%`);
setUploadProgress(progress);
- });
-
+ }, apiConfigForUpload.chunkedUploadEndpoint);
+
// Variable to store upload result
let chunkUploadResult;
-
+
try {
// Start the chunked upload
console.log('Starting chunked upload process...');
chunkUploadResult = await uploader.uploadFile();
console.log('Upload result:', chunkUploadResult);
-
+
if (!chunkUploadResult.success) {
throw new Error('Chunked upload failed');
}
-
+
console.log('Chunked upload complete, starting processing');
console.log('File path:', chunkUploadResult.file_path);
console.log('Filename:', chunkUploadResult.filename);
@@ -82,10 +84,11 @@ function App() {
console.error('Chunked upload error:', uploadError);
throw uploadError;
}
-
+
// Now process the uploaded file
+ const apiConfig = getApiConfig();
response = await authApiClient.post(
- 'https://ai-sandbox.oliver.solutions/video_query_back/api/process',
+ apiConfig.videoProcessingEndpoint,
{
file_path: chunkUploadResult.file_path,
filename: chunkUploadResult.filename,
diff --git a/frontend/src/auth/AuthProvider.js b/frontend/src/auth/AuthProvider.js
index f13edcd..01d7bc7 100644
--- a/frontend/src/auth/AuthProvider.js
+++ b/frontend/src/auth/AuthProvider.js
@@ -1,39 +1,41 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { MsalProvider } from '@azure/msal-react';
import { PublicClientApplication, EventType, InteractionType } from '@azure/msal-browser';
-import { msalConfig } from './authConfig';
+import { loadConfig, getMsalConfig } from '../utils/configLoader';
-// Use the standard msalConfig but with implicit flow
-// This approach should work when the app is registered as a non-SPA client
-const msalConfig_enhanced = {
- ...msalConfig
-};
+// MSAL instance will be created after config is loaded
+let msalInstance = null;
-// Initialize MSAL
-export const msalInstance = new PublicClientApplication(msalConfig_enhanced);
-
-// Initialize MSAL instance
-(async () => {
+// Create MSAL instance with loaded configuration
+const createMsalInstance = async () => {
try {
+ console.log("Loading configuration for MSAL...");
+ await loadConfig();
+
+ const msalConfig = getMsalConfig();
+ console.log("Creating MSAL instance with runtime config...");
+
+ const instance = new PublicClientApplication(msalConfig);
+
console.log("Initializing MSAL instance...");
- await msalInstance.initialize();
+ await instance.initialize();
console.log("MSAL instance initialized successfully");
-
+
// Try to set active account after initialization
- if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
+ if (!instance.getActiveAccount() && instance.getAllAccounts().length > 0) {
console.log("Setting active account during initialization");
- msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
+ instance.setActiveAccount(instance.getAllAccounts()[0]);
}
// Handle any initial redirect response at startup
try {
console.log("Checking for redirect response at startup...");
- const response = await msalInstance.handleRedirectPromise();
+ const response = await instance.handleRedirectPromise();
if (response) {
console.log("Found redirect response at startup:", response);
if (response.account) {
console.log("Setting active account from redirect response:", response.account.name);
- msalInstance.setActiveAccount(response.account);
+ instance.setActiveAccount(response.account);
}
} else {
console.log("No redirect response at startup");
@@ -43,13 +45,13 @@ export const msalInstance = new PublicClientApplication(msalConfig_enhanced);
}
// Configure event callbacks for authentication events
- msalInstance.addEventCallback((event) => {
+ instance.addEventCallback((event) => {
// Handle successful logins
if (event.eventType === EventType.LOGIN_SUCCESS) {
console.log("Login success event triggered", event);
if (event.payload && event.payload.account) {
console.log("Setting active account from event:", event.payload.account.name);
- msalInstance.setActiveAccount(event.payload.account);
+ instance.setActiveAccount(event.payload.account);
// Force reload to update authentication state
if (event.interactionType === "redirect") {
window.location.reload();
@@ -81,68 +83,44 @@ export const msalInstance = new PublicClientApplication(msalConfig_enhanced);
console.log("Redirect handling completed");
}
});
-
+
console.log("Event callbacks registered");
+ return instance;
+
} catch (error) {
- console.error("Error initializing MSAL:", error);
+ console.error("Error creating MSAL instance:", error);
+ throw error;
}
-})();
+};
+
+// Export getter for MSAL instance
+export const getMsalInstance = () => {
+ return msalInstance;
+};
/**
* MSAL Provider Component to wrap the application with authentication context
*/
export const AuthProvider = ({ children }) => {
- const [isInitialized, setIsInitialized] = React.useState(false);
-
- // Check for authentication on component mount
+ const [isInitialized, setIsInitialized] = useState(false);
+ const [initError, setInitError] = useState(null);
+
+ // Initialize MSAL on component mount
useEffect(() => {
- const initializeAndHandleRedirect = async () => {
+ const initialize = async () => {
try {
- // Ensure MSAL is initialized
- if (!msalInstance.initialized) {
- console.log("Initializing MSAL from component...");
- await msalInstance.initialize();
- console.log("MSAL initialized from component");
- }
-
- // Handle any redirect response with PKCE auth code flow
- try {
- console.log("Handling redirect promise with PKCE...");
- // This properly handles auth code + PKCE flow redirects
- const response = await msalInstance.handleRedirectPromise();
-
- // If we have a response, we just returned from a redirect
- if (response) {
- console.log("Redirect response from PKCE flow:", response);
- if (response.account) {
- console.log("Setting active account after PKCE redirect:", response.account.name);
- msalInstance.setActiveAccount(response.account);
- }
- } else {
- console.log("No redirect response");
- // Try to set active account if not already set
- const accounts = msalInstance.getAllAccounts();
- console.log("Accounts found:", accounts.length);
- if (accounts.length > 0 && !msalInstance.getActiveAccount()) {
- console.log("Setting active account from cached accounts:", accounts[0].name);
- msalInstance.setActiveAccount(accounts[0]);
- }
- }
- } catch (redirectErr) {
- console.error("Error handling PKCE redirect:", redirectErr);
- console.error("Redirect error details:", JSON.stringify(redirectErr, null, 2));
- }
-
- // Mark as initialized
- setIsInitialized(true);
- } catch (initErr) {
- console.error("Error during MSAL initialization:", initErr);
- // Even if there's an error, mark as initialized to avoid infinite loop
+ console.log("AuthProvider: Starting initialization...");
+ msalInstance = await createMsalInstance();
+ console.log("AuthProvider: MSAL instance created successfully");
setIsInitialized(true);
+ } catch (error) {
+ console.error("AuthProvider: Error during initialization:", error);
+ setInitError(error);
+ setIsInitialized(true); // Mark as initialized even on error to show error message
}
};
-
- initializeAndHandleRedirect();
+
+ initialize();
}, []);
// Show loading until MSAL is initialized
@@ -150,9 +128,36 @@ export const AuthProvider = ({ children }) => {
return (
- Loading authentication...
+ Loading configuration...
+
+
Loading configuration and initializing authentication...
+
+ );
+ }
+
+ // Show error if initialization failed
+ if (initError) {
+ return (
+
+
+
Configuration Error
+
Failed to load application configuration:
+
{initError.message}
+
+
Please ensure config.json is available and properly configured.
+
+
+ );
+ }
+
+ // Only render MSAL provider if instance was created successfully
+ if (!msalInstance) {
+ return (
+
+
+
Initialization Error
+
MSAL instance was not created properly. Please refresh the page.
-
Initializing authentication...
);
}
diff --git a/frontend/src/auth/authApiClient.js b/frontend/src/auth/authApiClient.js
index dc87765..871ab7b 100644
--- a/frontend/src/auth/authApiClient.js
+++ b/frontend/src/auth/authApiClient.js
@@ -1,5 +1,5 @@
import axios from 'axios';
-import { msalInstance } from './AuthProvider';
+import { getMsalInstance } from './AuthProvider';
import { loginRequest } from './authConfig';
/**
@@ -27,10 +27,13 @@ const redirectToLogin = async () => {
// Clear MSAL caches before redirecting
try {
console.log("API: Clearing MSAL cache before redirect");
- msalInstance.clearCache();
- await msalInstance.logout({
- onRedirectNavigate: () => false // Don't redirect yet, we'll do it manually
- });
+ const msalInstance = getMsalInstance();
+ if (msalInstance) {
+ msalInstance.clearCache();
+ await msalInstance.logout({
+ onRedirectNavigate: () => false // Don't redirect yet, we'll do it manually
+ });
+ }
} catch (e) {
console.error("API: Error clearing cache:", e);
}
@@ -38,8 +41,9 @@ const redirectToLogin = async () => {
// Set a flag in sessionStorage to indicate we're expecting a login
sessionStorage.setItem("redirectedForLogin", "true");
- // Redirect to login page
- window.location.href = window.location.origin + "/video_query/";
+ // Redirect to login page using dynamic base path
+ const { navigateToPath } = require('../utils/pathUtils');
+ navigateToPath('/');
};
// Add request interceptor to add auth token to all API requests
@@ -78,6 +82,12 @@ authApiClient.interceptors.request.use(
console.log("API: No manual token, trying MSAL");
// Get active account and check if token exists
+ const msalInstance = getMsalInstance();
+ if (!msalInstance) {
+ console.warn("API: MSAL instance not available! Redirecting to login...");
+ await redirectToLogin();
+ return config;
+ }
const account = msalInstance.getActiveAccount();
if (!account) {
console.warn("API: No active account! Redirecting to login...");
diff --git a/frontend/src/auth/authConfig.js b/frontend/src/auth/authConfig.js
index 007ff45..79e5123 100644
--- a/frontend/src/auth/authConfig.js
+++ b/frontend/src/auth/authConfig.js
@@ -1,35 +1,25 @@
/*
* MSAL configuration for authentication
+ * This module exports configuration that will be loaded at runtime
*/
-// Get the public URL from environment or use default
-const publicUrl = process.env.REACT_APP_BASE_URL || '/video_query';
+import { getMsalConfig, getApiConfig } from '../utils/configLoader';
-export const msalConfig = {
- auth: {
- clientId: "9079054c-9620-4757-a256-23413042f1ef",
- authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
- redirectUri: "https://ai-sandbox.oliver.solutions/video_query/",
- postLogoutRedirectUri: "https://ai-sandbox.oliver.solutions/video_query/",
- navigateToLoginRequestUrl: true
- },
- cache: {
- cacheLocation: "sessionStorage",
- storeAuthStateInCookie: true,
- },
- system: {
- allowRedirectInIframe: true,
- tokenRenewalOffsetSeconds: 300,
- // Log all messages for debugging
- loggerOptions: {
- loggerCallback: (level, message) => {
- console.log(`MSAL: ${message}`);
- },
- logLevel: 4 // Verbose
- }
- }
+// Export a getter function instead of a static object
+// This ensures the config is loaded from runtime configuration
+export const getMsalConfigFromRuntime = () => {
+ return getMsalConfig();
};
+// Legacy export for backward compatibility
+// This will throw an error if config hasn't been loaded yet
+export const msalConfig = new Proxy({}, {
+ get: (target, prop) => {
+ const config = getMsalConfig();
+ return config[prop];
+ }
+});
+
// Add scopes here for access token request
// For more information about scopes visit:
// https://learn.microsoft.com/en-us/azure/active-directory/develop/permissions-consent-overview
@@ -38,7 +28,16 @@ export const loginRequest = {
};
// Add endpoints here for API calls
-export const apiConfig = {
- videoProcessingEndpoint: "https://ai-sandbox.oliver.solutions/video_query_back/api/process",
- chunkedUploadEndpoint: "https://ai-sandbox.oliver.solutions/video_query_back/api"
-};
\ No newline at end of file
+// These are now loaded from runtime configuration
+
+export const getApiConfigFromRuntime = () => {
+ return getApiConfig();
+};
+
+// Legacy export for backward compatibility
+export const apiConfig = new Proxy({}, {
+ get: (target, prop) => {
+ const config = getApiConfig();
+ return config[prop];
+ }
+});
\ No newline at end of file
diff --git a/frontend/src/components/Login.js b/frontend/src/components/Login.js
index 1677a88..0faf330 100644
--- a/frontend/src/components/Login.js
+++ b/frontend/src/components/Login.js
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useMsal } from '@azure/msal-react';
import { loginRequest } from '../auth/authConfig';
+import { getManualAuthConfig } from '../utils/configLoader';
const Login = () => {
const { instance, accounts, inProgress } = useMsal();
@@ -59,11 +60,19 @@ const Login = () => {
console.log("Login: No existing accounts found, proceeding with login");
}
+ // Get configuration values from runtime config
+ try {
+ var authConfig = getManualAuthConfig();
+ } catch (error) {
+ console.error("Configuration not loaded for login:", error);
+ return;
+ }
+
// Open Azure AD login page directly
// Use id_token for better backend compatibility
- const tenantId = "e519c2e6-bc6d-4fdf-8d9c-923c2f002385";
- const clientId = "9079054c-9620-4757-a256-23413042f1ef";
- const redirectUri = encodeURIComponent("https://ai-sandbox.oliver.solutions/video_query/");
+ const tenantId = authConfig.tenantId;
+ const clientId = authConfig.clientId;
+ const redirectUri = encodeURIComponent(authConfig.redirectUri);
const responseType = "id_token+token"; // Get both ID token and access token
const scope = encodeURIComponent("openid profile email User.Read");
const nonce = Math.random().toString(36).substring(2, 15);
diff --git a/frontend/src/components/ResultDisplay.js b/frontend/src/components/ResultDisplay.js
index 2b2e725..daacb3d 100644
--- a/frontend/src/components/ResultDisplay.js
+++ b/frontend/src/components/ResultDisplay.js
@@ -1,6 +1,7 @@
import React, { useRef, useEffect, useState } from 'react';
import showdown from 'showdown';
import mermaid from 'mermaid';
+import { getApiConfig } from '../utils/configLoader';
const ResultDisplay = ({ result, isLoading, uploadProgress = 0 }) => {
const resultRef = useRef(null);
@@ -271,8 +272,10 @@ const ResultDisplay = ({ result, isLoading, uploadProgress = 0 }) => {
// Make API request to generate PDF
const authApiClient = require('../auth/authApiClient').authApiClient;
+ const apiConfig = getApiConfig();
+ const pdfEndpoint = `${apiConfig.chunkedUploadEndpoint}/api/generate-pdf`;
const response = await authApiClient.post(
- 'https://ai-sandbox.oliver.solutions/video_query_back/api/generate-pdf',
+ pdfEndpoint,
{
html: htmlToSend,
textDiagrams: textDiagrams,
diff --git a/frontend/src/utils/chunkedUploader.js b/frontend/src/utils/chunkedUploader.js
index d338017..007c3a4 100644
--- a/frontend/src/utils/chunkedUploader.js
+++ b/frontend/src/utils/chunkedUploader.js
@@ -5,14 +5,14 @@
import { authApiClient } from '../auth/authApiClient';
const CHUNK_SIZE = 2 * 1024 * 1024; // 2MB chunks
-const BACKEND_URL = 'https://ai-sandbox.oliver.solutions/video_query_back';
class ChunkedUploader {
- constructor(file, onProgress) {
+ constructor(file, onProgress, backendUrl = 'https://ai-sandbox.oliver.solutions/video_query_back') {
this.file = file;
this.onProgress = onProgress || (() => {});
this.uploadId = null;
this.aborted = false;
+ this.backendUrl = backendUrl;
}
/**
@@ -21,7 +21,7 @@ class ChunkedUploader {
async initUpload() {
try {
const response = await authApiClient.post(
- `${BACKEND_URL}/api/init-upload`,
+ `${this.backendUrl}/api/init-upload`,
{
filename: this.file.name,
size: this.file.size,
@@ -67,7 +67,7 @@ class ChunkedUploader {
try {
const response = await authApiClient.post(
- `${BACKEND_URL}/api/upload-chunk/${this.uploadId}`,
+ `${this.backendUrl}/api/upload-chunk/${this.uploadId}`,
formData,
{
headers: {
@@ -100,7 +100,7 @@ class ChunkedUploader {
async completeUpload() {
try {
const response = await authApiClient.post(
- `${BACKEND_URL}/api/complete-upload/${this.uploadId}`,
+ `${this.backendUrl}/api/complete-upload/${this.uploadId}`,
{},
{
headers: {
@@ -139,7 +139,7 @@ class ChunkedUploader {
try {
const response = await authApiClient.post(
- `${BACKEND_URL}/api/cancel-upload/${this.uploadId}`,
+ `${this.backendUrl}/api/cancel-upload/${this.uploadId}`,
{},
{
headers: {
diff --git a/frontend/src/utils/configLoader.js b/frontend/src/utils/configLoader.js
new file mode 100644
index 0000000..141ff4e
--- /dev/null
+++ b/frontend/src/utils/configLoader.js
@@ -0,0 +1,207 @@
+/**
+ * Runtime configuration loader
+ * Fetches configuration from public/config.json at application startup
+ */
+
+let cachedConfig = null;
+let configPromise = null;
+
+/**
+ * Loads configuration from the public config.json file
+ * @returns {Promise