diff --git a/src/editor/CloudAccountPanel.tsx b/src/editor/CloudAccountPanel.tsx index 31f8600..791ca37 100644 --- a/src/editor/CloudAccountPanel.tsx +++ b/src/editor/CloudAccountPanel.tsx @@ -5,6 +5,29 @@ import { PROJECT_LAST_SAVED_AT_STORAGE_KEY, PROJECT_STORAGE_KEY, WORKSPACE_BACKU import { WorkspaceConflictModal } from './WorkspaceConflictModal'; import { summarizeYamlWorkspace } from './workspaceSummary'; +export function buildGithubStartHref(params: { + apiBaseUrl: string; + baseUrl: string; + locationHref?: string; +}): string { + const apiBase = params.apiBaseUrl.trim(); + const baseUrl = params.baseUrl.trim() || '/'; + if (!apiBase) return '/api/v1/auth/github/start?returnTo=/'; + + const normalized = apiBase.replace(/\/+$/, ''); + const returnTo = (() => { + if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) return baseUrl; + if (!params.locationHref) return baseUrl; + try { + return new URL(baseUrl, params.locationHref).toString(); + } catch { + return baseUrl; + } + })(); + + return `${normalized}/api/v1/auth/github/start?returnTo=${encodeURIComponent(returnTo)}`; +} + export function CloudAccountPanel({ state, onLoadYaml, @@ -41,9 +64,8 @@ export function CloudAccountPanel({ const metaEnv = (import.meta as any)?.env as Record | undefined; const apiBase = typeof metaEnv?.VITE_API_BASE_URL === 'string' ? metaEnv.VITE_API_BASE_URL.trim() : ''; const baseUrl = typeof metaEnv?.BASE_URL === 'string' ? metaEnv.BASE_URL : '/'; - if (!apiBase) return '/api/v1/auth/github/start?returnTo=/'; - const normalized = apiBase.replace(/\/+$/, ''); - return `${normalized}/api/v1/auth/github/start?returnTo=${encodeURIComponent(baseUrl)}`; + const locationHref = typeof window !== 'undefined' ? window.location.href : undefined; + return buildGithubStartHref({ apiBaseUrl: apiBase, baseUrl, locationHref }); }, []); useEffect(() => { diff --git a/tests/editor/buildGithubStartHref.test.ts b/tests/editor/buildGithubStartHref.test.ts new file mode 100644 index 0000000..05df3da --- /dev/null +++ b/tests/editor/buildGithubStartHref.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; + +import { buildGithubStartHref } from '../../src/editor/CloudAccountPanel'; + +describe('buildGithubStartHref', () => { + it('uses an absolute returnTo when BASE_URL is relative (e.g. "./")', () => { + const href = buildGithubStartHref({ + apiBaseUrl: 'https://phaseractions-studio-production.up.railway.app', + baseUrl: './', + locationHref: 'https://phaseractions.studio/editor/index.html', + }); + + const url = new URL(href); + expect(url.origin).toBe('https://phaseractions-studio-production.up.railway.app'); + expect(url.pathname).toBe('/api/v1/auth/github/start'); + expect(url.searchParams.get('returnTo')).toBe('https://phaseractions.studio/editor/'); + }); + + it('keeps returnTo relative when no locationHref is provided', () => { + const href = buildGithubStartHref({ + apiBaseUrl: 'https://phaseractions-studio-production.up.railway.app/', + baseUrl: '/app/', + }); + const url = new URL(href); + expect(url.searchParams.get('returnTo')).toBe('/app/'); + }); +}); +