Fix various bad tenant isolation
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -53,9 +53,6 @@ const MainLayoutQuery = graphql`
|
||||
fullName
|
||||
email
|
||||
}
|
||||
invitations(first: 1, filter: {statuses: [PENDING]}) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
organization: node(id: $organizationId) {
|
||||
... on Organization {
|
||||
@@ -258,49 +255,79 @@ interface OrganizationsResponse {
|
||||
organizations: Organization[];
|
||||
}
|
||||
|
||||
interface Invitation {
|
||||
id: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: string;
|
||||
expiresAt: string;
|
||||
acceptedAt?: string | null;
|
||||
createdAt: string;
|
||||
organization: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface InvitationsResponse {
|
||||
invitations: Invitation[];
|
||||
}
|
||||
|
||||
function OrganizationSelectorWrapper({ organizationId }: { organizationId: string }) {
|
||||
const data = useLazyLoadQuery<MainLayoutQueryType>(MainLayoutQuery, { organizationId });
|
||||
return <OrganizationSelector viewer={data.viewer} currentOrganization={data.organization} />;
|
||||
return <OrganizationSelector currentOrganization={data.organization} />;
|
||||
}
|
||||
|
||||
function OrganizationSelector({
|
||||
viewer,
|
||||
currentOrganization
|
||||
}: {
|
||||
viewer: MainLayoutQueryType["response"]["viewer"];
|
||||
currentOrganization: MainLayoutQueryType["response"]["organization"];
|
||||
}) {
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
const [pendingInvitationsCount, setPendingInvitationsCount] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const pendingInvitationsCount = viewer.invitations.totalCount;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrganizations = async () => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetch('/auth/organizations', {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Fetch organizations and invitations in parallel
|
||||
const [orgsResponse, invitationsResponse] = await Promise.all([
|
||||
fetch('/auth/organizations', { credentials: 'include' }),
|
||||
fetch('/auth/invitations', { credentials: 'include' })
|
||||
]);
|
||||
|
||||
if (!orgsResponse.ok) {
|
||||
throw new Error('Failed to fetch organizations');
|
||||
}
|
||||
|
||||
const data: OrganizationsResponse = await response.json();
|
||||
setOrganizations(data.organizations);
|
||||
if (!invitationsResponse.ok) {
|
||||
throw new Error('Failed to fetch invitations');
|
||||
}
|
||||
|
||||
const orgsData: OrganizationsResponse = await orgsResponse.json();
|
||||
const invitationsData: InvitationsResponse = await invitationsResponse.json();
|
||||
|
||||
// Count pending invitations (those without acceptedAt)
|
||||
const pendingCount = invitationsData.invitations.filter(
|
||||
inv => !inv.acceptedAt
|
||||
).length;
|
||||
|
||||
setOrganizations(orgsData.organizations);
|
||||
setPendingInvitationsCount(pendingCount);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
console.error('Failed to fetch organizations:', err);
|
||||
console.error('Failed to fetch data:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchOrganizations();
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<26b3620e6aed7f97ffb1710be1eb267a>>
|
||||
* @generated SignedSource<<b7983d3d3c089aa0efebaab639de76fb>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -20,9 +20,6 @@ export type MainLayoutQuery$data = {
|
||||
};
|
||||
readonly viewer: {
|
||||
readonly id: string;
|
||||
readonly invitations: {
|
||||
readonly totalCount: number;
|
||||
};
|
||||
readonly user: {
|
||||
readonly email: string;
|
||||
readonly fullName: string;
|
||||
@@ -63,54 +60,21 @@ v3 = {
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "filter",
|
||||
"value": {
|
||||
"statuses": [
|
||||
"PENDING"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "InvitationConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "invitations",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "totalCount",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "invitations(filter:{\"statuses\":[\"PENDING\"]},first:1)"
|
||||
},
|
||||
v5 = [
|
||||
v4 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v6 = {
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
@@ -145,14 +109,13 @@ return {
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v5/*: any*/),
|
||||
"args": (v4/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
@@ -162,8 +125,8 @@ return {
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
@@ -203,14 +166,13 @@ return {
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v5/*: any*/),
|
||||
"args": (v4/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
@@ -227,8 +189,8 @@ return {
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
@@ -239,16 +201,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "a8f9f58d27677c55b5a217617db83e27",
|
||||
"cacheID": "ee5a60e709dee856df7d2fef13974c9f",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MainLayoutQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MainLayoutQuery(\n $organizationId: ID!\n) {\n viewer {\n id\n user {\n fullName\n email\n id\n }\n invitations(first: 1, filter: {statuses: [PENDING]}) {\n totalCount\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n logoUrl\n }\n id\n }\n}\n"
|
||||
"text": "query MainLayoutQuery(\n $organizationId: ID!\n) {\n viewer {\n id\n user {\n fullName\n email\n id\n }\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n logoUrl\n }\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "17986fcea321c4567d86584d1a9f89c1";
|
||||
(node as any).hash = "9ea3e5a91a2d2be0993e7deebafa11b0";
|
||||
|
||||
export default node;
|
||||
|
||||
Reference in New Issue
Block a user