Plug back new org CTA in org dropdown

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-20 00:43:54 +01:00
committed by Bryan Frimin
parent 38bf027dd6
commit 8b8e671953
6 changed files with 193 additions and 46 deletions

View File

@@ -4,8 +4,8 @@ import {
type PreloadedQuery,
} from "react-relay";
import { organizationLayoutQuery } from "./OrganizationLayoutQuery";
import { Outlet } from "react-router";
import { Layout, Skeleton } from "@probo/ui";
import { Link, Outlet } from "react-router";
import { Badge, Button, IconPeopleAdd, Layout, Skeleton } from "@probo/ui";
import { Sidebar } from "./_components/Sidebar";
import { OrganizationDropdown } from "./_components/OrganizationDropdown";
import { consoleEnvironment } from "/environments";
@@ -13,6 +13,7 @@ import type { OrganizationLayoutQuery } from "./__generated__/OrganizationLayout
import { PermissionsProvider } from "/providers/PermissionsProvider";
import { SessionDropdown } from "./_components/SessionDropdown";
import { Suspense } from "react";
import { useTranslate } from "@probo/i18n";
interface OrganizationLayoutProps {
queryRef: PreloadedQuery<OrganizationLayoutQuery>;
@@ -21,7 +22,9 @@ interface OrganizationLayoutProps {
export default function OrganizationLayout(props: OrganizationLayoutProps) {
const { queryRef } = props;
const data = usePreloadedQuery<OrganizationLayoutQuery>(
const { __ } = useTranslate();
const { organization, viewer } = usePreloadedQuery<OrganizationLayoutQuery>(
organizationLayoutQuery,
queryRef,
);
@@ -32,10 +35,25 @@ export default function OrganizationLayout(props: OrganizationLayoutProps) {
header={
<>
<div className="mr-auto">
<OrganizationDropdown fKey={data.organization} />
<OrganizationDropdown
organizationFKey={organization}
viewerFKey={viewer}
/>
{viewer.pendingInvitations.totalCount > 0 && (
<Link to="/" className="relative" title={__("Invitations")}>
<Button variant="tertiary" icon={IconPeopleAdd} />
<Badge
variant="info"
size="sm"
className="absolute -top-1 -right-1 min-w-[20px] h-5 flex items-center justify-center"
>
{viewer.pendingInvitations.totalCount}
</Badge>
</Link>
)}
</div>
<Suspense fallback={<Skeleton className="w-32 h-8" />}>
<SessionDropdown fKey={data.viewer} />
<SessionDropdown fKey={viewer} />
</Suspense>
</>
}

View File

@@ -12,12 +12,13 @@ export const organizationLayoutQuery = graphql`
query OrganizationLayoutQuery($organizationId: ID!) {
organization: node(id: $organizationId) @required(action: THROW) {
... on Organization {
...OrganizationDropdownFragment
...OrganizationDropdown_organizationFragment
}
}
viewer @required(action: THROW) {
pendingInvitations {
totalCount
...OrganizationDropdown_viewerFragment
pendingInvitations @required(action: THROW) {
totalCount @required(action: THROW)
}
...SessionDropdownFragment @arguments(organizationId: $organizationId)
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<1d82d885a77675a330799121edb5cb69>>
* @generated SignedSource<<09fbc762db2d00eb944a0ef9afe3756d>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -15,13 +15,13 @@ export type OrganizationLayoutQuery$variables = {
};
export type OrganizationLayoutQuery$data = {
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownFragment">;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdown_organizationFragment">;
};
readonly viewer: {
readonly pendingInvitations: {
readonly totalCount: number | null | undefined;
} | null | undefined;
readonly " $fragmentSpreads": FragmentRefs<"SessionDropdownFragment">;
readonly totalCount: number;
};
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdown_viewerFragment" | "SessionDropdownFragment">;
};
};
export type OrganizationLayoutQuery = {
@@ -47,19 +47,8 @@ v1 = [
v2 = {
"alias": null,
"args": null,
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
}
],
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
v3 = [
@@ -99,7 +88,7 @@ return {
{
"args": null,
"kind": "FragmentSpread",
"name": "OrganizationDropdownFragment"
"name": "OrganizationDropdown_organizationFragment"
}
],
"type": "Organization",
@@ -120,7 +109,31 @@ return {
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "OrganizationDropdown_viewerFragment"
},
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
{
"kind": "RequiredField",
"field": (v2/*: any*/),
"action": "THROW"
}
],
"storageKey": null
},
"action": "THROW"
},
{
"args": (v3/*: any*/),
"kind": "FragmentSpread",
@@ -182,7 +195,18 @@ return {
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -223,16 +247,16 @@ return {
]
},
"params": {
"cacheID": "0ebb386f89d19888f18a05c8dc573895",
"cacheID": "ca7f41668eb74024237351d0e076819e",
"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 ...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"
"text": "query OrganizationLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...OrganizationDropdown_organizationFragment\n }\n id\n }\n viewer {\n ...OrganizationDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n ...SessionDropdownFragment_4xMPKw\n id\n }\n}\n\nfragment OrganizationDropdown_organizationFragment on Organization {\n name\n}\n\nfragment OrganizationDropdown_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"
}
};
})();
(node as any).hash = "d3d91888eabb36cc450ca585757b21c7";
(node as any).hash = "81b8eb0af21d649268e30c80d16226de";
export default node;

View File

@@ -1,12 +1,15 @@
import { useFragment, useQueryLoader } from "react-relay";
import { graphql } from "relay-runtime";
import type { OrganizationDropdownFragment$key } from "./__generated__/OrganizationDropdownFragment.graphql";
import {
Badge,
Button,
Dropdown,
DropdownItem,
DropdownSeparator,
IconChevronGrabberVertical,
IconMagnifyingGlass,
IconPeopleAdd,
IconPlusLarge,
Input,
} from "@probo/ui";
import { Suspense, useCallback, useState } from "react";
@@ -16,22 +19,43 @@ import {
organizationDropdownMenuQuery,
} from "./OrganizationDropdownMenu";
import type { OrganizationDropdownMenuQuery } from "./__generated__/OrganizationDropdownMenuQuery.graphql";
import { Link } from "react-router";
import type { OrganizationDropdown_organizationFragment$key } from "./__generated__/OrganizationDropdown_organizationFragment.graphql";
import type { OrganizationDropdown_viewerFragment$key } from "./__generated__/OrganizationDropdown_viewerFragment.graphql";
const fragment = graphql`
fragment OrganizationDropdownFragment on Organization {
const organizationFragment = graphql`
fragment OrganizationDropdown_organizationFragment on Organization {
name
}
`;
const viewerFragment = graphql`
fragment OrganizationDropdown_viewerFragment on Identity {
pendingInvitations @required(action: THROW) {
totalCount @required(action: THROW)
}
}
`;
export function OrganizationDropdown(props: {
fKey: OrganizationDropdownFragment$key;
organizationFKey: OrganizationDropdown_organizationFragment$key;
viewerFKey: OrganizationDropdown_viewerFragment$key;
}) {
const { fKey } = props;
const { organizationFKey, viewerFKey } = props;
const { __ } = useTranslate();
const [search, setSearch] = useState("");
const currentOrganization = useFragment(fragment, fKey);
const currentOrganization =
useFragment<OrganizationDropdown_organizationFragment$key>(
organizationFragment,
organizationFKey,
);
const {
pendingInvitations: { totalCount: pendingInvitationCount },
} = useFragment<OrganizationDropdown_viewerFragment$key>(
viewerFragment,
viewerFKey,
);
const [queryRef, loadQuery] = useQueryLoader<OrganizationDropdownMenuQuery>(
organizationDropdownMenuQuery,
);
@@ -83,6 +107,23 @@ export function OrganizationDropdown(props: {
)}
</div>
<DropdownSeparator />
{pendingInvitationCount > 0 && (
<DropdownItem asChild>
<Link to="/">
<IconPeopleAdd size={16} />
<span className="flex-1">{__("Invitations")}</span>
<Badge variant="info" size="sm">
{pendingInvitationCount}
</Badge>
</Link>
</DropdownItem>
)}
<DropdownItem asChild>
<Link to="/organizations/new">
<IconPlusLarge size={16} />
{__("Add organization")}
</Link>
</DropdownItem>
</Dropdown>
</div>
);

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<06f02e43b64c316083067f5b3978a08a>>
* @generated SignedSource<<1d2465517ea2dcfa99fb3dd8757fe980>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,20 +10,20 @@
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OrganizationDropdownFragment$data = {
export type OrganizationDropdown_organizationFragment$data = {
readonly name: string;
readonly " $fragmentType": "OrganizationDropdownFragment";
readonly " $fragmentType": "OrganizationDropdown_organizationFragment";
};
export type OrganizationDropdownFragment$key = {
readonly " $data"?: OrganizationDropdownFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownFragment">;
export type OrganizationDropdown_organizationFragment$key = {
readonly " $data"?: OrganizationDropdown_organizationFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdown_organizationFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationDropdownFragment",
"name": "OrganizationDropdown_organizationFragment",
"selections": [
{
"alias": null,
@@ -37,6 +37,6 @@ const node: ReaderFragment = {
"abstractKey": null
};
(node as any).hash = "9f19581f86736e6345912284668a8f25";
(node as any).hash = "8e6d0c702f821670315b5bde55e2556a";
export default node;

View File

@@ -0,0 +1,63 @@
/**
* @generated SignedSource<<d4b9efa7b9092ceb3f995507108a68e3>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OrganizationDropdown_viewerFragment$data = {
readonly pendingInvitations: {
readonly totalCount: number;
};
readonly " $fragmentType": "OrganizationDropdown_viewerFragment";
};
export type OrganizationDropdown_viewerFragment$key = {
readonly " $data"?: OrganizationDropdown_viewerFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdown_viewerFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationDropdown_viewerFragment",
"selections": [
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "pendingInvitations",
"plural": false,
"selections": [
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
"action": "THROW"
}
],
"storageKey": null
},
"action": "THROW"
}
],
"type": "Identity",
"abstractKey": null
};
(node as any).hash = "ce947f812c95b74e9f250b8d5821ebb0";
export default node;