diff --git a/apps/ui/src/components/App/Nav.vue b/apps/ui/src/components/App/Nav.vue index 2791f38fc..2733762f0 100644 --- a/apps/ui/src/components/App/Nav.vue +++ b/apps/ui/src/components/App/Nav.vue @@ -2,7 +2,7 @@ import { FunctionalComponent } from 'vue'; import { SPACES_DISCUSSIONS } from '@/helpers/discourse'; import { compareAddresses } from '@/helpers/utils'; -import { getNetwork } from '@/networks'; +import { getNetwork, offchainNetworks } from '@/networks'; import IHAnnotation from '~icons/heroicons-outline/annotation'; import IHBell from '~icons/heroicons-outline/bell'; import IHCash from '~icons/heroicons-outline/cash'; @@ -10,6 +10,7 @@ import IHCog from '~icons/heroicons-outline/cog'; import IHGlobeAlt from '~icons/heroicons-outline/globe-alt'; import IHGlobe from '~icons/heroicons-outline/globe-americas'; import IHHome from '~icons/heroicons-outline/home'; +import IHIdentification from '~icons/heroicons-outline/identification'; import IHLightningBolt from '~icons/heroicons-outline/lightning-bolt'; import IHNewspaper from '~icons/heroicons-outline/newspaper'; import IHStop from '~icons/heroicons-outline/stop'; @@ -42,15 +43,17 @@ const space = computed(() => : null ); -const isController = computedAsync(async () => { - if (!networkId.value || !space.value) return false; - - const { account } = web3.value; +const controller = computedAsync(async () => { + if (!networkId.value || !space.value) return null; const network = getNetwork(networkId.value); - const controller = await network.helpers.getSpaceController(space.value); + return network.helpers.getSpaceController(space.value); +}); - return compareAddresses(controller, account); +const isController = computed(() => { + const { account } = web3.value; + + return controller.value && compareAddresses(controller.value, account); }); const canSeeSettings = computed(() => { @@ -63,6 +66,22 @@ const canSeeSettings = computed(() => { return admins.includes(web3.value.account.toLowerCase()); } + return false; +}); + +const canSeeMembers = computed(() => { + const data = space.value?.additionalRawData; + + if (!networkId.value || !offchainNetworks.includes(networkId.value)) { + return false; + } + + return ( + controller.value || + (data?.admins?.length ?? 0) > 0 || + (data?.moderators?.length ?? 0) > 0 || + (data?.members?.length ?? 0) > 0 + ); }); const navigationConfig = computed< @@ -81,6 +100,14 @@ const navigationConfig = computed< name: 'Leaderboard', icon: IHUserGroup }, + ...(canSeeMembers.value + ? { + members: { + name: 'Members', + icon: IHIdentification + } + } + : undefined), ...(space.value?.delegations && space.value.delegations.length > 0 ? { delegates: { diff --git a/apps/ui/src/networks/offchain/api/index.ts b/apps/ui/src/networks/offchain/api/index.ts index 7687bb91a..af5e992b0 100644 --- a/apps/ui/src/networks/offchain/api/index.ts +++ b/apps/ui/src/networks/offchain/api/index.ts @@ -42,6 +42,7 @@ import { RANKING_QUERY, SPACE_QUERY, SPACES_QUERY, + STATEMENTS_AND_USERS_QUERY, STATEMENTS_QUERY, STRATEGIES_QUERY, STRATEGY_QUERY, @@ -791,6 +792,29 @@ export function createApi( return statements; }, + loadStatementsAndUsers: async ( + networkId: NetworkID, + spaceId: string, + userIds: string[] + ): Promise<{ statements: Statement[]; users: User[] }> => { + const { data } = await apollo.query({ + query: STATEMENTS_AND_USERS_QUERY, + variables: { + statementsFirst: 100, + statementsWhere: { + delegate_in: userIds, + network: networkId, + space: spaceId + }, + userIds + } + }); + + return { + statements: data.statements, + users: data.users + }; + }, loadStrategies: async () => { const { data } = await apollo.query({ query: STRATEGIES_QUERY diff --git a/apps/ui/src/networks/offchain/api/queries.ts b/apps/ui/src/networks/offchain/api/queries.ts index fa74b985f..e3e112ce4 100644 --- a/apps/ui/src/networks/offchain/api/queries.ts +++ b/apps/ui/src/networks/offchain/api/queries.ts @@ -367,6 +367,25 @@ export const STRATEGY_QUERY = gql` ${STRATEGY_FRAGMENT} `; +export const STATEMENTS_AND_USERS_QUERY = gql` + query ( + $statementsFirst: Int! + $statementsWhere: StatementsWhere + $userIds: [String!] + ) { + statements(first: $statementsFirst, where: $statementsWhere) { + delegate + space + network + statement + } + users(where: { id_in: $userIds }) { + id + about + } + } +`; + export const NETWORKS_USAGE_QUERY = gql` query Networks { networks { diff --git a/apps/ui/src/networks/types.ts b/apps/ui/src/networks/types.ts index a1288b0a1..ee18dc7a2 100644 --- a/apps/ui/src/networks/types.ts +++ b/apps/ui/src/networks/types.ts @@ -321,6 +321,11 @@ export type NetworkApi = { spaceId: string, userIds: string[] ): Promise; + loadStatementsAndUsers( + networkId: NetworkID, + spaceId: string, + userIds: string[] + ): Promise<{ statements: Statement[]; users: User[] }>; loadStrategies(): Promise; loadStrategy(address: string): Promise; getNetworksUsage(): Promise>; diff --git a/apps/ui/src/routes/common.ts b/apps/ui/src/routes/common.ts index ac4d41067..6f7e4e662 100644 --- a/apps/ui/src/routes/common.ts +++ b/apps/ui/src/routes/common.ts @@ -6,6 +6,7 @@ import SpaceDelegates from '@/views/Space/Delegates.vue'; import SpaceDiscussions from '@/views/Space/Discussions.vue'; import SpaceEditor from '@/views/Space/Editor.vue'; import SpaceLeaderboard from '@/views/Space/Leaderboard.vue'; +import SpaceMembers from '@/views/Space/Members.vue'; import SpaceOverview from '@/views/Space/Overview.vue'; import SpaceProposals from '@/views/Space/Proposals.vue'; import SpaceSearch from '@/views/Space/Search.vue'; @@ -71,6 +72,11 @@ export const spaceChildrenRoutes: RouteRecordRaw[] = [ name: 'space-leaderboard', component: SpaceLeaderboard }, + { + path: 'members', + name: 'space-members', + component: SpaceMembers + }, { path: 'profile/:user', name: 'space-user', diff --git a/apps/ui/src/views/Space/Members.vue b/apps/ui/src/views/Space/Members.vue new file mode 100644 index 000000000..ef53ca3aa --- /dev/null +++ b/apps/ui/src/views/Space/Members.vue @@ -0,0 +1,128 @@ + + +