diff --git a/src/components/v5/common/ActionSidebar/partials/UserSelect/UserSelect.tsx b/src/components/v5/common/ActionSidebar/partials/UserSelect/UserSelect.tsx index 4721c9f88b..e15661145f 100644 --- a/src/components/v5/common/ActionSidebar/partials/UserSelect/UserSelect.tsx +++ b/src/components/v5/common/ActionSidebar/partials/UserSelect/UserSelect.tsx @@ -228,7 +228,6 @@ const UserSelect: FC = ({ }} isLoading={usersOptions.isLoading} className="z-sidebar" - showEmptyContent={false} shouldReturnAddresses /> )} diff --git a/src/components/v5/shared/SearchSelect/SearchSelect.tsx b/src/components/v5/shared/SearchSelect/SearchSelect.tsx index 0802b31d30..3af526c1c3 100644 --- a/src/components/v5/shared/SearchSelect/SearchSelect.tsx +++ b/src/components/v5/shared/SearchSelect/SearchSelect.tsx @@ -209,6 +209,8 @@ const SearchSelect = React.forwardRef( icon={Binoculars} title={{ id: 'actionSidebar.emptyTitle' }} description={{ id: 'actionSidebar.emptyDescription' }} + isDropdown + className="!p-0" /> )} diff --git a/src/context/MemberContext/MemberContextProvider.tsx b/src/context/MemberContext/MemberContextProvider.tsx index 34065cff0a..bd0bb26703 100644 --- a/src/context/MemberContext/MemberContextProvider.tsx +++ b/src/context/MemberContext/MemberContextProvider.tsx @@ -17,6 +17,7 @@ import { } from '~constants/index.ts'; import { useSearchContext } from '~context/SearchContext/SearchContext.ts'; import { + useOnCreateColonyContributorSubscription, useOnUpdateColonySubscription, useSearchColonyContributorsQuery, } from '~gql'; @@ -119,7 +120,7 @@ const MemberContextProvider: FC = ({ children }) => { data: memberSearchData, loading, fetchMore, - refetch, + refetch: refetchColonyContributors, } = useSearchColonyContributorsQuery({ variables: { colonyAddress, @@ -153,15 +154,25 @@ const MemberContextProvider: FC = ({ children }) => { }, }); - const { data } = useOnUpdateColonySubscription(); + const { data: newColonyUpdateResult } = useOnUpdateColonySubscription(); + + const newColonyUpdate = + newColonyUpdateResult?.onUpdateColony + ?.lastUpdatedContributorsWithReputation; + + const { data: newColonyContributorResult } = + useOnCreateColonyContributorSubscription(); + + const newColonyContributor = + newColonyContributorResult?.onCreateColonyContributor?.contributorAddress; useEffect(() => { let timeout; // When the colony first loads, the reputation is updated asynchronously. This means that the currently // cached reputation might be out of date. If this is the case, we should refetch. - if (data?.onUpdateColony?.lastUpdatedContributorsWithReputation) { + if (newColonyUpdate || newColonyContributor) { // It looks hacky, but we need the timeout to ensure that opensearch has been updated before we refetch. - timeout = setTimeout(refetch, 2000); + timeout = setTimeout(refetchColonyContributors, 2000); } return () => { @@ -169,7 +180,7 @@ const MemberContextProvider: FC = ({ children }) => { clearTimeout(timeout); } }; - }, [data?.onUpdateColony?.lastUpdatedContributorsWithReputation, refetch]); + }, [newColonyUpdate, newColonyContributor, refetchColonyContributors]); const allMembers = useMemo( () => diff --git a/src/graphql/generated.ts b/src/graphql/generated.ts index c3813a0c4c..1e3892190c 100644 --- a/src/graphql/generated.ts +++ b/src/graphql/generated.ts @@ -1157,10 +1157,12 @@ export type ColonyMultiSig = { /** Extended user object for given executedBy */ executedByUser?: Maybe; /** - * In case of multiple multisig actions in a motion, when funding an expenditure, array containing + * In case of multiple funding actions in a multisig, when funding an expenditure, array containing * the details of tokens and amounts to be funded */ expenditureFunding?: Maybe>; + /** Expenditure associated with the motion, if any */ + expenditureId?: Maybe; /** Whether the underlying action completed */ hasActionCompleted: Scalars['Boolean']; /** @@ -1611,6 +1613,7 @@ export type CreateColonyMultiSigInput = { executedAt?: InputMaybe; executedBy?: InputMaybe; expenditureFunding?: InputMaybe>; + expenditureId?: InputMaybe; hasActionCompleted: Scalars['Boolean']; id?: InputMaybe; isDecision: Scalars['Boolean']; @@ -2365,7 +2368,7 @@ export type Expenditure = { ownerAddress: Scalars['ID']; /** Array containing expenditure slots */ slots: Array; - /** Indicates whether the splitPaymentClaimedNotification has been sent to prevent sending multiple notifications */ + /** Only used for Split Payments, used to prevent sending multiple payment notifications */ splitPaymentPayoutClaimedNotificationSent?: Maybe; /** Address of StagedExpenditure extension which set the expenditure as staged, if applicable */ stagedExpenditureAddress?: Maybe; @@ -3295,6 +3298,7 @@ export type ModelColonyMultiSigConditionInput = { createdAt?: InputMaybe; executedAt?: InputMaybe; executedBy?: InputMaybe; + expenditureId?: InputMaybe; hasActionCompleted?: InputMaybe; isDecision?: InputMaybe; isExecuted?: InputMaybe; @@ -3322,6 +3326,7 @@ export type ModelColonyMultiSigFilterInput = { createdAt?: InputMaybe; executedAt?: InputMaybe; executedBy?: InputMaybe; + expenditureId?: InputMaybe; hasActionCompleted?: InputMaybe; id?: InputMaybe; isDecision?: InputMaybe; @@ -4374,6 +4379,7 @@ export type ModelSubscriptionColonyMultiSigFilterInput = { createdAt?: InputMaybe; executedAt?: InputMaybe; executedBy?: InputMaybe; + expenditureId?: InputMaybe; hasActionCompleted?: InputMaybe; id?: InputMaybe; isDecision?: InputMaybe; @@ -6674,6 +6680,7 @@ export type Query = { getMotionTimeoutPeriods?: Maybe; getMotionVoterRewards?: Maybe; getMultiSigByColonyAddress?: Maybe; + getMultiSigByExpenditureId?: Maybe; getMultiSigByTransactionHash?: Maybe; getMultiSigUserSignature?: Maybe; getMultiSigUserSignatureByMultiSigId?: Maybe; @@ -7254,6 +7261,16 @@ export type QueryGetMultiSigByColonyAddressArgs = { }; +/** Root query type */ +export type QueryGetMultiSigByExpenditureIdArgs = { + expenditureId: Scalars['ID']; + filter?: InputMaybe; + limit?: InputMaybe; + nextToken?: InputMaybe; + sortDirection?: InputMaybe; +}; + + /** Root query type */ export type QueryGetMultiSigByTransactionHashArgs = { filter?: InputMaybe; @@ -9642,6 +9659,7 @@ export type UpdateColonyMultiSigInput = { executedAt?: InputMaybe; executedBy?: InputMaybe; expenditureFunding?: InputMaybe>; + expenditureId?: InputMaybe; hasActionCompleted?: InputMaybe; id: Scalars['ID']; isDecision?: InputMaybe; @@ -10759,6 +10777,11 @@ export type SearchColonyContributorsQueryVariables = Exact<{ export type SearchColonyContributorsQuery = { __typename?: 'Query', searchColonyContributors?: { __typename?: 'SearchableColonyContributorConnection', total?: number | null, nextToken?: string | null, items: Array<{ __typename?: 'ColonyContributor', contributorAddress: string, isVerified: boolean, hasPermissions?: boolean | null, hasReputation?: boolean | null, isWatching?: boolean | null, colonyReputationPercentage: number, type?: ContributorType | null, roles?: { __typename?: 'ModelColonyRoleConnection', items: Array<{ __typename?: 'ColonyRole', domainId: string, role_0?: boolean | null, role_1?: boolean | null, role_2?: boolean | null, role_3?: boolean | null, role_5?: boolean | null, role_6?: boolean | null, isMultiSig?: boolean | null, id: string, domain: { __typename?: 'Domain', id: string, nativeId: number, metadata?: { __typename?: 'DomainMetadata', name: string, color: DomainColor } | null } } | null> } | null, reputation?: { __typename?: 'ModelContributorReputationConnection', items: Array<{ __typename?: 'ContributorReputation', reputationPercentage: number, reputationRaw: string, domainId: string, id: string, domain: { __typename?: 'Domain', id: string, nativeId: number, metadata?: { __typename?: 'DomainMetadata', name: string, color: DomainColor } | null } } | null> } | null, user?: { __typename?: 'User', walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null } | null } | null> } | null }; +export type OnCreateColonyContributorSubscriptionVariables = Exact<{ [key: string]: never; }>; + + +export type OnCreateColonyContributorSubscription = { __typename?: 'Subscription', onCreateColonyContributor?: { __typename?: 'ColonyContributor', contributorAddress: string } | null }; + export type GetMembersCountQueryVariables = Exact<{ filter: SearchableColonyContributorFilterInput; nextToken?: InputMaybe; @@ -14368,6 +14391,35 @@ export function useSearchColonyContributorsLazyQuery(baseOptions?: Apollo.LazyQu export type SearchColonyContributorsQueryHookResult = ReturnType; export type SearchColonyContributorsLazyQueryHookResult = ReturnType; export type SearchColonyContributorsQueryResult = Apollo.QueryResult; +export const OnCreateColonyContributorDocument = gql` + subscription OnCreateColonyContributor { + onCreateColonyContributor { + contributorAddress + } +} + `; + +/** + * __useOnCreateColonyContributorSubscription__ + * + * To run a query within a React component, call `useOnCreateColonyContributorSubscription` and pass it any options that fit your needs. + * When your component renders, `useOnCreateColonyContributorSubscription` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useOnCreateColonyContributorSubscription({ + * variables: { + * }, + * }); + */ +export function useOnCreateColonyContributorSubscription(baseOptions?: Apollo.SubscriptionHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSubscription(OnCreateColonyContributorDocument, options); + } +export type OnCreateColonyContributorSubscriptionHookResult = ReturnType; +export type OnCreateColonyContributorSubscriptionResult = Apollo.SubscriptionResult; export const GetMembersCountDocument = gql` query GetMembersCount($filter: SearchableColonyContributorFilterInput!, $nextToken: String) { searchColonyContributors( diff --git a/src/graphql/queries/contributors.graphql b/src/graphql/queries/contributors.graphql index cc01b5b0b3..d0d2495e00 100644 --- a/src/graphql/queries/contributors.graphql +++ b/src/graphql/queries/contributors.graphql @@ -49,6 +49,12 @@ query SearchColonyContributors( } } +subscription OnCreateColonyContributor { + onCreateColonyContributor { + contributorAddress + } +} + query GetMembersCount( $filter: SearchableColonyContributorFilterInput! $nextToken: String diff --git a/src/hooks/useGetColoniesMembersCount.ts b/src/hooks/useGetColoniesMembersCount.ts index 80f19491eb..7910e9a965 100644 --- a/src/hooks/useGetColoniesMembersCount.ts +++ b/src/hooks/useGetColoniesMembersCount.ts @@ -1,6 +1,9 @@ +import { useEffect } from 'react'; + import { type SearchableColonyContributorFilterInput, useGetMembersCountQuery, + useOnCreateColonyContributorSubscription, } from '~gql'; /** @@ -31,6 +34,7 @@ export const useGetColoniesMembersCount = ( data: contributorsCount, fetchMore, loading: contributorsCountLoading, + refetch: refetchMembersCount, } = useGetMembersCountQuery({ variables: { filter: { ...queryFilter }, @@ -71,6 +75,27 @@ export const useGetColoniesMembersCount = ( }, }); + const { data: newColonyContributorResult } = + useOnCreateColonyContributorSubscription(); + + const newColonyContributor = + newColonyContributorResult?.onCreateColonyContributor?.contributorAddress; + + useEffect(() => { + let timeout: NodeJS.Timeout; + + if (newColonyContributor) { + // It looks hacky, but we need the timeout to ensure that opensearch has been updated before we refetch. + timeout = setTimeout(refetchMembersCount, 2000); + } + + return () => { + if (timeout) { + clearTimeout(timeout); + } + }; + }, [newColonyContributor, refetchMembersCount]); + const membersCount = contributorsCount?.searchColonyContributors?.aggregateItems[0]?.result ?.__typename === 'SearchableAggregateBucketResult'