Fix invitation list connection
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<37b59716fa1aba5ed805ce03278ce01d>>
|
* @generated SignedSource<<d415b10cc562b5554452cc3ed15bc31e>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -111,7 +111,13 @@ return {
|
|||||||
"kind": "RequiredField",
|
"kind": "RequiredField",
|
||||||
"field": {
|
"field": {
|
||||||
"alias": "invitations",
|
"alias": "invitations",
|
||||||
"args": null,
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "orderBy",
|
||||||
|
"variableName": "order"
|
||||||
|
}
|
||||||
|
],
|
||||||
"concreteType": "InvitationConnection",
|
"concreteType": "InvitationConnection",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "__InvitationListFragment_invitations_connection",
|
"name": "__InvitationListFragment_invitations_connection",
|
||||||
@@ -233,6 +239,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "6a1b77bc8b0e5f345de9865ec78ef7e9";
|
(node as any).hash = "d6f8750bb89b233418c3a66669c4aa23";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<e8e0e8e378c0a308255a8e2f3918b758>>
|
* @generated SignedSource<<23d03caf79ac153cc19aae8e87c38a27>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -355,7 +355,9 @@ return {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v13/*: any*/),
|
"args": (v13/*: any*/),
|
||||||
"filters": [],
|
"filters": [
|
||||||
|
"orderBy"
|
||||||
|
],
|
||||||
"handle": "connection",
|
"handle": "connection",
|
||||||
"key": "InvitationListFragment_invitations",
|
"key": "InvitationListFragment_invitations",
|
||||||
"kind": "LinkedHandle",
|
"kind": "LinkedHandle",
|
||||||
@@ -381,6 +383,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "6a1b77bc8b0e5f345de9865ec78ef7e9";
|
(node as any).hash = "d6f8750bb89b233418c3a66669c4aa23";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<0f7c89bd92b5743593efd542812568a7>>
|
* @generated SignedSource<<bd98e1c1ff5f9393deaec2a4aec1649c>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -197,7 +197,10 @@ v17 = {
|
|||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
},
|
||||||
|
v18 = [
|
||||||
|
"orderBy"
|
||||||
|
];
|
||||||
return {
|
return {
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
@@ -411,9 +414,7 @@ return {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v6/*: any*/),
|
"args": (v6/*: any*/),
|
||||||
"filters": [
|
"filters": (v18/*: any*/),
|
||||||
"orderBy"
|
|
||||||
],
|
|
||||||
"handle": "connection",
|
"handle": "connection",
|
||||||
"key": "MemberListFragment_members",
|
"key": "MemberListFragment_members",
|
||||||
"kind": "LinkedHandle",
|
"kind": "LinkedHandle",
|
||||||
@@ -499,7 +500,7 @@ return {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v6/*: any*/),
|
"args": (v6/*: any*/),
|
||||||
"filters": [],
|
"filters": (v18/*: any*/),
|
||||||
"handle": "connection",
|
"handle": "connection",
|
||||||
"key": "InvitationListFragment_invitations",
|
"key": "InvitationListFragment_invitations",
|
||||||
"kind": "LinkedHandle",
|
"kind": "LinkedHandle",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import type { LoadMoreFn } from "react-relay";
|
import type { LoadMoreFn } from "react-relay";
|
||||||
import type { OperationType } from "relay-runtime";
|
import type { OperationType } from "relay-runtime";
|
||||||
|
|
||||||
type Order = {
|
export type Order = {
|
||||||
direction: "ASC" | "DESC";
|
direction: "ASC" | "DESC";
|
||||||
field: string;
|
field: string;
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,7 @@ export const SortableContext = createContext({
|
|||||||
direction: "DESC",
|
direction: "DESC",
|
||||||
field: "CREATED_AT",
|
field: "CREATED_AT",
|
||||||
},
|
},
|
||||||
onOrderChange: (() => {}) as (order: Order) => void,
|
changeOrder: (() => {}) as (order: Order) => void,
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultOrder = {
|
const defaultOrder = {
|
||||||
@@ -54,14 +54,14 @@ export function SortableTable({
|
|||||||
}) {
|
}) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [order, setOrder] = useState(defaultOrder);
|
const [order, setOrder] = useState(defaultOrder);
|
||||||
const onOrderChange = (o: Order) => {
|
const changeOrder = (o: Order) => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setOrder(o);
|
setOrder(o);
|
||||||
refetch({ order: o });
|
refetch({ order: o });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<SortableContext value={{ order, onOrderChange }}>
|
<SortableContext value={{ order, changeOrder }}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Table {...props} />
|
<Table {...props} />
|
||||||
{hasNext && loadNext && (
|
{hasNext && loadNext && (
|
||||||
@@ -83,22 +83,29 @@ export function SortableTable({
|
|||||||
export function SortableTh({
|
export function SortableTh({
|
||||||
children,
|
children,
|
||||||
field,
|
field,
|
||||||
|
onOrderChange,
|
||||||
...props
|
...props
|
||||||
}: ComponentProps<typeof Th> & { field: string }) {
|
}: ComponentProps<typeof Th> & {
|
||||||
const { order, onOrderChange } = useContext(SortableContext);
|
field: string;
|
||||||
|
onOrderChange?: (order: { direction: "ASC" | "DESC"; field: string }) => void;
|
||||||
|
}) {
|
||||||
|
const { order, changeOrder } = useContext(SortableContext);
|
||||||
const isCurrentField = order.field === field;
|
const isCurrentField = order.field === field;
|
||||||
const isDesc = order.direction === "DESC";
|
const isDesc = order.direction === "DESC";
|
||||||
const changeOrder = () => {
|
const handleChangeOrder = () => {
|
||||||
onOrderChange({
|
const newOrder = {
|
||||||
direction: isDesc && isCurrentField ? "ASC" : "DESC",
|
direction:
|
||||||
|
isDesc && isCurrentField ? ("ASC" as const) : ("DESC" as const),
|
||||||
field,
|
field,
|
||||||
});
|
};
|
||||||
|
changeOrder(newOrder);
|
||||||
|
onOrderChange?.(newOrder);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Th {...props}>
|
<Th {...props}>
|
||||||
<button
|
<button
|
||||||
className="flex items-center cursor-pointer hover:text-txt-primary"
|
className="flex items-center cursor-pointer hover:text-txt-primary"
|
||||||
onClick={changeOrder}
|
onClick={handleChangeOrder}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<IconChevronTriangleDownSmall
|
<IconChevronTriangleDownSmall
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { ConnectionHandler, graphql } from "relay-runtime";
|
||||||
import { MemberList } from "./_components/MemberList";
|
import { MemberList } from "./_components/MemberList";
|
||||||
import type { MembersPageQuery } from "/__generated__/iam/MembersPageQuery.graphql";
|
import type { MembersPageQuery } from "/__generated__/iam/MembersPageQuery.graphql";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
@@ -7,6 +7,7 @@ import { Button, Card, TabBadge, TabItem, Tabs } from "@probo/ui";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { InvitationList } from "./_components/InvitationList";
|
import { InvitationList } from "./_components/InvitationList";
|
||||||
import { InviteUserDialog } from "./_components/InviteUserDialog";
|
import { InviteUserDialog } from "./_components/InviteUserDialog";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
|
||||||
export const membersPageQuery = graphql`
|
export const membersPageQuery = graphql`
|
||||||
query MembersPageQuery($organizationId: ID!) {
|
query MembersPageQuery($organizationId: ID!) {
|
||||||
@@ -37,6 +38,7 @@ export function MembersPage(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { queryRef } = props;
|
const { queryRef } = props;
|
||||||
|
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<"memberships" | "invitations">(
|
const [activeTab, setActiveTab] = useState<"memberships" | "invitations">(
|
||||||
@@ -51,12 +53,23 @@ export function MembersPage(props: {
|
|||||||
throw new Error("node is of invalid type");
|
throw new Error("node is of invalid type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [invitationListConnectionId, setInvitationListConnectionId] = useState(
|
||||||
|
ConnectionHandler.getConnectionID(
|
||||||
|
organizationId,
|
||||||
|
"InvitationListFragment_invitations",
|
||||||
|
{ orderBy: { direction: "ASC", field: "CREATED_AT" } },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
|
||||||
{organization.canInviteUser && (
|
{organization.canInviteUser && (
|
||||||
<InviteUserDialog viewerMembershipFKey={organization}>
|
<InviteUserDialog
|
||||||
|
connectionId={invitationListConnectionId}
|
||||||
|
viewerMembershipFKey={organization}
|
||||||
|
>
|
||||||
<Button variant="secondary">{__("Invite member")}</Button>
|
<Button variant="secondary">{__("Invite member")}</Button>
|
||||||
</InviteUserDialog>
|
</InviteUserDialog>
|
||||||
)}
|
)}
|
||||||
@@ -88,7 +101,10 @@ export function MembersPage(props: {
|
|||||||
{activeTab === "memberships" && <MemberList fKey={organization} />}
|
{activeTab === "memberships" && <MemberList fKey={organization} />}
|
||||||
|
|
||||||
{activeTab === "invitations" && (
|
{activeTab === "invitations" && (
|
||||||
<InvitationList fKey={organization} />
|
<InvitationList
|
||||||
|
fKey={organization}
|
||||||
|
onConnectionIdChange={setInvitationListConnectionId}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { Tbody, Td, Th, Thead, Tr } from "@probo/ui";
|
import { Tbody, Td, Th, Thead, Tr } from "@probo/ui";
|
||||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
import {
|
||||||
|
SortableTable,
|
||||||
|
SortableTh,
|
||||||
|
type Order,
|
||||||
|
} from "/components/SortableTable";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { graphql, usePaginationFragment } from "react-relay";
|
import { ConnectionHandler, graphql, usePaginationFragment } from "react-relay";
|
||||||
import { InvitationListItem } from "./InvitationListItem";
|
import { InvitationListItem } from "./InvitationListItem";
|
||||||
import type { InvitationListFragment$key } from "/__generated__/iam/InvitationListFragment.graphql";
|
import type { InvitationListFragment$key } from "/__generated__/iam/InvitationListFragment.graphql";
|
||||||
import type { InvitationListFragment_RefetchQuery } from "/__generated__/iam/InvitationListFragment_RefetchQuery.graphql";
|
import type { InvitationListFragment_RefetchQuery } from "/__generated__/iam/InvitationListFragment_RefetchQuery.graphql";
|
||||||
import type { ComponentProps } from "react";
|
import { type ComponentProps } from "react";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
|
||||||
const fragment = graphql`
|
const fragment = graphql`
|
||||||
fragment InvitationListFragment on Organization
|
fragment InvitationListFragment on Organization
|
||||||
@@ -27,7 +32,10 @@ const fragment = graphql`
|
|||||||
before: $before
|
before: $before
|
||||||
orderBy: $order
|
orderBy: $order
|
||||||
)
|
)
|
||||||
@connection(key: "InvitationListFragment_invitations", filters: [])
|
@connection(
|
||||||
|
key: "InvitationListFragment_invitations"
|
||||||
|
filters: ["orderBy"]
|
||||||
|
)
|
||||||
@required(action: THROW) {
|
@required(action: THROW) {
|
||||||
__id
|
__id
|
||||||
totalCount
|
totalCount
|
||||||
@@ -41,9 +49,13 @@ const fragment = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function InvitationList(props: { fKey: InvitationListFragment$key }) {
|
export function InvitationList(props: {
|
||||||
const { fKey } = props;
|
fKey: InvitationListFragment$key;
|
||||||
|
onConnectionIdChange: (connectionId: string) => void;
|
||||||
|
}) {
|
||||||
|
const { fKey, onConnectionIdChange } = props;
|
||||||
|
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
const invitationsPagination = usePaginationFragment<
|
const invitationsPagination = usePaginationFragment<
|
||||||
@@ -55,6 +67,16 @@ export function InvitationList(props: { fKey: InvitationListFragment$key }) {
|
|||||||
invitationsPagination.refetch({}, { fetchPolicy: "network-only" });
|
invitationsPagination.refetch({}, { fetchPolicy: "network-only" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOrderChange = (order: Order) => {
|
||||||
|
onConnectionIdChange(
|
||||||
|
ConnectionHandler.getConnectionID(
|
||||||
|
organizationId,
|
||||||
|
"InvitationListFragment_invitations",
|
||||||
|
{ orderBy: order },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableTable
|
<SortableTable
|
||||||
{...invitationsPagination}
|
{...invitationsPagination}
|
||||||
@@ -67,12 +89,22 @@ export function InvitationList(props: { fKey: InvitationListFragment$key }) {
|
|||||||
>
|
>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<SortableTh field="FULL_NAME">{__("Name")}</SortableTh>
|
<SortableTh field="FULL_NAME" onOrderChange={handleOrderChange}>
|
||||||
<SortableTh field="EMAIL">{__("Email")}</SortableTh>
|
{__("Name")}
|
||||||
<SortableTh field="ROLE">{__("Role")}</SortableTh>
|
</SortableTh>
|
||||||
<SortableTh field="CREATED_AT">{__("Invited")}</SortableTh>
|
<SortableTh field="EMAIL" onOrderChange={handleOrderChange}>
|
||||||
|
{__("Email")}
|
||||||
|
</SortableTh>
|
||||||
|
<SortableTh field="ROLE" onOrderChange={handleOrderChange}>
|
||||||
|
{__("Role")}
|
||||||
|
</SortableTh>
|
||||||
|
<SortableTh field="CREATED_AT" onOrderChange={handleOrderChange}>
|
||||||
|
{__("Invited")}
|
||||||
|
</SortableTh>
|
||||||
<Th>{__("Status")}</Th>
|
<Th>{__("Status")}</Th>
|
||||||
<SortableTh field="ACCEPTED_AT">{__("Accepted at")}</SortableTh>
|
<SortableTh field="ACCEPTED_AT" onOrderChange={handleOrderChange}>
|
||||||
|
{__("Accepted at")}
|
||||||
|
</SortableTh>
|
||||||
<Th></Th>
|
<Th></Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
import { useFragment } from "react-relay";
|
import { useFragment } from "react-relay";
|
||||||
import { ConnectionHandler, graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
@@ -59,11 +59,12 @@ const schema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
type InviteUserDialogProps = PropsWithChildren<{
|
type InviteUserDialogProps = PropsWithChildren<{
|
||||||
|
connectionId: string;
|
||||||
viewerMembershipFKey: InviteUserDialog_currentRoleFragment$key;
|
viewerMembershipFKey: InviteUserDialog_currentRoleFragment$key;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function InviteUserDialog(props: InviteUserDialogProps) {
|
export function InviteUserDialog(props: InviteUserDialogProps) {
|
||||||
const { children, viewerMembershipFKey } = props;
|
const { children, connectionId, viewerMembershipFKey } = props;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
@@ -87,11 +88,6 @@ export function InviteUserDialog(props: InviteUserDialogProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = handleSubmit((data) => {
|
const onSubmit = handleSubmit((data) => {
|
||||||
const connectionId = ConnectionHandler.getConnectionID(
|
|
||||||
organizationId,
|
|
||||||
"InvitationListFragment_invitations",
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
inviteUser({
|
inviteUser({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
Reference in New Issue
Block a user