Skip to content

Commit e27eb93

Browse files
cfsmp3claude
andauthored
feat: use relative URLs for frontend API calls (#439)
* feat: use relative URLs for frontend API calls Change frontend to use relative URLs instead of hardcoded localhost values. This enables the same Docker image to work in any environment since nginx handles routing to the backend. Changes: - backendURL now uses '/' (relative path) - containerOrigin derived from window.location.origin - Add getWebSocketURL() helper for WebSocket connections - Remove VITE_* env vars from jest config (no longer needed) - Update tests and snapshots This is a prerequisite for automated deployment where images should be environment-agnostic. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add Vite dev proxy for local development Configure Vite's development server to proxy backend routes to localhost:8000, mimicking nginx's routing behavior. This allows relative URLs to work in both local development and production. Routes proxied: /auth, /api, /tasks, /health, /sync, /ws, and all task mutation endpoints. Also proxies /taskchampion to localhost:8080. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d6b5e80 commit e27eb93

6 files changed

Lines changed: 112 additions & 34 deletions

File tree

frontend/jest.config.cjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,7 @@ module.exports = {
2626
options: {
2727
metaObjectReplacement: {
2828
url: 'https://www.url.com',
29-
env: {
30-
VITE_BACKEND_URL: 'http://localhost:8000/',
31-
VITE_FRONTEND_URL: 'http://localhost:80',
32-
VITE_CONTAINER_ORIGIN: 'http://localhost:8080/',
33-
},
29+
env: {},
3430
},
3531
},
3632
},

frontend/src/components/HomePage.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Footer } from './HomeComponents/Footer/Footer';
55
import { SetupGuide } from './HomeComponents/SetupGuide/SetupGuide';
66
import { FAQ } from './HomeComponents/FAQ/FAQ';
77
import { Tasks } from './HomeComponents/Tasks/Tasks';
8-
import { url } from '@/components/utils/URLs';
8+
import { url, getWebSocketURL } from '@/components/utils/URLs';
99
import { useNavigate } from 'react-router';
1010
import { motion } from 'framer-motion';
1111
import { toast } from 'react-toastify';
@@ -60,9 +60,7 @@ export const HomePage: React.FC = () => {
6060
getTasks(userInfo.email, userInfo.encryption_secret, userInfo.uuid);
6161
}
6262

63-
const socketURL = `${url.backendURL.replace(/^http/, 'ws')}ws?clientID=${
64-
userInfo.uuid
65-
}`;
63+
const socketURL = getWebSocketURL(`ws?clientID=${userInfo.uuid}`);
6664
const socket = new WebSocket(socketURL);
6765

6866
// socket.onopen = () => console.log('WebSocket connected!');

frontend/src/components/LandingComponents/Hero/__tests__/__snapshots__/Hero.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ exports[`Hero component using snapshot renders correctly 1`] = `
4242
class="space-y-4 md:space-y-0 md:space-x-4"
4343
>
4444
<a
45-
href="http://localhost:8000/auth/oauth"
45+
href="/auth/oauth"
4646
>
4747
<button
4848
class="w-full md:w-1/3 bg-blue-400 hover:bg-blue-500"

frontend/src/components/__tests__/HomePage.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jest.mock('@/components/utils/URLs', () => ({
8888
containerOrigin: 'http://mocked-origin/',
8989
frontendURL: 'http://mocked-frontend-url/',
9090
},
91+
getWebSocketURL: (path: string) => `ws://mocked-backend-url/${path}`,
9192
}));
9293

9394
// Mock fetch
Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,39 @@
1-
const isTesting = false;
1+
// Check if we're in a browser environment
2+
const isBrowser = typeof window !== 'undefined';
23

