Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
0c5bacb
feat(frontend): v1 scaffold, strict TypeScript and import boundary (U…
jaseemjaskp Oct 26, 2026
5c55a7c
fix(frontend): close v1 guardrail bypasses found in self-review (UN-4…
jaseemjaskp Oct 26, 2026
87c803c
feat(frontend): run v1 checks in CI and tighten what pages may import…
jaseemjaskp Oct 27, 2026
f86de0b
refactor(frontend): address SonarCloud findings on the v1 guardrails …
jaseemjaskp Oct 27, 2026
67be34b
refactor(frontend): split the contract-matrix builder to cut its comp…
jaseemjaskp Oct 27, 2026
81e15b1
fix(frontend): check every glob pattern and v0 -> v1 imports Biome ca…
jaseemjaskp Oct 27, 2026
7e66cc5
UN-4286 [FEAT] Add the v1 plugin manifest, slots and build check
athul-rs Oct 24, 2026
1f426f9
UN-4286 [FEAT] Pass VITE_DISTRIBUTION through the frontend Docker build
athul-rs Oct 24, 2026
7ae6de5
UN-4286 [FEAT] Keep overlaid v1 plugins out of OSS commits
athul-rs Oct 24, 2026
cbfd332
UN-4286 [FEAT] Build the plugin registry in plugins/ and hand Slot to…
athul-rs Oct 27, 2026
1babd39
UN-4286 [FIX] Let v1 tests typecheck until the scaffold settles the c…
athul-rs Oct 27, 2026
0bd4a2c
UN-4290 [FEAT] Add the v1 account menu and organisation switcher
athul-rs Oct 26, 2026
e7d4b55
UN-4290 [FIX] Show the e-mail as the account menu title when the user…
athul-rs Oct 26, 2026
7e541ab
UN-4299 [MISC] Turn on skipLibCheck for v1: vitest 3.2 and @types/cha…
praveen-formido Oct 28, 2026
6f22f62
UN-4299 [FEAT] Add the v1 theme model: three modes, follow-system and…
praveen-formido Oct 28, 2026
7aa5bf4
UN-4299 [FEAT] Add the v1 portal container and the theme preference s…
praveen-formido Oct 28, 2026
0c85cd6
UN-4299 [FEAT] Add the v1 token source, the generator and tokens.css …
praveen-formido Oct 28, 2026
6393d31
UN-4299 [FIX] Prefix the spacing utilities in the token table so they…
praveen-formido Oct 28, 2026
4a05a26
UN-4299 [FEAT] Add cn() configured for the v1 utility prefix
praveen-formido Oct 28, 2026
a9307e7
UN-4299 [FIX] Pin the v1 prefix in cn() with a test that fails withou…
praveen-formido Oct 28, 2026
5a361ae
UN-4299 [FIX] Use "ui" as the v1 utility prefix: Tailwind accepts low…
praveen-formido Oct 28, 2026
0e2b7a0
UN-4299 [FEAT] Add the prefixed v1 Tailwind root with fonts, modes, t…
praveen-formido Oct 28, 2026
d737fa8
UN-4299 [FIX] Exclude the generated tokens.css from Biome formatting …
praveen-formido Oct 28, 2026
a9acf5e
UN-4299 [FIX] Sort the imports in cn.ts so biome ci passes
praveen-formido Oct 28, 2026
861c7b6
UN-4286 [FIX] Retry a slot's import after a failed load
athul-rs Oct 28, 2026
014249a
UN-4290 [FIX] Check the sign-out redirect, share the sign-out sequenc…
athul-rs Oct 28, 2026
6c31314
UN-4286 [FIX] Reject unknown slots and distributions and load manifes…
athul-rs Oct 28, 2026
1c97ee3
UN-4290 [FIX] Harden the organisation switcher and match its Figma marks
athul-rs Oct 28, 2026
b3e8acf
UN-4290 [CHORE] Merge the P0-T07 review fixes
athul-rs Oct 28, 2026
54469cb
UN-4299 [FIX] Make the root compile test fail on unscoped selector li…
praveen-formido Oct 28, 2026
43c9ced
UN-4299 [FEAT] Add ThemeProvider: the v1 root, follow-system binding …
praveen-formido Oct 28, 2026
cc6126c
UN-4299 [FIX] Apply the stored theme before first paint with useLayou…
praveen-formido Oct 28, 2026
927f4e0
UN-4286 [FIX] Load esbuild only under Node, so the cloud overlay runs…
athul-rs Oct 28, 2026
52c4f55
UN-4290 [CHORE] Merge the P0-T07 loader fix
athul-rs Oct 28, 2026
0e13d94
UN-4299 [FEAT] Enforce the v1 styling policy: raw colours, pixels, in…
praveen-formido Oct 28, 2026
3b11ca4
UN-4290 [FIX] Follow only an http(s) sign-out redirect
athul-rs Oct 28, 2026
c3a703c
UN-4299 [FIX] Scope the styling lint to class strings and cover vh/vw…
praveen-formido Oct 28, 2026
a35da5b
UN-4299 [FIX] Keep v0's Tailwind scan out of src/v1, the lint scripts…
praveen-formido Oct 28, 2026
d6047e9
UN-4299 [FIX] Teach cn the type roles so a role and a text colour no …
praveen-formido Oct 28, 2026
b29503d
UN-4299 [FIX] Reject colour literals outside class strings, named-col…
praveen-formido Oct 28, 2026
e064614
UN-4299 [FIX] Keep the focus ring visible in forced-colors mode and s…
praveen-formido Oct 28, 2026
88354e1
UN-4299 [FIX] Create the portal container before the mode changes so …
praveen-formido Oct 28, 2026
a41e1ba
UN-4299 [FIX] Document the v0 rules that reach v1, the focus-ring usa…
praveen-formido Oct 28, 2026
8231919
UN-4299 [FIX] Name the theme storage key in the v1 README
praveen-formido Oct 28, 2026
d0c1be0
UN-4299 [MISC] Name the three theme modes in the v1 README
praveen-formido Oct 28, 2026
f8969a5
Merge remote-tracking branch 'origin/UN-4299-design-tokens-themes' in…
athul-rs Oct 28, 2026
d791573
UN-4290 Move the v1 menus onto the ui: prefix and design tokens
athul-rs Oct 28, 2026
664d1cf
fix(frontend): typecheck v1 tests that import vitest; skip cloud plug…
jaseemjaskp Oct 29, 2026
8ede7ec
UN-4286 [FIX] Write the plugin matrix to a file given without --dir, …
athul-rs Oct 29, 2026
fbe0e6c
UN-4290 [FIX] Scroll a long organisation list inside a panel capped t…
athul-rs Oct 29, 2026
787bf8b
fix(frontend): close v1 guardrail gaps from team review (UN-4270)
jaseemjaskp Oct 29, 2026
b9bed0d
feat(frontend): add an assets/ folder to the v1 taxonomy (UN-4270)
jaseemjaskp Oct 29, 2026
3889a34
fix(frontend): find v1 imports only in executable code, not in string…
jaseemjaskp Oct 29, 2026
bef9515
UN-4299 [FEAT] Let a v1 style attribute set CSS custom properties only
praveen-formido Oct 29, 2026
678efbb
UN-4299 [FIX] Make check-v1-styles.mjs executable to match its shebang
praveen-formido Oct 29, 2026
8028a73
UN-4299 [FIX] Type the custom-property keys a v1 style attribute may set
praveen-formido Oct 29, 2026
feb8c5f
fix(frontend): don't read JSX apostrophes as strings in the v1 scanne…
jaseemjaskp Oct 29, 2026
5312a6b
Merge remote-tracking branch 'origin/UN-4270-v1-scaffold' into UN-428…
athul-rs Oct 29, 2026
6b2df4d
UN-4286 [FIX] Give the missing-import test a template the v1 guard ca…
athul-rs Oct 29, 2026
5e944c3
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Oct 29, 2026
3d88fbf
Merge branch 'UN-4290-v1-account-menu' into UN-4290-v1-menus-tokens
athul-rs Oct 29, 2026
982c743
Merge remote-tracking branch 'origin/UN-4299-design-tokens-themes' in…
athul-rs Oct 29, 2026
d555e16
UN-4290 [FIX] Check the scroll classes by their ui: names
athul-rs Oct 29, 2026
8510dac
UN-4290 [FIX] Make the space before the new-tab hint explicit
athul-rs Oct 29, 2026
6202e5a
UN-4290 [FIX] Put the menus on the popover colour and match the switc…
athul-rs Oct 30, 2026
7af9264
fix(frontend): read v1 imports from a Babel AST, not a hand-written t…
jaseemjaskp Oct 30, 2026
daa1a18
fix(frontend): read v1 import specifiers through TypeScript assertion…
jaseemjaskp Oct 30, 2026
73b9ffa
fix(frontend): close v1 boundary gaps from the daa1a182d review (UN-4…
jaseemjaskp Nov 2, 2026
508e370
UN-4299 [FIX] Close the styling-lint gaps found in review
praveen-formido Nov 2, 2026
6e75882
UN-4299 [FIX] Set color-scheme per mode, bind spacing utilities to th…
praveen-formido Nov 2, 2026
622695f
UN-4299 [FIX] Run the token generator under bun
praveen-formido Nov 2, 2026
7fdcb02
fix(frontend): read CSS image-set() and escaped url()s; allowlist v1 …
jaseemjaskp Nov 2, 2026
ee517cb
chore: git-ignore cloud v1 plugins copied into src/v1/plugins/ (UN-4270)
jaseemjaskp Nov 2, 2026
e565ae5
UN-4299 [MISC] Merge main into the tokens branch now that the v1 scaf…
praveen-formido Nov 2, 2026
c6c0b21
UN-4299 [FIX] Close the S3 separator gap and two lint false positives…
praveen-formido Nov 2, 2026
4667e5d
UN-4299 [MISC] Move the token values to tokens.json and settle the So…
praveen-formido Nov 3, 2026
6fe2322
UN-4286 [MISC] Merge the final v1 scaffold head (#2380, ee517cb4e)
athul-rs Nov 3, 2026
9cdbf7b
UN-4286 [MISC] Merge main after the v1 scaffold landed (#2380)
athul-rs Nov 3, 2026
6f7c439
Merge remote-tracking branch 'origin/UN-4286-plugin-manifest' into UN…
athul-rs Nov 3, 2026
ab695c6
Merge remote-tracking branch 'origin/UN-4290-v1-account-menu' into UN…
athul-rs Nov 3, 2026
497c7b6
UN-4290 [MISC] Merge the latest tokens branch (#2399), which has main…
athul-rs Nov 3, 2026
f50b148
UN-4290 [FIX] Switch organisation by signing in again when the identi…
athul-rs Nov 3, 2026
6c85e7f
Merge branch 'UN-4290-v1-account-menu' into UN-4290-v1-menus-tokens
athul-rs Nov 3, 2026
fa189cf
Merge branch 'main' into UN-4299-design-tokens-themes
muhammad-ali-e Nov 4, 2026
486c2be
Merge branch 'main' into UN-4299-design-tokens-themes
muhammad-ali-e Nov 4, 2026
678ddaf
Merge commit 'refs/tmp/pr2399' into UN-4290-v1-menus-tokens
athul-rs Nov 5, 2026
4e56d2a
Merge remote-tracking branch 'origin/main' into UN-4290-v1-menus-tokens
athul-rs Nov 5, 2026
fc7a131
UN-4286 [MISC] Merge main into the plugin manifest branch
athul-rs Nov 9, 2026
4a25e80
UN-4290 [MISC] Merge the updated plugin manifest branch (#2394)
athul-rs Nov 9, 2026
000ae0d
UN-4286 [MISC] Merge main (v1 icons lint) into the plugin manifest br…
athul-rs Nov 9, 2026
779e6aa
UN-4290 [MISC] Merge the updated plugin manifest branch (#2394)
athul-rs Nov 9, 2026
0551930
UN-4290 [FIX] Account menu uses the dropdown-menu shadow
athul-rs Nov 10, 2026
365c924
UN-4286 [FIX] Test the v1 guard in both plugin-import resolvers directly
athul-rs Nov 10, 2026
c015c25
Merge remote-tracking branch 'origin/main' into UN-4286-plugin-manifest
athul-rs Nov 10, 2026
a29fc09
Merge commit 'c015c2537' into UN-4290-v1-account-menu
athul-rs Nov 10, 2026
cacc2e2
UN-4290 [FIX] Address review: restore the plugin README, explain a 41…
athul-rs Nov 11, 2026
8b94e7c
Merge origin/main into UN-4286-plugin-manifest
athul-rs Nov 11, 2026
4c4b9ae
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Nov 11, 2026
b3a667a
UN-4286 [FIX] Address review: check plugin routes against OSS pages, …
athul-rs Nov 11, 2026
9be9d5a
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Nov 11, 2026
f37d0d9
UN-4290 [FEAT] Keep the current organisation's details through a 403 …
athul-rs Nov 11, 2026
5cc28d7
UN-4286 [FEAT] Match plugin routes by overlap and report every render…
athul-rs Nov 12, 2026
85797a2
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Nov 12, 2026
98623d6
UN-4286 [FEAT] Use a fixed overlap rule in the registry test
athul-rs Nov 12, 2026
8963bc4
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Nov 12, 2026
7367906
UN-4286 [FIX] Address review: check manifest shape and route patterns…
athul-rs Nov 12, 2026
341df53
Merge branch 'UN-4286-plugin-manifest' into UN-4290-v1-account-menu
athul-rs Nov 12, 2026
e05f055
UN-4290 [FIX] Address review: list shape check, typed refusals, panel…
athul-rs Nov 12, 2026
20bd0f8
Merge remote-tracking branch 'origin/main' into UN-4290-v1-account-menu
athul-rs Nov 13, 2026
ee29b71
UN-4290 [FIX] Alias the Radix Popover so the v0 shim scan skips it
athul-rs Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/account_v2/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
|---|---|---|
| `role` | The caller has a membership in the organization on this deployment. | The stored role: `admin` or `user` in OSS; `unstract_admin`, `unstract_user`, `unstract_supervisor` or `unstract_reviewer` in cloud. |
| `member_count` | The organization exists on this deployment and, where the deployment sets `REGION`, its metadata doesn't name a different region. An organization created before the stamp and not back-filled carries no region, so a local copy of one from another region still sends the copy's count. | The people the members page lists: the organization's member records on this deployment. Platform API key accounts and pending invitations are not counted. |
| `region` | The deployment sets `REGION`, and either the organization's metadata names a region or the organization exists on this deployment. | The region code (`US`, `EU`; `STAGING`, `DEV` or `INTEGRATION` outside production), from the metadata trimmed and uppercased, else this deployment's `REGION`. The client maps it to a label. OSS and on-prem never send it: their settings don't read `REGION`, whatever the environment sets. Cloud stamps `region` on an organization's metadata where it is created. An organization from another region that the user once opened here has a local copy; its row shows the home region and no `member_count`, since this deployment counts only the people who opened the copy. Organizations created before the stamp and not back-filled show the region of the deployment that answered. |
| `region` | The deployment sets `REGION`, and either the organization's metadata names a region or the organization exists on this deployment. | The region code (`US`, `EU`; `STAGING`, `DEV` or `INTEGRATION` outside production), from the metadata trimmed and uppercased, else this deployment's `REGION`. The client shows the code as is. OSS and on-prem never send it: their settings don't read `REGION`, whatever the environment sets. Cloud stamps `region` on an organization's metadata where it is created. An organization from another region that the user once opened here has a local copy; its row shows the home region and no `member_count`, since this deployment counts only the people who opened the copy. Organizations created before the stamp and not back-filled show the region of the deployment that answered. |

If working out these three fields fails, the list is returned without them and the failure is logged.

Expand Down
38 changes: 38 additions & 0 deletions frontend/src/v1/lib/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import axios from "axios";

const SAFE_METHODS = new Set(["get", "head", "options"]);

function cookie(name: string): string | undefined {
const prefix = `${name}=`;
const entry = document.cookie
.split("; ")
.find((part) => part.startsWith(prefix));
if (entry === undefined) {
return undefined;
}
try {
return decodeURIComponent(entry.slice(prefix.length));
} catch {
// A malformed value is no token: send no header and let the server refuse.
return undefined;
}
}

/**
* The one HTTP client v1 services use. Django's session cookie authenticates;
* unsafe methods carry the CSRF cookie back as X-CSRFToken. Set here rather
* than through axios's xsrf options, which only the browser adapter applies.
* ponytail: no 401 or error-toast handling yet; add it when a page needs it.
*/
export const http = axios.create({ withCredentials: true });
Comment thread
athul-rs marked this conversation as resolved.
Resolved

http.interceptors.request.use((config) => {
const token = cookie("csrftoken");
if (
token !== undefined &&
!SAFE_METHODS.has((config.method ?? "get").toLowerCase())
) {
config.headers.set("X-CSRFToken", token);
}
return config;
});
Comment thread
athul-rs marked this conversation as resolved.
Resolved
112 changes: 112 additions & 0 deletions frontend/src/v1/lib/organizations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, expect, test } from "vitest";
import {
describeOrganization,
initial,
initials,
landingPath,
organizationInitials,
roleLabel,
} from "./organizations";

describe("roleLabel", () => {
test.each([
["admin", "Admin"],
["unstract_admin", "Admin"],
["user", "Member"],
["unstract_user", "Member"],
["unstract_supervisor", "Supervisor"],
["unstract_reviewer", "Reviewer"],
])("%s reads %s", (role, label) => {
expect(roleLabel(role)).toBe(label);
});

test("an unknown role is not guessed", () => {
expect(roleLabel("auditor")).toBeUndefined();
expect(roleLabel(undefined)).toBeUndefined();
});
});

describe("describeOrganization", () => {
test("role, member count and region, as frame NEW10 shows", () => {
expect(
describeOrganization({ role: "admin", member_count: 40, region: "US" }),
).toBe("Admin · 40 members · US");
});

test("one member is singular", () => {
expect(describeOrganization({ role: "admin", member_count: 1 })).toBe(
"Admin · 1 member",
);
});

test("absent fields are left out, never guessed (OSS sends no region)", () => {
expect(describeOrganization({ role: "user" })).toBe("Member");
expect(describeOrganization({ member_count: 3 })).toBe("3 members");
expect(describeOrganization({})).toBe("");
});
});

describe("initials", () => {
test.each([
["Zipstack", "Z"],
["Tanushree Daga", "TD"],
["Tanushree’s organisation", "TO"],
[" ", "?"],
])("%s gives %s", (name, expected) => {
expect(initials(name)).toBe(expected);
});
});

describe("initial", () => {
test.each([
["Tanushree Daga", "T"],
["[email protected]", "A"],
[" ", "?"],
])("%s gives %s", (name, expected) => {
expect(initial(name)).toBe(expected);
});
});

describe("organizationInitials", () => {
test.each([
["Zipstack", "ZI"],
["Tanushree’s organisation", "TO"],
["Acme Data Labs", "AD"],
["X", "X"],
[" ", "?"],
])("%s gives %s", (name, expected) => {
expect(organizationInitials(name)).toBe(expected);
});
});

describe("landingPath", () => {
test("keeps the section in the new organisation", () => {
expect(landingPath("/zipstack/workflows", "zipstack", "tanushree")).toBe(
"/tanushree/workflows",
);
});

test("drops anything under the section: that record may not exist there", () => {
expect(
landingPath("/zipstack/tools/123/settings", "zipstack", "tanushree"),
).toBe("/tanushree/tools");
});

test("falls back to the dashboard when the path has no section", () => {
expect(landingPath("/zipstack", "zipstack", "tanushree")).toBe(
"/tanushree/dashboard",
);
expect(landingPath("/zipstack/", "zipstack", "tanushree")).toBe(
"/tanushree/dashboard",
);
});

test("falls back to the dashboard outside the current organisation's paths", () => {
expect(landingPath("/landing", "zipstack", "tanushree")).toBe(
"/tanushree/dashboard",
);
expect(landingPath("/otherorg/workflows", "zipstack", "tanushree")).toBe(
"/tanushree/dashboard",
);
});
Comment thread
athul-rs marked this conversation as resolved.
Resolved
});
82 changes: 82 additions & 0 deletions frontend/src/v1/lib/organizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/** Wording and navigation rules for the organisation switcher (page 19). */

// Stored roles: `admin`/`user` in OSS, `unstract_*` in cloud (account_v2/ReadMe.md).
const ROLE_LABELS: Record<string, string> = {
admin: "Admin",
unstract_admin: "Admin",
user: "Member",
unstract_user: "Member",
unstract_supervisor: "Supervisor",
unstract_reviewer: "Reviewer",
};

export function roleLabel(role: string | undefined): string | undefined {
return role === undefined ? undefined : ROLE_LABELS[role];
}

export interface OrganizationDetails {
role?: string;
member_count?: number;
region?: string;
}

/**
* "Admin · 40 members · US". The list leaves a field out when this deployment
* cannot answer for it (OSS has no regions), so a missing part is skipped.
* v1 shows the region code as the backend sends it, with no label mapping.
*/
export function describeOrganization({
role,
member_count,
region,
}: OrganizationDetails): string {
const members =
member_count === undefined
? undefined
: `${member_count} member${member_count === 1 ? "" : "s"}`;

Check warning on line 36 in frontend/src/v1/lib/organizations.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=Zipstack_unstract&issues=AaGBOz5eUvgOB0NM6fhk&open=AaGBOz5eUvgOB0NM6fhk&pullRequest=2395
return [roleLabel(role), members, region].filter(Boolean).join(" · ");
Comment thread
athul-rs marked this conversation as resolved.
Resolved
}

export function initials(name: string): string {
const words = name.trim().split(/\s+/).filter(Boolean);
if (words.length === 0) {
return "?";
}
return words
.slice(0, 2)
.map((word) => word[0].toUpperCase())
.join("");
}

/** One letter, for the small marks (the account trigger, the organisation pill). */
export function initial(name: string): string {
return name.trim()[0]?.toUpperCase() ?? "?";
}

/** Two letters for an organisation avatar; a one-word name gives its first two. */
export function organizationInitials(name: string): string {
const words = name.trim().split(/\s+/).filter(Boolean);
if (words.length === 0) {
return "?";
}
const letters =
words.length === 1 ? [...words[0]].slice(0, 2) : words.map((w) => w[0]);
return letters.slice(0, 2).join("").toUpperCase();
}

/**
* Where the app lands after switching: the same section in the new
* organisation, else its dashboard. Paths are `/<org name>/<section>/...`.
* ponytail: keeps only the section, since a record id below it belongs to the
* old organisation; ask the route registry which sections exist once it does.
*/
export function landingPath(
pathname: string,
fromOrg: string,
toOrg: string,
): string {
const [org, section] = pathname.split("/").filter(Boolean);
return org === fromOrg && section
? `/${toOrg}/${section}`
: `/${toOrg}/dashboard`;
}
75 changes: 75 additions & 0 deletions frontend/src/v1/services/organizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { http } from "@v1/lib/http";
import type { OrganizationDetails } from "@v1/lib/organizations";
import axios from "axios";

/** A row of GET /api/v1/organization. role, member_count and region may be absent. */
export interface Organization extends OrganizationDetails {
id: string;
/** The URL name: paths are /<name>/... */
name: string;
display_name: string;
}

/**
* A refusal the backend gives a meaning to. `session-ended`: the list call
* answered 412 after logging the session out; `body` may name the cause.
* `access-removed`: the set call answered 403, so the user left that
* organisation after the list loaded. Any other failure is thrown as is.
*/
export class OrganizationError extends Error {
readonly kind: "session-ended" | "access-removed";
readonly body: unknown;

constructor(kind: OrganizationError["kind"], body?: unknown) {
super(`Organisation request refused: ${kind}`);
this.name = "OrganizationError";
this.kind = kind;
this.body = body;
}
}

function refusal(
error: unknown,
status: number,
kind: OrganizationError["kind"],
): unknown {
return axios.isAxiosError(error) && error.response?.status === status
? new OrganizationError(kind, error.response.data)
: error;
}

export async function listOrganizations(): Promise<Organization[]> {
let data: { organizations?: unknown } | undefined;
try {
({ data } = await http.get<{ organizations?: unknown }>(
"/api/v1/organization",
));
} catch (error) {
throw refusal(error, 412, "session-ended");
}
// A proxy or SPA fallback can answer 200 with some other body.
if (!Array.isArray(data?.organizations)) {
throw new Error("Organization list reply carried no organizations");

Check warning on line 52 in frontend/src/v1/services/organizations.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`new Error()` is too unspecific for a type check. Use `new TypeError()` instead.

See more on https://sonarcloud.io/project/issues?id=Zipstack_unstract&issues=AaHQCizd669Cil2yon9D&open=AaHQCizd669Cil2yon9D&pullRequest=2395
}
return data.organizations as Organization[];
}
Comment thread
athul-rs marked this conversation as resolved.
Resolved

/** Makes the organisation the session's current one. */
export async function switchOrganization(id: string): Promise<void> {
try {
await http.post(`/api/v1/organization/${encodeURIComponent(id)}/set`);
} catch (error) {
throw refusal(error, 403, "access-removed");
}
}

/**
* Where to send the browser to switch when the identity provider picked the
* organisation at login (the session's `org_switch_via_login`): a new login
* naming it, so the provider records the choice for every product. `prompt=none`
* shows no screen, since the provider already has a session.
*/
export function organizationLoginUrl(id: string): string {
const query = new URLSearchParams({ organization: id, prompt: "none" });
return `/api/v1/login?${query.toString()}`;
}
Loading
Loading