diff --git a/apps/console/src/pages/iam/auth/SignInPage.tsx b/apps/console/src/pages/iam/auth/SignInPage.tsx index 88ab399f2..d035c99c3 100644 --- a/apps/console/src/pages/iam/auth/SignInPage.tsx +++ b/apps/console/src/pages/iam/auth/SignInPage.tsx @@ -24,7 +24,7 @@ export default function SignInPage() { const [signIn, isSigningIn] = useMutation(signInMutation); - const handlePasswordLogin: FormEventHandler = async (e) => { + const handlePasswordLogin: FormEventHandler = (e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const emailValue = formData.get("email")?.toString(); @@ -32,24 +32,24 @@ export default function SignInPage() { if (!emailValue || !passwordValue) return; - try { - signIn({ - variables: { - input: { - email: emailValue, - password: passwordValue, - }, + signIn({ + variables: { + input: { + email: emailValue, + password: passwordValue, }, - }); - - window.location.href = "/"; - } catch (e: unknown) { - toast({ - title: __("Error"), - description: e instanceof Error ? e.message : __("Failed to login"), - variant: "error", - }); - } + }, + onCompleted: () => { + window.location.href = "/"; + }, + onError: (e) => { + toast({ + title: __("Error"), + description: e instanceof Error ? e.message : __("Failed to login"), + variant: "error", + }); + }, + }); }; return ( diff --git a/apps/console/src/pages/iam/organizations/OrganizationLayout.tsx b/apps/console/src/pages/iam/organizations/OrganizationLayout.tsx index 847a5fbb6..8f72a6a91 100644 --- a/apps/console/src/pages/iam/organizations/OrganizationLayout.tsx +++ b/apps/console/src/pages/iam/organizations/OrganizationLayout.tsx @@ -5,12 +5,14 @@ import { } from "react-relay"; import { organizationLayoutQuery } from "./OrganizationLayoutQuery"; import { Outlet } from "react-router"; -import { Layout } from "@probo/ui"; +import { Layout, Skeleton } from "@probo/ui"; import { Sidebar } from "./_components/Sidebar"; import { OrganizationDropdown } from "./_components/OrganizationDropdown"; import { consoleEnvironment } from "/environments"; import type { OrganizationLayoutQuery } from "./__generated__/OrganizationLayoutQuery.graphql"; import { PermissionsProvider } from "/providers/PermissionsProvider"; +import { SessionDropdown } from "./_components/SessionDropdown"; +import { Suspense } from "react"; interface OrganizationLayoutProps { queryRef: PreloadedQuery; @@ -32,9 +34,9 @@ export default function OrganizationLayout(props: OrganizationLayoutProps) {
- {/* }> - - */} + }> + + } sidebar={} diff --git a/apps/console/src/pages/iam/organizations/OrganizationLayoutQuery.tsx b/apps/console/src/pages/iam/organizations/OrganizationLayoutQuery.tsx index 55d6a302a..c55ee1dc7 100644 --- a/apps/console/src/pages/iam/organizations/OrganizationLayoutQuery.tsx +++ b/apps/console/src/pages/iam/organizations/OrganizationLayoutQuery.tsx @@ -15,10 +15,11 @@ export const organizationLayoutQuery = graphql` ...OrganizationDropdownFragment } } - viewer { + viewer @required(action: THROW) { pendingInvitations { totalCount } + ...SessionDropdownFragment @arguments(organizationId: $organizationId) } } `; diff --git a/apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts b/apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts index 6f5492644..b13f9886e 100644 --- a/apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts +++ b/apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<1d82d885a77675a330799121edb5cb69>> * @lightSyntaxTransform * @nogrep */ @@ -21,7 +21,8 @@ export type OrganizationLayoutQuery$data = { readonly pendingInvitations: { readonly totalCount: number | null | undefined; } | null | undefined; - } | null | undefined; + readonly " $fragmentSpreads": FragmentRefs<"SessionDropdownFragment">; + }; }; export type OrganizationLayoutQuery = { response: OrganizationLayoutQuery$data; @@ -61,7 +62,14 @@ v2 = { ], "storageKey": null }, -v3 = { +v3 = [ + { + "kind": "Variable", + "name": "organizationId", + "variableName": "organizationId" + } +], +v4 = { "alias": null, "args": null, "kind": "ScalarField", @@ -103,16 +111,25 @@ return { "action": "THROW" }, { - "alias": null, - "args": null, - "concreteType": "Identity", - "kind": "LinkedField", - "name": "viewer", - "plural": false, - "selections": [ - (v2/*: any*/) - ], - "storageKey": null + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": (v3/*: any*/), + "kind": "FragmentSpread", + "name": "SessionDropdownFragment" + } + ], + "storageKey": null + }, + "action": "THROW" } ], "type": "Query", @@ -153,7 +170,7 @@ return { "type": "Organization", "abstractKey": null }, - (v3/*: any*/) + (v4/*: any*/) ], "storageKey": null }, @@ -166,23 +183,56 @@ return { "plural": false, "selections": [ (v2/*: any*/), - (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 } ] }, "params": { - "cacheID": "b991c1a406cc9c0df5c67099285c31a8", + "cacheID": "0ebb386f89d19888f18a05c8dc573895", "id": null, "metadata": {}, "name": "OrganizationLayoutQuery", "operationKind": "query", - "text": "query OrganizationLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...OrganizationDropdownFragment\n }\n id\n }\n viewer {\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment OrganizationDropdownFragment on Organization {\n name\n}\n" + "text": "query OrganizationLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...OrganizationDropdownFragment\n }\n id\n }\n viewer {\n pendingInvitations {\n totalCount\n }\n ...SessionDropdownFragment_4xMPKw\n id\n }\n}\n\nfragment OrganizationDropdownFragment on Organization {\n name\n}\n\nfragment SessionDropdownFragment_4xMPKw on Identity {\n email\n profileFor(organizationId: $organizationId) {\n firstName\n lastName\n id\n }\n}\n" } }; })(); -(node as any).hash = "2e8a1c50dc999d218830ab3df10cdeb5"; +(node as any).hash = "d3d91888eabb36cc450ca585757b21c7"; export default node; diff --git a/apps/console/src/pages/iam/organizations/_components/SessionDropdown.tsx b/apps/console/src/pages/iam/organizations/_components/SessionDropdown.tsx new file mode 100644 index 000000000..0792fd3a9 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/_components/SessionDropdown.tsx @@ -0,0 +1,104 @@ +import { + DropdownSeparator, + IconArrowBoxLeft, + IconCircleQuestionmark, + IconKey, + IconPageTextLine, + UserDropdown, + UserDropdownItem, + useToast, +} from "@probo/ui"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { graphql } from "relay-runtime"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { useFragment, useMutation } from "react-relay"; +import { useTranslate } from "@probo/i18n"; +import type { SessionDropdownFragment$key } from "./__generated__/SessionDropdownFragment.graphql"; + +export const fragment = graphql` + fragment SessionDropdownFragment on Identity + @argumentDefinitions(organizationId: { type: "ID!" }) { + email + profileFor(organizationId: $organizationId) { + firstName + lastName + } + } +`; + +const signOutMutation = graphql` + mutation SessionDropdownSignOutMutation { + signOut { + success + } + } +`; + +export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) { + const { fKey } = props; + + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + const { isAuthorized } = use(PermissionsContext); + const { toast } = useToast(); + + const { email, profileFor: profile } = + useFragment(fragment, fKey); + const [signOut] = useMutation(signOutMutation); + + const handleLogout: React.MouseEventHandler = (e) => { + e.preventDefault(); + + signOut({ + variables: {}, + onCompleted: () => { + window.location.reload(); + }, + onError: (e) => { + toast({ + title: __("Error"), + description: e.message as string, + variant: "error", + }); + }, + }); + }; + + // FIXME: user should always have a default profile + const fullName = profile?.firstName + ? `${profile.firstName} ${profile.lastName}` + : email; + + return ( + + {isAuthorized("Organization", "deleteOrganization") && ( + + )} + {isAuthorized("Organization", "listSignableDocuments") && ( + + )} + + + + + ); +} diff --git a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx index cf232e688..c4c9aa86b 100644 --- a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx +++ b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx @@ -32,7 +32,6 @@ export function Sidebar() { const prefix = `/organizations/${organizationId}`; - console.log(isAuthorized("Organization", "listMeetings")); return (
    {isAuthorized("Organization", "listMeetings") && ( diff --git a/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownFragment.graphql.ts b/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownFragment.graphql.ts new file mode 100644 index 000000000..7a82b79a8 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownFragment.graphql.ts @@ -0,0 +1,83 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type SessionDropdownFragment$data = { + readonly email: any; + readonly profileFor: { + readonly firstName: string | null | undefined; + readonly lastName: string | null | undefined; + } | null | undefined; + readonly " $fragmentType": "SessionDropdownFragment"; +}; +export type SessionDropdownFragment$key = { + readonly " $data"?: SessionDropdownFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"SessionDropdownFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } + ], + "kind": "Fragment", + "metadata": null, + "name": "SessionDropdownFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "organizationId", + "variableName": "organizationId" + } + ], + "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 + } + ], + "storageKey": null + } + ], + "type": "Identity", + "abstractKey": null +}; + +(node as any).hash = "de1d0f069e5648898cab6a56a394d86f"; + +export default node; diff --git a/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownSignOutMutation.graphql.ts b/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownSignOutMutation.graphql.ts new file mode 100644 index 000000000..04d693a3a --- /dev/null +++ b/apps/console/src/pages/iam/organizations/_components/__generated__/SessionDropdownSignOutMutation.graphql.ts @@ -0,0 +1,74 @@ +/** + * @generated SignedSource<<8cb399eb2f38eaf3059531ea4bc3fa30>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type SessionDropdownSignOutMutation$variables = Record; +export type SessionDropdownSignOutMutation$data = { + readonly signOut: { + readonly success: boolean; + } | null | undefined; +}; +export type SessionDropdownSignOutMutation = { + response: SessionDropdownSignOutMutation$data; + variables: SessionDropdownSignOutMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "SignOutPayload", + "kind": "LinkedField", + "name": "signOut", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "success", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "SessionDropdownSignOutMutation", + "selections": (v0/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "SessionDropdownSignOutMutation", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "a677190a127d7f75d5c629fab236a7a3", + "id": null, + "metadata": {}, + "name": "SessionDropdownSignOutMutation", + "operationKind": "mutation", + "text": "mutation SessionDropdownSignOutMutation {\n signOut {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "10c9bdf35ab3961c7488973e052cf978"; + +export default node;