3-
export const url = isTesting
4-
? {
5-
backendURL: '',
6-
frontendURL: '',
7-
containerOrigin: '',
8-
githubRepoURL: '',
9-
githubDocsURL: '',
10-
zulipURL: '',
11-
taskwarriorURL: '',
12-
taskchampionSyncServerURL: '',
13-
}
14-
: {
15-
backendURL: import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000/',
16-
frontendURL: import.meta.env.VITE_FRONTEND_URL || 'http://localhost:80',
17-
containerOrigin:
18-
import.meta.env.VITE_CONTAINER_ORIGIN || 'http://localhost:8080/',
19-
githubRepoURL: 'https://github.com/CCExtractor/ccsync',
20-
githubDocsURL: 'https://its-me-abhishek.github.io/ccsync-docs/',
21-
zulipURL: 'https://ccextractor.org/public/general/support/',
22-
taskwarriorURL: 'https://taskwarrior.org/docs/',
23-
taskchampionSyncServerURL:
24-
'https://github.com/GothenburgBitFactory/taskchampion-sync-server/tree/main',
25-
};
4+
// Helper to get the WebSocket URL for the current host
5+
export const getWebSocketURL = (path: string): string => {
6+
if (!isBrowser) {
7+
return `ws://localhost:8000/${path}`;
8+
}
9+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
10+
return `${protocol}//${window.location.host}/${path}`;
11+
};
12+
13+
// Helper to get the sync server URL (shown to users in SetupGuide)
14+
const getSyncServerURL = (): string => {
15+
if (!isBrowser) {
16+
return 'http://localhost:8080/';
17+
}
18+
return `${window.location.origin}/taskchampion/`;
19+
};
20+
21+
export const url = {
22+
// Backend API calls use relative URLs - nginx routes to backend
23+
backendURL: '/',
24+
25+
// Frontend URL for redirects - just use root
26+
frontendURL: '/',
27+
28+
// Sync server URL - derived from current origin
29+
// This is shown to users in SetupGuide for their taskwarrior config
30+
containerOrigin: getSyncServerURL(),
31+
32+
// External URLs (unchanged)
33+
githubRepoURL: 'https://github.com/CCExtractor/ccsync',
34+
githubDocsURL: 'https://its-me-abhishek.github.io/ccsync-docs/',
35+
zulipURL: 'https://ccextractor.org/public/general/support/',
36+
taskwarriorURL: 'https://taskwarrior.org/docs/',
37+
taskchampionSyncServerURL:
38+
'https://github.com/GothenburgBitFactory/taskchampion-sync-server/tree/main',
39+
};

frontend/vite.config.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,73 @@ export default defineConfig({
99
'@': path.resolve(__dirname, './src'),
1010
},
1111
},
12+
server: {
13+
proxy: {
14+
// Proxy backend routes during development
15+
// This mimics nginx's routing behavior in production
16+
'/auth': {
17+
target: 'http://localhost:8000',
18+
changeOrigin: true,
19+
},
20+
'/api': {
21+
target: 'http://localhost:8000',
22+
changeOrigin: true,
23+
},
24+
'/tasks': {
25+
target: 'http://localhost:8000',
26+
changeOrigin: true,
27+
},
28+
'/health': {
29+
target: 'http://localhost:8000',
30+
changeOrigin: true,
31+
},
32+
'/sync': {
33+
target: 'http://localhost:8000',
34+
changeOrigin: true,
35+
},
36+
'/ws': {
37+
target: 'ws://localhost:8000',
38+
ws: true,
39+
},
40+
// Task mutation endpoints
41+
'/add-task': {
42+
target: 'http://localhost:8000',
43+
changeOrigin: true,
44+
},
45+
'/edit-task': {
46+
target: 'http://localhost:8000',
47+
changeOrigin: true,
48+
},
49+
'/delete-task': {
50+
target: 'http://localhost:8000',
51+
changeOrigin: true,
52+
},
53+
'/complete-task': {
54+
target: 'http://localhost:8000',
55+
changeOrigin: true,
56+
},
57+
'/modify-task': {
58+
target: 'http://localhost:8000',
59+
changeOrigin: true,
60+
},
61+
'/add-tasks': {
62+
target: 'http://localhost:8000',
63+
changeOrigin: true,
64+
},
65+
'/complete-tasks': {
66+
target: 'http://localhost:8000',
67+
changeOrigin: true,
68+
},
69+
'/delete-tasks': {
70+
target: 'http://localhost:8000',
71+
changeOrigin: true,
72+
},
73+
// Taskchampion sync server
74+
'/taskchampion': {
75+
target: 'http://localhost:8080',
76+
changeOrigin: true,
77+
rewrite: (path) => path.replace(/^\/taskchampion/, ''),
78+
},
79+
},
80+
},
1281
});

0 commit comments

Comments
 (0)