diff --git a/ui/src/pages/CompanyImport.test.tsx b/ui/src/pages/CompanyImport.test.tsx index 95341d32764..835a712a922 100644 --- a/ui/src/pages/CompanyImport.test.tsx +++ b/ui/src/pages/CompanyImport.test.tsx @@ -396,10 +396,22 @@ describe("CompanyImport", () => { expect(container.textContent).toContain("Uploading and importing"); expect(container.textContent).toContain("Keep this page open."); - // While the import runs, previewing is locked (and explained) so a - // concurrent preview cannot replace the plan the import started from. + // While the import runs, previewing and the structural package/settings + // controls are locked (and explained), so nothing can replace or unmount + // the plan the import started from. expect(findButton((text) => text === "Preview import")?.disabled).toBe(true); - expect(container.textContent).toContain("Import in progress — previewing unlocks when it finishes."); + expect(container.textContent).toContain( + "Import in progress — the package and settings unlock when it finishes.", + ); + const lockedUrlInput = container.querySelector( + 'input[placeholder="https://github.com/owner/repo/tree/main/company"]', + ); + expect(lockedUrlInput?.disabled).toBe(true); + const lockedSelects = Array.from(container.querySelectorAll("select")).filter( + (select) => select.value === "new" || select.value === "rename", + ); + expect(lockedSelects).toHaveLength(2); + expect(lockedSelects.every((select) => select.disabled)).toBe(true); // Config edits while the import is in flight must not detach it from // the UI: the progress panel keeps reporting it until it settles. @@ -440,6 +452,11 @@ describe("CompanyImport", () => { expect(container.textContent).not.toContain("Import failed:"); expect(findButton((text) => text.startsWith("Import 3 file"))).toBeTruthy(); expect(findButton((text) => text === "Preview import")?.disabled).toBe(false); + expect( + container.querySelector( + 'input[placeholder="https://github.com/owner/repo/tree/main/company"]', + )?.disabled, + ).toBe(false); // The pause toggle feeds the request payload too: after another failed // attempt, toggling it also supersedes the request and clears the panel. diff --git a/ui/src/pages/CompanyImport.tsx b/ui/src/pages/CompanyImport.tsx index 4bb7fec8b11..952da1d64a9 100644 --- a/ui/src/pages/CompanyImport.tsx +++ b/ui/src/pages/CompanyImport.tsx @@ -943,7 +943,9 @@ export function CompanyImport() { // pending request is never detached: its panel keeps reporting it (and the // action buttons stay disabled) until it settles. The new-company name and // pause toggle are set against a rendered preview, so they reset only the - // panels; structural changes also discard the preview itself. + // panels; structural changes also discard the preview itself. Structural + // controls are locked while an import runs, so this can never unmount the + // preview section that hosts a running import's status panels. function resetMutationState() { if (!previewMutation.isPending) previewMutation.reset(); if (!importMutation.isPending) importMutation.reset(); @@ -1336,7 +1338,9 @@ export function CompanyImport() { sourceMode === key ? "border-foreground bg-accent" : "border-border hover:bg-accent/50", + importMutation.isPending && "cursor-not-allowed opacity-50", )} + disabled={importMutation.isPending} onClick={() => { setSourceMode(key); resetImportFlowState(); @@ -1364,6 +1368,7 @@ export function CompanyImport() { size="sm" variant="outline" onClick={() => packageInputRef.current?.click()} + disabled={importMutation.isPending} > Choose zip @@ -1405,6 +1410,7 @@ export function CompanyImport() { type="text" value={importUrl} placeholder="https://github.com/owner/repo/tree/main/company" + disabled={importMutation.isPending} onChange={(e) => { setImportUrl(e.target.value); resetImportFlowState(); @@ -1417,6 +1423,7 @@ export function CompanyImport() { { setCollisionStrategy(e.target.value as CompanyPortabilityCollisionStrategy); resetImportFlowState(); @@ -1483,7 +1491,7 @@ export function CompanyImport() { )} {importMutation.isPending && ( - Import in progress — previewing unlocks when it finishes. + Import in progress — the package and settings unlock when it finishes. )} {inlineImportBlocked && (