From e7b062c88dcb0c5eec393b757afe13d8202d11cc Mon Sep 17 00:00:00 2001 From: Hamza Alqurneh Date: Tue, 15 Sep 2026 16:09:07 +0300 Subject: [PATCH 01/12] fix: land on the dashboard after signing in Co-Authored-By: Claude Opus 5 (1M context) --- SW.Bitween.Web/ClientApp/e2e/login.spec.ts | 26 ++++++++++++++++++++++ SW.Bitween.Web/ClientApp/src/nav.ts | 13 ++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/SW.Bitween.Web/ClientApp/e2e/login.spec.ts b/SW.Bitween.Web/ClientApp/e2e/login.spec.ts index 4ce0863a..da909f0a 100644 --- a/SW.Bitween.Web/ClientApp/e2e/login.spec.ts +++ b/SW.Bitween.Web/ClientApp/e2e/login.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type Page } from "@playwright/test"; +import { ADMIN_EMAIL, ADMIN_PASSWORD, signInAsAdmin } from "./helpers"; /** * What the sign-in page offers is driven by the anonymous config endpoint. These tests rewrite @@ -46,3 +47,28 @@ test("Microsoft-only with no Microsoft app configured explains itself", async ({ await expect(passwordField(page)).toHaveCount(0); await expect(microsoftButton(page)).toHaveCount(0); }); + +/** + * Where signing in leaves you. Two different answers, which is the point of testing both: an + * operator opening the app wants to see how the system is doing, but somebody who followed a + * link — or whose token expired mid-task — wants the page they were going to, not a detour. + */ +test("signing in with nowhere to go lands on the dashboard", async ({ page }) => { + await signInAsAdmin(page); + + await expect(page).toHaveURL(/\/dashboard$/); + await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible(); +}); + +test("signing in to reach a page lands on that page, not the dashboard", async ({ page }) => { + // Arriving cold at a protected URL — a link from a colleague, or a reload after the token + // expired. The guard sends it to login and login has to give it back. + await page.goto("partners"); + await page.waitForURL((url) => url.pathname.endsWith("/login")); + + await page.fill("#login-email", ADMIN_EMAIL); + await page.fill("#login-password", ADMIN_PASSWORD); + await page.getByRole("button", { name: "Sign in" }).click(); + + await expect(page).toHaveURL(/\/partners$/); +}); diff --git a/SW.Bitween.Web/ClientApp/src/nav.ts b/SW.Bitween.Web/ClientApp/src/nav.ts index 3b36679c..5a02ff16 100644 --- a/SW.Bitween.Web/ClientApp/src/nav.ts +++ b/SW.Bitween.Web/ClientApp/src/nav.ts @@ -118,8 +118,19 @@ export const visibleGroups = (permissions: PermissionKey[]): NavGroup[] => (g) => g.items.length > 0, ); -/** Where to land after signing in: the first page this session can see. */ +/** + * Where to land after signing in with nowhere particular to go. + * + * The dashboard, which is deliberately not in the sidebar — reached from the logo — so the + * rule below would never pick it. Landing there is the one moment it is the obviously right + * page: you have just arrived and want to know how the system is doing before going anywhere. + * + * Signing in *to get somewhere* is a different thing and does not come through here: the guard + * remembers the page it turned away and login returns to it, so an expired session and a link + * from a colleague both still end where they were headed. + */ export const homePath = (session: Session): string => { + if (session.permissions.includes("dashboard.view")) return "/dashboard"; const groups = visibleGroups(session.permissions); return groups[0]?.items[0]?.path ?? "/profile"; }; From 6e572bbab59df3b1a602b45fb980546e3f293949 Mon Sep 17 00:00:00 2001 From: Hamza Alqurneh Date: Tue, 15 Sep 2026 16:09:16 +0300 Subject: [PATCH 02/12] fix: put adapter fields above the mapper editor link Co-Authored-By: Claude Opus 5 (1M context) --- .../src/components/config/AdapterConfig.tsx | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx b/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx index 4fe48658..9555e8e7 100644 --- a/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx +++ b/SW.Bitween.Web/ClientApp/src/components/config/AdapterConfig.tsx @@ -515,27 +515,6 @@ export function AdapterConfig({ )} - {adapter && adapter.props.length > 0 && ( - /* - Two columns are decided by how wide *this* box is, not how wide the window is. - A `sm:` breakpoint reads the viewport, so a 360px side panel on a 2000px screen - still got two columns, and every email address wrapped onto three lines. The - container query asks the box instead, so a narrow one simply stacks. - */ -
- {requiredProps.length > 0 && ( -
{requiredProps.map(renderProp)}
- )} - {optionalProps.length > 0 && ( -
-

- Optional · {optionalProps.length} field{optionalProps.length === 1 ? "" : "s"} -

-
{optionalProps.map(renderProp)}
-
- )} -
- )} {adapter && usesVisualMappingEditor(adapter.id) && ( onOpenMapperEditor ? (