Move page loaders in lazy components + plug viewer membership
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
22
apps/console/src/pages/iam/auth/AuthLayout.tsx
Normal file
22
apps/console/src/pages/iam/auth/AuthLayout.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Logo } from "@probo/ui";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 min-h-screen text-txt-primary">
|
||||||
|
<div className="bg-level-0 flex flex-col items-center justify-center">
|
||||||
|
<div className="w-full max-w-md px-6">
|
||||||
|
<IAMRelayProvider>
|
||||||
|
<Outlet />
|
||||||
|
</IAMRelayProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hidden lg:flex bg-dialog font-bold flex-col items-center justify-center p-8 text-txt-primary lg:p-10">
|
||||||
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
|
<Logo withPicto className="w-[440px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ const signInMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO initial screen + SAML login
|
// TODO initial screen + SAML login + after sign in URL state
|
||||||
export default function SignInPage() {
|
export default function SignInPage() {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
import { graphql, usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
import { membershipLayoutQuery } from "./MembershipLayoutQuery";
|
|
||||||
import { Link, Outlet } from "react-router";
|
import { Link, Outlet } from "react-router";
|
||||||
import { Badge, Button, IconPeopleAdd, Layout, Skeleton } from "@probo/ui";
|
import { Badge, Button, IconPeopleAdd, Layout, Skeleton } from "@probo/ui";
|
||||||
import { Sidebar } from "./_components/Sidebar";
|
import { Sidebar } from "./_components/Sidebar";
|
||||||
@@ -11,7 +10,24 @@ import { Suspense } from "react";
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { CoreRelayProvider } from "/providers/CoreRelayProvider";
|
import { CoreRelayProvider } from "/providers/CoreRelayProvider";
|
||||||
|
|
||||||
export default function MembershipLayout(props: {
|
export const membershipLayoutQuery = graphql`
|
||||||
|
query MembershipLayoutQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) @required(action: THROW) {
|
||||||
|
... on Organization {
|
||||||
|
...MembershipsDropdown_organizationFragment
|
||||||
|
...SessionDropdownFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewer @required(action: THROW) {
|
||||||
|
...MembershipsDropdown_viewerFragment
|
||||||
|
pendingInvitations @required(action: THROW) {
|
||||||
|
totalCount @required(action: THROW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function MembershipLayout(props: {
|
||||||
queryRef: PreloadedQuery<MembershipLayoutQuery>;
|
queryRef: PreloadedQuery<MembershipLayoutQuery>;
|
||||||
}) {
|
}) {
|
||||||
const { queryRef } = props;
|
const { queryRef } = props;
|
||||||
@@ -47,7 +63,7 @@ export default function MembershipLayout(props: {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<Skeleton className="w-32 h-8" />}>
|
<Suspense fallback={<Skeleton className="w-32 h-8" />}>
|
||||||
<SessionDropdown fKey={viewer} />
|
<SessionDropdown fKey={organization} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { Skeleton } from "@probo/ui";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import type { MembershipLayoutQuery } from "./__generated__/MembershipLayoutQuery.graphql";
|
||||||
|
import { MembershipLayout, membershipLayoutQuery } from "./MembershipLayout";
|
||||||
|
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
||||||
|
|
||||||
|
function MembershipLayoutLoader() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<MembershipLayoutQuery>(
|
||||||
|
membershipLayoutQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
|
if (!queryRef) {
|
||||||
|
return <Skeleton className="w-full h-screen" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Skeleton className="w-full h-screen" />}>
|
||||||
|
<MembershipLayout queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return (
|
||||||
|
<IAMRelayProvider>
|
||||||
|
<MembershipLayoutLoader />
|
||||||
|
</IAMRelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { lazy } from "@probo/react-lazy";
|
|
||||||
import { useQueryLoader } from "react-relay";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { Suspense, useEffect } from "react";
|
|
||||||
import { Skeleton } from "@probo/ui";
|
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
|
||||||
import type { MembershipLayoutQuery } from "./__generated__/MembershipLayoutQuery.graphql";
|
|
||||||
|
|
||||||
const Layout = lazy(() => import("./MembershipLayout"));
|
|
||||||
|
|
||||||
export const membershipLayoutQuery = graphql`
|
|
||||||
query MembershipLayoutQuery($organizationId: ID!) {
|
|
||||||
organization: node(id: $organizationId) @required(action: THROW) {
|
|
||||||
... on Organization {
|
|
||||||
...MembershipsDropdown_organizationFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewer @required(action: THROW) {
|
|
||||||
...MembershipsDropdown_viewerFragment
|
|
||||||
pendingInvitations @required(action: THROW) {
|
|
||||||
totalCount @required(action: THROW)
|
|
||||||
}
|
|
||||||
...SessionDropdownFragment @arguments(organizationId: $organizationId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function MembershipLayoutQuery() {
|
|
||||||
const organizationId = useOrganizationId();
|
|
||||||
const [queryRef, loadQuery] = useQueryLoader<MembershipLayoutQuery>(
|
|
||||||
membershipLayoutQuery,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadQuery({
|
|
||||||
organizationId,
|
|
||||||
});
|
|
||||||
}, [loadQuery, organizationId]);
|
|
||||||
|
|
||||||
if (!queryRef) {
|
|
||||||
return <Skeleton className="w-full h-screen" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<Skeleton className="w-full h-screen" />}>
|
|
||||||
<Layout queryRef={queryRef} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
import { graphql, usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -9,15 +9,42 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { InvitationCard } from "./_components/InvitationCard";
|
import { InvitationCard } from "./_components/InvitationCard";
|
||||||
import { membershipsPageQuery } from "./MembershipsPageQuery";
|
|
||||||
import { MembershipCard } from "./_components/MembershipCard";
|
import { MembershipCard } from "./_components/MembershipCard";
|
||||||
import type { MembershipsPageQuery } from "./__generated__/MembershipsPageQuery.graphql";
|
import type { MembershipsPageQuery } from "./__generated__/MembershipsPageQuery.graphql";
|
||||||
|
|
||||||
interface PageProps {
|
export const membershipsPageQuery = graphql`
|
||||||
queryRef: PreloadedQuery<MembershipsPageQuery>;
|
query MembershipsPageQuery {
|
||||||
|
viewer @required(action: THROW) {
|
||||||
|
memberships(first: 1000, orderBy: { direction: DESC, field: CREATED_AT })
|
||||||
|
@required(action: THROW) {
|
||||||
|
edges @required(action: THROW) {
|
||||||
|
node @required(action: THROW) {
|
||||||
|
id
|
||||||
|
...MembershipCardFragment
|
||||||
|
organization @required(action: THROW) {
|
||||||
|
name
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pendingInvitations(
|
||||||
|
first: 1000
|
||||||
|
orderBy: { direction: DESC, field: CREATED_AT }
|
||||||
|
) @required(action: THROW) {
|
||||||
|
edges @required(action: THROW) {
|
||||||
|
node @required(action: THROW) {
|
||||||
|
id
|
||||||
|
...InvitationCardFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export default function MembershipsPage(props: PageProps) {
|
export function MembershipsPage(props: {
|
||||||
|
queryRef: PreloadedQuery<MembershipsPageQuery>;
|
||||||
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { CenteredLayoutSkeleton } from "@probo/ui";
|
||||||
|
import type { MembershipsPageQuery } from "./__generated__/MembershipsPageQuery.graphql";
|
||||||
|
import { MembershipsPage, membershipsPageQuery } from "./MembershipsPage";
|
||||||
|
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
||||||
|
|
||||||
|
function MembershipsPageLoader() {
|
||||||
|
const [queryRef, loadQuery] =
|
||||||
|
useQueryLoader<MembershipsPageQuery>(membershipsPageQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({});
|
||||||
|
}, [loadQuery]);
|
||||||
|
|
||||||
|
if (!queryRef) {
|
||||||
|
return <CenteredLayoutSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<CenteredLayoutSkeleton />}>
|
||||||
|
<MembershipsPage queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return (
|
||||||
|
<IAMRelayProvider>
|
||||||
|
<MembershipsPageLoader />
|
||||||
|
</IAMRelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { lazy } from "@probo/react-lazy";
|
|
||||||
import { useQueryLoader } from "react-relay";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { Suspense, useEffect } from "react";
|
|
||||||
import { CenteredLayoutSkeleton } from "@probo/ui";
|
|
||||||
import type { MembershipsPageQuery } from "./__generated__/MembershipsPageQuery.graphql";
|
|
||||||
|
|
||||||
const Page = lazy(() => import("./MembershipsPage"));
|
|
||||||
|
|
||||||
export const membershipsPageQuery = graphql`
|
|
||||||
query MembershipsPageQuery {
|
|
||||||
viewer @required(action: THROW) {
|
|
||||||
memberships(first: 1000, orderBy: { direction: DESC, field: CREATED_AT })
|
|
||||||
@required(action: THROW) {
|
|
||||||
edges @required(action: THROW) {
|
|
||||||
node @required(action: THROW) {
|
|
||||||
id
|
|
||||||
...MembershipCardFragment
|
|
||||||
organization @required(action: THROW) {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pendingInvitations(
|
|
||||||
first: 1000
|
|
||||||
orderBy: { direction: DESC, field: CREATED_AT }
|
|
||||||
) @required(action: THROW) {
|
|
||||||
edges @required(action: THROW) {
|
|
||||||
node @required(action: THROW) {
|
|
||||||
id
|
|
||||||
...InvitationCardFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function MembershipsPageQuery() {
|
|
||||||
const [queryRef, loadQuery] =
|
|
||||||
useQueryLoader<MembershipsPageQuery>(membershipsPageQuery);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadQuery({});
|
|
||||||
}, [loadQuery]);
|
|
||||||
|
|
||||||
if (!queryRef) {
|
|
||||||
return <CenteredLayoutSkeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<CenteredLayoutSkeleton />}>
|
|
||||||
<Page queryRef={queryRef} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<4b0d5d271f19e23939fc2b392aa79fbc>>
|
* @generated SignedSource<<5ad02f9e0c07f5e9900005150f93d6fe>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -15,13 +15,13 @@ export type MembershipLayoutQuery$variables = {
|
|||||||
};
|
};
|
||||||
export type MembershipLayoutQuery$data = {
|
export type MembershipLayoutQuery$data = {
|
||||||
readonly organization: {
|
readonly organization: {
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment">;
|
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment">;
|
||||||
};
|
};
|
||||||
readonly viewer: {
|
readonly viewer: {
|
||||||
readonly pendingInvitations: {
|
readonly pendingInvitations: {
|
||||||
readonly totalCount: number;
|
readonly totalCount: number;
|
||||||
};
|
};
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_viewerFragment" | "SessionDropdownFragment">;
|
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_viewerFragment">;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type MembershipLayoutQuery = {
|
export type MembershipLayoutQuery = {
|
||||||
@@ -51,14 +51,7 @@ v2 = {
|
|||||||
"name": "totalCount",
|
"name": "totalCount",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v3 = [
|
v3 = {
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "organizationId",
|
|
||||||
"variableName": "organizationId"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v4 = {
|
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
@@ -89,6 +82,11 @@ return {
|
|||||||
"args": null,
|
"args": null,
|
||||||
"kind": "FragmentSpread",
|
"kind": "FragmentSpread",
|
||||||
"name": "MembershipsDropdown_organizationFragment"
|
"name": "MembershipsDropdown_organizationFragment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "SessionDropdownFragment"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Organization",
|
"type": "Organization",
|
||||||
@@ -133,11 +131,6 @@ return {
|
|||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
"action": "THROW"
|
"action": "THROW"
|
||||||
},
|
|
||||||
{
|
|
||||||
"args": (v3/*: any*/),
|
|
||||||
"kind": "FragmentSpread",
|
|
||||||
"name": "SessionDropdownFragment"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -178,12 +171,62 @@ return {
|
|||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Membership",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewerMembership",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "identity",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "email",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "MembershipProfile",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "profile",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fullName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Organization",
|
"type": "Organization",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
},
|
},
|
||||||
(v4/*: any*/)
|
(v3/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
@@ -207,56 +250,23 @@ return {
|
|||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
(v3/*: any*/)
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "email",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": (v3/*: any*/),
|
|
||||||
"concreteType": "IdentityProfile",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "profileFor",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "firstName",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "lastName",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
(v4/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
(v4/*: any*/)
|
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "a849dd96b5088ddc9a3bbab50273c411",
|
"cacheID": "1fcebe9376b82798d829d98e2bdabaf3",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "MembershipLayoutQuery",
|
"name": "MembershipLayoutQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query MembershipLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n }\n id\n }\n viewer {\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n ...SessionDropdownFragment_4xMPKw\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment_4xMPKw on Identity {\n email\n profileFor(organizationId: $organizationId) {\n firstName\n lastName\n id\n }\n}\n"
|
"text": "query MembershipLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n }\n id\n }\n viewer {\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "e28f1f5e510386718b39cd547d4ce59f";
|
(node as any).hash = "c703bc4dce3307a22e42b5fa528537b2";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import type { SessionDropdownFragment$key } from "./__generated__/SessionDropdownFragment.graphql";
|
import type { SessionDropdownFragment$key } from "./__generated__/SessionDropdownFragment.graphql";
|
||||||
|
|
||||||
export const fragment = graphql`
|
export const fragment = graphql`
|
||||||
fragment SessionDropdownFragment on Identity
|
fragment SessionDropdownFragment on Organization {
|
||||||
@argumentDefinitions(organizationId: { type: "ID!" }) {
|
viewerMembership @required(action: THROW) {
|
||||||
|
identity @required(action: THROW) {
|
||||||
email
|
email
|
||||||
profileFor(organizationId: $organizationId) {
|
}
|
||||||
firstName
|
profile @required(action: THROW) {
|
||||||
lastName
|
fullName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -43,8 +45,12 @@ export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) {
|
|||||||
const { isAuthorized } = use(PermissionsContext);
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { email, profileFor: profile } =
|
const {
|
||||||
useFragment<SessionDropdownFragment$key>(fragment, fKey);
|
viewerMembership: {
|
||||||
|
identity: { email },
|
||||||
|
profile: { fullName },
|
||||||
|
},
|
||||||
|
} = useFragment<SessionDropdownFragment$key>(fragment, fKey);
|
||||||
const [signOut] = useMutation(signOutMutation);
|
const [signOut] = useMutation(signOutMutation);
|
||||||
|
|
||||||
const handleLogout: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
const handleLogout: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
||||||
@@ -65,11 +71,6 @@ export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: user should always have a default profile
|
|
||||||
const fullName = profile?.firstName
|
|
||||||
? `${profile.firstName} ${profile.lastName}`
|
|
||||||
: email;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserDropdown fullName={fullName} email={email}>
|
<UserDropdown fullName={fullName} email={email}>
|
||||||
{isAuthorized("Organization", "deleteOrganization") && (
|
{isAuthorized("Organization", "deleteOrganization") && (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<ccb8e318151fff4495fb2eaacc671612>>
|
* @generated SignedSource<<0d0906bc941900bb200a6a9824aaacc4>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,11 +11,14 @@
|
|||||||
import { ReaderFragment } from 'relay-runtime';
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
import { FragmentRefs } from "relay-runtime";
|
import { FragmentRefs } from "relay-runtime";
|
||||||
export type SessionDropdownFragment$data = {
|
export type SessionDropdownFragment$data = {
|
||||||
|
readonly viewerMembership: {
|
||||||
|
readonly identity: {
|
||||||
readonly email: any;
|
readonly email: any;
|
||||||
readonly profileFor: {
|
};
|
||||||
readonly firstName: string | null | undefined;
|
readonly profile: {
|
||||||
readonly lastName: string | null | undefined;
|
readonly fullName: string;
|
||||||
} | null | undefined;
|
};
|
||||||
|
};
|
||||||
readonly " $fragmentType": "SessionDropdownFragment";
|
readonly " $fragmentType": "SessionDropdownFragment";
|
||||||
};
|
};
|
||||||
export type SessionDropdownFragment$key = {
|
export type SessionDropdownFragment$key = {
|
||||||
@@ -24,16 +27,30 @@ export type SessionDropdownFragment$key = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const node: ReaderFragment = {
|
const node: ReaderFragment = {
|
||||||
"argumentDefinitions": [
|
"argumentDefinitions": [],
|
||||||
{
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "organizationId"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "SessionDropdownFragment",
|
"name": "SessionDropdownFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Membership",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewerMembership",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "identity",
|
||||||
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -41,43 +58,44 @@ const node: ReaderFragment = {
|
|||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "organizationId",
|
|
||||||
"variableName": "organizationId"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"concreteType": "IdentityProfile",
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "MembershipProfile",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "profileFor",
|
"name": "profile",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "firstName",
|
"name": "fullName",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
"action": "THROW"
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "lastName",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Identity",
|
"type": "Organization",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
};
|
};
|
||||||
|
|
||||||
(node as any).hash = "de1d0f069e5648898cab6a56a394d86f";
|
(node as any).hash = "c283fcefcab15b977b44ece82f7cf450";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { FormEventHandler } from "react";
|
|||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { formatError, type GraphQLError } from "@probo/helpers";
|
import { formatError, type GraphQLError } from "@probo/helpers";
|
||||||
import type { NewOrganizationPageMutation } from "./__generated__/NewOrganizationPageMutation.graphql";
|
import type { NewOrganizationPageMutation } from "./__generated__/NewOrganizationPageMutation.graphql";
|
||||||
|
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
||||||
|
|
||||||
const createOrganizationMutation = graphql`
|
const createOrganizationMutation = graphql`
|
||||||
mutation NewOrganizationPageMutation($input: CreateOrganizationInput!) {
|
mutation NewOrganizationPageMutation($input: CreateOrganizationInput!) {
|
||||||
@@ -17,7 +18,7 @@ const createOrganizationMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function Page() {
|
function NewOrganizationPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
@@ -97,3 +98,11 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return (
|
||||||
|
<IAMRelayProvider>
|
||||||
|
<NewOrganizationPage />
|
||||||
|
</IAMRelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,23 @@ import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
|||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql";
|
import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql";
|
||||||
import { generalSettingsPageQuery } from "./GeneralSettingsPageQuery";
|
|
||||||
import type { GeneralSettingsPage_deleteMutation } from "./__generated__/GeneralSettingsPage_deleteMutation.graphql";
|
import type { GeneralSettingsPage_deleteMutation } from "./__generated__/GeneralSettingsPage_deleteMutation.graphql";
|
||||||
import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog";
|
import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog";
|
||||||
import { OrganizationForm } from "./_components/OrganizationForm";
|
import { OrganizationForm } from "./_components/OrganizationForm";
|
||||||
|
|
||||||
|
export const generalSettingsPageQuery = graphql`
|
||||||
|
query GeneralSettingsPageQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) @required(action: THROW) {
|
||||||
|
__typename
|
||||||
|
... on Organization {
|
||||||
|
id
|
||||||
|
name @required(action: THROW)
|
||||||
|
...OrganizationFormFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const deleteOrganizationMutation = graphql`
|
const deleteOrganizationMutation = graphql`
|
||||||
mutation GeneralSettingsPage_deleteMutation(
|
mutation GeneralSettingsPage_deleteMutation(
|
||||||
$input: DeleteOrganizationInput!
|
$input: DeleteOrganizationInput!
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { useQueryLoader } from "react-relay";
|
import { useQueryLoader } from "react-relay";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { GeneralSettingsPage } from "./GeneralSettingsPage";
|
import {
|
||||||
|
GeneralSettingsPage,
|
||||||
|
generalSettingsPageQuery,
|
||||||
|
} from "./GeneralSettingsPage";
|
||||||
import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql";
|
import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql";
|
||||||
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
|
||||||
|
|
||||||
export const generalSettingsPageQuery = graphql`
|
function GeneralSettingsPageLoader() {
|
||||||
query GeneralSettingsPageQuery($organizationId: ID!) {
|
|
||||||
organization: node(id: $organizationId) @required(action: THROW) {
|
|
||||||
__typename
|
|
||||||
... on Organization {
|
|
||||||
id
|
|
||||||
name @required(action: THROW)
|
|
||||||
...OrganizationFormFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function GeneralSettingsPageQuery() {
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const [queryRef, loadQuery] = useQueryLoader<GeneralSettingsPageQuery>(
|
const [queryRef, loadQuery] = useQueryLoader<GeneralSettingsPageQuery>(
|
||||||
generalSettingsPageQuery,
|
generalSettingsPageQuery,
|
||||||
@@ -41,7 +30,7 @@ function GeneralSettingsPageQuery() {
|
|||||||
export default function () {
|
export default function () {
|
||||||
return (
|
return (
|
||||||
<IAMRelayProvider>
|
<IAMRelayProvider>
|
||||||
<GeneralSettingsPageQuery />
|
<GeneralSettingsPageLoader />
|
||||||
</IAMRelayProvider>
|
</IAMRelayProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import { Outlet } from "react-router";
|
|||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
|
||||||
export default function SettingsLayout() {
|
export default function () {
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
useRouteError,
|
useRouteError,
|
||||||
} from "react-router";
|
} from "react-router";
|
||||||
import { EmployeeLayout } from "./layouts/EmployeeLayout";
|
import { EmployeeLayout } from "./layouts/EmployeeLayout";
|
||||||
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
import { CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
||||||
import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx";
|
import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx";
|
||||||
import { loadQuery } from "react-relay";
|
import { loadQuery } from "react-relay";
|
||||||
import { riskRoutes } from "./routes/riskRoutes.ts";
|
import { riskRoutes } from "./routes/riskRoutes.ts";
|
||||||
@@ -46,13 +46,6 @@ import {
|
|||||||
UnAuthenticatedError,
|
UnAuthenticatedError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
} from "@probo/relay";
|
} from "@probo/relay";
|
||||||
import { MembershipsPageQuery } from "./pages/iam/memberships/MembershipsPageQuery.tsx";
|
|
||||||
import { MembershipLayoutQuery } from "./pages/iam/memberships/MembershipLayoutQuery.tsx";
|
|
||||||
import { IAMRelayProvider } from "./providers/IAMRelayProvider.tsx";
|
|
||||||
|
|
||||||
const NewOrganizationPage = lazy(
|
|
||||||
() => import("./pages/iam/organizations/NewOrganizationPage"),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level error boundary
|
* Top level error boundary
|
||||||
@@ -78,11 +71,7 @@ function ErrorBoundary({ error: propsError }: { error?: string }) {
|
|||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/auth",
|
path: "/auth",
|
||||||
Component: () => (
|
Component: lazy(() => import("./pages/iam/auth/AuthLayout")),
|
||||||
<IAMRelayProvider>
|
|
||||||
<AuthLayout />
|
|
||||||
</IAMRelayProvider>
|
|
||||||
),
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "login",
|
path: "login",
|
||||||
@@ -118,18 +107,14 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
Component: () => (
|
Component: lazy(
|
||||||
<IAMRelayProvider>
|
() => import("./pages/iam/memberships/MembershipsPageLoader"),
|
||||||
<MembershipsPageQuery />
|
|
||||||
</IAMRelayProvider>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "organizations/new",
|
path: "organizations/new",
|
||||||
Component: () => (
|
Component: lazy(
|
||||||
<IAMRelayProvider>
|
() => import("./pages/iam/organizations/NewOrganizationPage"),
|
||||||
<NewOrganizationPage />
|
|
||||||
</IAMRelayProvider>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -184,10 +169,8 @@ const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/organizations/:organizationId",
|
path: "/organizations/:organizationId",
|
||||||
Component: () => (
|
Component: lazy(
|
||||||
<IAMRelayProvider>
|
() => import("./pages/iam/memberships/MembershipLayoutLoader.tsx"),
|
||||||
<MembershipLayoutQuery />
|
|
||||||
</IAMRelayProvider>
|
|
||||||
),
|
),
|
||||||
ErrorBoundary: ErrorBoundary,
|
ErrorBoundary: ErrorBoundary,
|
||||||
children: [
|
children: [
|
||||||
@@ -222,7 +205,7 @@ const routes = [
|
|||||||
path: "general",
|
path: "general",
|
||||||
Component: lazy(
|
Component: lazy(
|
||||||
() =>
|
() =>
|
||||||
import("./pages/iam/organizations/settings/GeneralSettingsPageQuery.tsx"),
|
import("./pages/iam/organizations/settings/GeneralSettingsPageLoader.tsx"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Outlet } from "react-router";
|
|
||||||
import { Logo } from "../Atoms/Logo/Logo";
|
|
||||||
|
|
||||||
export function AuthLayout() {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 min-h-screen text-txt-primary">
|
|
||||||
<div className="bg-level-0 flex flex-col items-center justify-center">
|
|
||||||
<div className="w-full max-w-md px-6">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="hidden lg:flex bg-dialog font-bold flex flex-col items-center justify-center p-8 text-txt-primary lg:p-10">
|
|
||||||
<div className="flex flex-col items-center justify-center gap-4">
|
|
||||||
<Logo withPicto className="w-[440px]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// Layouts
|
// Layouts
|
||||||
export { Layout, Drawer } from "./Layouts/Layout";
|
export { Layout, Drawer } from "./Layouts/Layout";
|
||||||
export { AuthLayout } from "./Layouts/AuthLayout";
|
|
||||||
export { ErrorLayout } from "./Layouts/ErrorLayout";
|
export { ErrorLayout } from "./Layouts/ErrorLayout";
|
||||||
export {
|
export {
|
||||||
CenteredLayout,
|
CenteredLayout,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package iam
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -715,6 +716,41 @@ func (s AccountService) ListOrganizations(ctx context.Context, identityID gid.GI
|
|||||||
return organizations, nil
|
return organizations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s AccountService) GetMembershipForOrganization(
|
||||||
|
ctx context.Context,
|
||||||
|
identityID gid.GID,
|
||||||
|
organizationID gid.GID,
|
||||||
|
) (*coredata.Membership, error) {
|
||||||
|
membership := &coredata.Membership{}
|
||||||
|
|
||||||
|
err := s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(tx pg.Conn) error {
|
||||||
|
identity := &coredata.Identity{}
|
||||||
|
|
||||||
|
if err := identity.LoadByID(ctx, tx, identityID); err != nil {
|
||||||
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
|
return NewIdentityNotFoundError(identityID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot load identity %q: %w", identityID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := membership.LoadByIdentityInOrganization(ctx, tx, identityID, organizationID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return membership, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s AccountService) GetProfileForMembership(ctx context.Context, membershipID gid.GID) (*coredata.MembershipProfile, error) {
|
func (s AccountService) GetProfileForMembership(ctx context.Context, membershipID gid.GID) (*coredata.MembershipProfile, error) {
|
||||||
var (
|
var (
|
||||||
scope = coredata.NewScopeFromObjectID(membershipID)
|
scope = coredata.NewScopeFromObjectID(membershipID)
|
||||||
|
|||||||
@@ -208,6 +208,8 @@ type Organization implements Node {
|
|||||||
last: Int
|
last: Int
|
||||||
before: CursorKey
|
before: CursorKey
|
||||||
): SAMLConfigurationConnection @goField(forceResolver: true)
|
): SAMLConfigurationConnection @goField(forceResolver: true)
|
||||||
|
|
||||||
|
viewerMembership: Membership @goField(forceResolver: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MembershipRole
|
enum MembershipRole
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ type ComplexityRoot struct {
|
|||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
SamlConfigurations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
SamlConfigurations func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||||
UpdatedAt func(childComplexity int) int
|
UpdatedAt func(childComplexity int) int
|
||||||
|
ViewerMembership func(childComplexity int) int
|
||||||
WebsiteURL func(childComplexity int) int
|
WebsiteURL func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +478,7 @@ type OrganizationResolver interface {
|
|||||||
Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error)
|
Members(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MembershipOrderBy) (*types.MembershipConnection, error)
|
||||||
Invitations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, status *coredata.InvitationStatus) (*types.InvitationConnection, error)
|
Invitations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, status *coredata.InvitationStatus) (*types.InvitationConnection, error)
|
||||||
SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error)
|
SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error)
|
||||||
|
ViewerMembership(ctx context.Context, obj *types.Organization) (*types.Membership, error)
|
||||||
}
|
}
|
||||||
type PersonalAPIKeyConnectionResolver interface {
|
type PersonalAPIKeyConnectionResolver interface {
|
||||||
TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error)
|
TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error)
|
||||||
@@ -1282,6 +1284,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Organization.UpdatedAt(childComplexity), true
|
return e.complexity.Organization.UpdatedAt(childComplexity), true
|
||||||
|
case "Organization.viewerMembership":
|
||||||
|
if e.complexity.Organization.ViewerMembership == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Organization.ViewerMembership(childComplexity), true
|
||||||
case "Organization.websiteUrl":
|
case "Organization.websiteUrl":
|
||||||
if e.complexity.Organization.WebsiteURL == nil {
|
if e.complexity.Organization.WebsiteURL == nil {
|
||||||
break
|
break
|
||||||
@@ -2189,6 +2197,8 @@ type Organization implements Node {
|
|||||||
last: Int
|
last: Int
|
||||||
before: CursorKey
|
before: CursorKey
|
||||||
): SAMLConfigurationConnection @goField(forceResolver: true)
|
): SAMLConfigurationConnection @goField(forceResolver: true)
|
||||||
|
|
||||||
|
viewerMembership: Membership @goField(forceResolver: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MembershipRole
|
enum MembershipRole
|
||||||
@@ -3621,6 +3631,8 @@ func (ec *executionContext) fieldContext_CreateOrganizationPayload_organization(
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -3870,6 +3882,8 @@ func (ec *executionContext) fieldContext_DeleteOrganizationHorizontalLogoPayload
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -4639,6 +4653,8 @@ func (ec *executionContext) fieldContext_Invitation_organization(_ context.Conte
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -5071,6 +5087,8 @@ func (ec *executionContext) fieldContext_Membership_organization(_ context.Conte
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -7562,6 +7580,53 @@ func (ec *executionContext) fieldContext_Organization_samlConfigurations(ctx con
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Organization_viewerMembership(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
|
||||||
|
return graphql.ResolveField(
|
||||||
|
ctx,
|
||||||
|
ec.OperationContext,
|
||||||
|
field,
|
||||||
|
ec.fieldContext_Organization_viewerMembership,
|
||||||
|
func(ctx context.Context) (any, error) {
|
||||||
|
return ec.resolvers.Organization().ViewerMembership(ctx, obj)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
ec.marshalOMembership2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐMembership,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_Organization_viewerMembership(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "Organization",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: true,
|
||||||
|
IsResolver: true,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
switch field.Name {
|
||||||
|
case "id":
|
||||||
|
return ec.fieldContext_Membership_id(ctx, field)
|
||||||
|
case "createdAt":
|
||||||
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
|
case "identity":
|
||||||
|
return ec.fieldContext_Membership_identity(ctx, field)
|
||||||
|
case "profile":
|
||||||
|
return ec.fieldContext_Membership_profile(ctx, field)
|
||||||
|
case "organization":
|
||||||
|
return ec.fieldContext_Membership_organization(ctx, field)
|
||||||
|
case "role":
|
||||||
|
return ec.fieldContext_Membership_role(ctx, field)
|
||||||
|
case "permissions":
|
||||||
|
return ec.fieldContext_Membership_permissions(ctx, field)
|
||||||
|
case "lastSession":
|
||||||
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no field named %q was found under type Membership", field.Name)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _OrganizationSessionCreated_session(ctx context.Context, field graphql.CollectedField, obj *types.OrganizationSessionCreated) (ret graphql.Marshaler) {
|
func (ec *executionContext) _OrganizationSessionCreated_session(ctx context.Context, field graphql.CollectedField, obj *types.OrganizationSessionCreated) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -7975,6 +8040,8 @@ func (ec *executionContext) fieldContext_Permission_organization(_ context.Conte
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -8332,6 +8399,8 @@ func (ec *executionContext) fieldContext_PersonalAPIKey_organizations(_ context.
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -10561,6 +10630,8 @@ func (ec *executionContext) fieldContext_UpdateOrganizationPayload_organization(
|
|||||||
return ec.fieldContext_Organization_invitations(ctx, field)
|
return ec.fieldContext_Organization_invitations(ctx, field)
|
||||||
case "samlConfigurations":
|
case "samlConfigurations":
|
||||||
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
return ec.fieldContext_Organization_samlConfigurations(ctx, field)
|
||||||
|
case "viewerMembership":
|
||||||
|
return ec.fieldContext_Organization_viewerMembership(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
|
||||||
},
|
},
|
||||||
@@ -15059,6 +15130,39 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
||||||
|
case "viewerMembership":
|
||||||
|
field := field
|
||||||
|
|
||||||
|
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
res = ec._Organization_viewerMembership(ctx, field, obj)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Deferrable != nil {
|
||||||
|
dfs, ok := deferred[field.Deferrable.Label]
|
||||||
|
di := 0
|
||||||
|
if ok {
|
||||||
|
dfs.AddField(field)
|
||||||
|
di = len(dfs.Values) - 1
|
||||||
|
} else {
|
||||||
|
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
|
||||||
|
deferred[field.Deferrable.Label] = dfs
|
||||||
|
}
|
||||||
|
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
|
||||||
|
return innerFunc(ctx, dfs)
|
||||||
|
})
|
||||||
|
|
||||||
|
// don't run the out.Concurrently() call below
|
||||||
|
out.Values[i] = graphql.Null
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
@@ -18731,6 +18835,13 @@ func (ec *executionContext) marshalOInviteMemberPayload2ᚖgoᚗproboᚗincᚋpr
|
|||||||
return ec._InviteMemberPayload(ctx, sel, v)
|
return ec._InviteMemberPayload(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOMembership2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐMembership(ctx context.Context, sel ast.SelectionSet, v *types.Membership) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._Membership(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalOMembershipConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐMembershipConnection(ctx context.Context, sel ast.SelectionSet, v *types.MembershipConnection) graphql.Marshaler {
|
func (ec *executionContext) marshalOMembershipConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐMembershipConnection(ctx context.Context, sel ast.SelectionSet, v *types.MembershipConnection) graphql.Marshaler {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ type Organization struct {
|
|||||||
Members *MembershipConnection `json:"members,omitempty"`
|
Members *MembershipConnection `json:"members,omitempty"`
|
||||||
Invitations *InvitationConnection `json:"invitations,omitempty"`
|
Invitations *InvitationConnection `json:"invitations,omitempty"`
|
||||||
SamlConfigurations *SAMLConfigurationConnection `json:"samlConfigurations,omitempty"`
|
SamlConfigurations *SAMLConfigurationConnection `json:"samlConfigurations,omitempty"`
|
||||||
|
ViewerMembership *Membership `json:"viewerMembership,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Organization) IsNode() {}
|
func (Organization) IsNode() {}
|
||||||
|
|||||||
@@ -1013,6 +1013,19 @@ func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *type
|
|||||||
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewerMembership is the resolver for the viewerMembership field.
|
||||||
|
func (r *organizationResolver) ViewerMembership(ctx context.Context, obj *types.Organization) (*types.Membership, error) {
|
||||||
|
identity := IdentityFromContext(ctx)
|
||||||
|
|
||||||
|
membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, identity.ID, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot get membership for organization", log.Error(err))
|
||||||
|
return nil, gqlutils.InternalServerError(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewMembership(membership), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TotalCount is the resolver for the totalCount field.
|
// TotalCount is the resolver for the totalCount field.
|
||||||
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) {
|
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) {
|
||||||
switch obj.Resolver.(type) {
|
switch obj.Resolver.(type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user