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