diff --git a/packages/ui/dev/scripts/generateMocks.ts b/packages/ui/dev/scripts/generateMocks.ts index 311e1c1c87..27f38e3ab0 100644 --- a/packages/ui/dev/scripts/generateMocks.ts +++ b/packages/ui/dev/scripts/generateMocks.ts @@ -1,6 +1,7 @@ import yargs from 'yargs' import { eventsModule } from './generateEventMocks' +import { forumModule } from './generators/forum/generateForumMocks' import { generateAllEvents } from './generators/generateEvents' import { generateMembers } from './generators/generateMembers' import { generateOpeningsAndUpcomingOpenings } from './generators/generateOpeningsAndUpcomingOpenings' @@ -44,4 +45,5 @@ yargs(process.argv.slice(2)) .scriptName('') .command(allModule) .command(eventsModule) + .command(forumModule) .demandCommand().argv diff --git a/packages/ui/dev/scripts/generators/forum/generateCategories.ts b/packages/ui/dev/scripts/generators/forum/generateCategories.ts new file mode 100644 index 0000000000..03740eea18 --- /dev/null +++ b/packages/ui/dev/scripts/generators/forum/generateCategories.ts @@ -0,0 +1,17 @@ +import faker from 'faker' + +import { RawForumCategoryMock } from '@/mocks/data/seedForum' + +import { randomFromRange } from '../utils' + +export const generateCategories = (): RawForumCategoryMock[] => { + let nextId = 0 + + return [...new Array(5)].map(() => { + return { + id: String(nextId++), + title: faker.lorem.words(randomFromRange(3, 5)), + description: faker.lorem.paragraph(randomFromRange(2, 3)), + } + }) +} diff --git a/packages/ui/dev/scripts/generators/forum/generateForumMocks.ts b/packages/ui/dev/scripts/generators/forum/generateForumMocks.ts new file mode 100644 index 0000000000..cc43d1e1d5 --- /dev/null +++ b/packages/ui/dev/scripts/generators/forum/generateForumMocks.ts @@ -0,0 +1,19 @@ +import { saveFile } from '../../helpers/saveFile' + +import { generateCategories } from './generateCategories' +import { generateForumThreads } from './generateForumThreads' + +export const generateForum = () => { + const forumCategories = generateCategories() + const forumThreads = generateForumThreads(forumCategories) + + const forumMocks = { forumCategories, forumThreads } + + Object.entries(forumMocks).forEach(([fileName, contents]) => saveFile(fileName, contents)) +} + +export const forumModule = { + command: 'forum', + describe: 'Generate forum from other mocks', + handler: generateForum, +} diff --git a/packages/ui/dev/scripts/generators/forum/generateForumThreads.ts b/packages/ui/dev/scripts/generators/forum/generateForumThreads.ts new file mode 100644 index 0000000000..c896232bc0 --- /dev/null +++ b/packages/ui/dev/scripts/generators/forum/generateForumThreads.ts @@ -0,0 +1,22 @@ +import faker from 'faker' + +import { RawForumCategoryMock, RawForumThreadMock } from '@/mocks/data/seedForum' + +import { randomFromRange } from '../utils' + +export const generateForumThreads = (forumCategories: Pick[]): RawForumThreadMock[] => { + let nextId = 0 + + return forumCategories + .map(({ id }) => { + return [...new Array(randomFromRange(3, 10))].map(() => { + return { + id: String(nextId++), + categoryId: id, + isSticky: !(nextId % 5), + title: faker.lorem.words(randomFromRange(4, 8)), + } + }) + }) + .flatMap((a) => a) +} diff --git a/packages/ui/src/accounts/hooks/useBalance.ts b/packages/ui/src/accounts/hooks/useBalance.ts index 4bb95651f9..38e96d043e 100644 --- a/packages/ui/src/accounts/hooks/useBalance.ts +++ b/packages/ui/src/accounts/hooks/useBalance.ts @@ -8,7 +8,7 @@ import { Balances } from '../types' export const useBalance = (address?: Address): Balances | null => { const { api, connectionState } = useApi() - const balances = useObservable(address ? api.derive.balances.all(address) : undefined, [connectionState, address]) + const balances = useObservable(address ? api?.derive.balances.all(address) : undefined, [connectionState, address]) if (balances === undefined) { return null diff --git a/packages/ui/src/accounts/modals/TransferModal/TransferSignModal.tsx b/packages/ui/src/accounts/modals/TransferModal/TransferSignModal.tsx index 0e4ab8acd3..bd3ee94d69 100644 --- a/packages/ui/src/accounts/modals/TransferModal/TransferSignModal.tsx +++ b/packages/ui/src/accounts/modals/TransferModal/TransferSignModal.tsx @@ -40,8 +40,12 @@ export function TransferSignModal({ onClose, from, amount, to, service }: Props) const fromAddress = from.address const balanceFrom = useBalance(fromAddress) const balanceTo = useBalance(toAddress) - const { api } = useApi() - const transaction = useMemo(() => api.tx?.balances?.transfer(toAddress, amount), [toAddress, amount]) + const { api, connectionState } = useApi() + const transaction = useMemo(() => api?.tx?.balances?.transfer(toAddress, amount), [ + toAddress, + amount, + connectionState, + ]) const { paymentInfo, sign, isReady } = useSignAndSendTransaction({ transaction, signer: fromAddress, service }) return ( diff --git a/packages/ui/src/app/App.tsx b/packages/ui/src/app/App.tsx index 8a8d9aa0b5..bb93931292 100644 --- a/packages/ui/src/app/App.tsx +++ b/packages/ui/src/app/App.tsx @@ -1,15 +1,14 @@ -import React, { ReactNode } from 'react' +import React from 'react' import { Redirect, Route, Switch } from 'react-router-dom' import { ConnectionStatus } from '@/common/components/ConnectionStatus' -import { Loading } from '@/common/components/Loading' import { Page } from '@/common/components/page/Page' -import { useApi } from '@/common/hooks/useApi' import { ProposalsRoutes } from '@/proposals/constants/routes' import { ExtensionWarning } from './components/ExtensionWarning' import { SideBar } from './components/SideBar' import { GlobalModals } from './GlobalModals' +import { Forum } from './pages/Forum' import { Members } from './pages/Members/Members' import { MyAccounts } from './pages/Profile/MyAccounts' import { MyMemberships } from './pages/Profile/MyMemberships' @@ -29,45 +28,34 @@ import { Providers } from './Providers' export const App = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ) - -export const WaitForAPI = ({ children }: { children: ReactNode }) => { - const { connectionState } = useApi() - - if (connectionState === 'connecting') { - return - } - - return <>{children} -} diff --git a/packages/ui/src/app/components/SideBar.tsx b/packages/ui/src/app/components/SideBar.tsx index e47c80ab88..6c9eee895b 100644 --- a/packages/ui/src/app/components/SideBar.tsx +++ b/packages/ui/src/app/components/SideBar.tsx @@ -81,7 +81,7 @@ export const SideBar = () => { - + Forum diff --git a/packages/ui/src/app/components/WaitForAPI.tsx b/packages/ui/src/app/components/WaitForAPI.tsx new file mode 100644 index 0000000000..90b3478dcd --- /dev/null +++ b/packages/ui/src/app/components/WaitForAPI.tsx @@ -0,0 +1,14 @@ +import React, { ReactNode } from 'react' + +import { Loading } from '@/common/components/Loading' +import { useApi } from '@/common/hooks/useApi' + +export const WaitForAPI = ({ children }: { children: ReactNode }) => { + const { connectionState } = useApi() + + if (connectionState === 'connecting') { + return + } + + return <>{children} +} diff --git a/packages/ui/src/app/index.ts b/packages/ui/src/app/index.ts index d213231eaf..4f75cc93a8 100644 --- a/packages/ui/src/app/index.ts +++ b/packages/ui/src/app/index.ts @@ -1 +1,2 @@ export * from './App' +export { WaitForAPI } from '@/app/components/WaitForAPI' diff --git a/packages/ui/src/app/pages/Forum/Forum.tsx b/packages/ui/src/app/pages/Forum/Forum.tsx new file mode 100644 index 0000000000..06eb77e3b4 --- /dev/null +++ b/packages/ui/src/app/pages/Forum/Forum.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Route, Switch } from 'react-router-dom' + +import { ForumCategories } from '@/app/pages/Forum/ForumCategories' +import { ForumCategory } from '@/app/pages/Forum/ForumCategory' + +export const Forum = () => { + return ( + + + + + ) +} diff --git a/packages/ui/src/app/pages/Forum/ForumCategories.tsx b/packages/ui/src/app/pages/Forum/ForumCategories.tsx new file mode 100644 index 0000000000..7a2464b6ec --- /dev/null +++ b/packages/ui/src/app/pages/Forum/ForumCategories.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import { PageLayout } from '@/app/components/PageLayout' +import { Loading } from '@/common/components/Loading' +import { RouterLink } from '@/common/components/RouterLink' +import { useForumCategories } from '@/forum/hooks/useForumCategories' + +export const ForumCategories = () => { + const { isLoading, forumCategories } = useForumCategories() + + return ( + Categories} + main={ +
+ {isLoading && } + {!isLoading && + forumCategories.length && + forumCategories.map((category) => { + return ( +
+ {category.id} | {category.title} +
+ ) + })} +
+ } + /> + ) +} diff --git a/packages/ui/src/app/pages/Forum/ForumCategory.tsx b/packages/ui/src/app/pages/Forum/ForumCategory.tsx new file mode 100644 index 0000000000..0b74fa8229 --- /dev/null +++ b/packages/ui/src/app/pages/Forum/ForumCategory.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { useParams } from 'react-router-dom' + +import { PageLayout } from '@/app/components/PageLayout' +import { Loading } from '@/common/components/Loading' +import { useForumCategoryThreads } from '@/forum/hooks/useForumCategoryThreads' + +export const ForumCategory = () => { + const { id } = useParams<{ id: string }>() + + const { isLoading, threads } = useForumCategoryThreads(id) + + return ( + Category} + main={ +
+ {isLoading && } + {!isLoading && + threads.length && + threads.map((thread) => { + return ( +
+ {thread.id} | {thread.isSticky ? '📌' : ''} {thread.title} +
+ ) + })} +
+ } + /> + ) +} diff --git a/packages/ui/src/app/pages/Forum/index.ts b/packages/ui/src/app/pages/Forum/index.ts new file mode 100644 index 0000000000..6924357750 --- /dev/null +++ b/packages/ui/src/app/pages/Forum/index.ts @@ -0,0 +1 @@ +export * from './Forum' diff --git a/packages/ui/src/app/pages/WorkingGroups/MyRoles/MyRole.tsx b/packages/ui/src/app/pages/WorkingGroups/MyRoles/MyRole.tsx index 7d906420a4..f8f2e0cab3 100644 --- a/packages/ui/src/app/pages/WorkingGroups/MyRoles/MyRole.tsx +++ b/packages/ui/src/app/pages/WorkingGroups/MyRoles/MyRole.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef } from 'react' +import React, { useCallback, useMemo, useRef } from 'react' import { useParams } from 'react-router-dom' import styled from 'styled-components' @@ -28,6 +28,7 @@ import { ApplicationDetailsModalCall } from '@/working-groups/modals/Application import { ModalTypes } from '@/working-groups/modals/ChangeAccountModal/constants' import { LeaveRoleModalCall } from '@/working-groups/modals/LeaveRoleModal' +import { useMyMemberships } from '../../../../memberships/hooks/useMyMemberships' import { useWorkerUnstakingPeriodEnd } from '../../../../working-groups/hooks/useWorkerUnstakingPeriodEnd' import { getRoleWarning } from '../../../../working-groups/model/getRoleWarning' @@ -35,6 +36,12 @@ export const MyRole = () => { const { id } = useParams<{ id: string }>() const { worker, isLoading } = useWorker(id) + const { members } = useMyMemberships() + + const isOwn = useMemo(() => { + return !!members.find((member) => member.id === worker?.membership.id) + }, [members.length, worker?.id]) + const isActive = worker && worker.status === 'WorkerStatusActive' const isLeaving = worker && worker.status === 'WorkerStatusLeaving' @@ -89,7 +96,7 @@ export const MyRole = () => { Opening - {isActive && ( + {isActive && isOwn && ( Leave this position @@ -130,7 +137,7 @@ export const MyRole = () => { - {isActive && ( + {isActive && isOwn && ( Change Role Account @@ -155,7 +162,7 @@ export const MyRole = () => { - {isActive && ( + {isActive && isOwn && ( Change Reward Account @@ -168,7 +175,7 @@ export const MyRole = () => { } sidebar={ - + } footer={ diff --git a/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx b/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx index 0f931f3dc7..db3d6647d2 100644 --- a/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx +++ b/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx @@ -74,10 +74,18 @@ export const UpcomingOpening = () => { - - + + - + {opening.hiringLimit ? ( + + ) : ( + + )} @@ -94,7 +102,7 @@ export const UpcomingOpening = () => { } footer={ - + } /> diff --git a/packages/ui/src/common/components/ConnectionStatus.tsx b/packages/ui/src/common/components/ConnectionStatus.tsx index 27545af697..3b190c8306 100644 --- a/packages/ui/src/common/components/ConnectionStatus.tsx +++ b/packages/ui/src/common/components/ConnectionStatus.tsx @@ -12,23 +12,23 @@ export const ConnectionStatus = () => { const show = useCallback(() => setShowNotification(true), []) const hide = useCallback(() => setShowNotification(false), []) const onConnected = useCallback(() => { - api.once('disconnected', onDisconnected) + api?.once('disconnected', onDisconnected) show() - }, []) + }, [api]) const onDisconnected = useCallback(() => { - api.once('connected', onConnected) + api?.once('connected', onConnected) show() - }, []) + }, [api]) useEffect(() => { - api.once('disconnected', onDisconnected) - api.once('connected', onConnected) + api?.once('disconnected', onDisconnected) + api?.once('connected', onConnected) return () => { - api.off('connected', onConnected) - api.off('disconnected', onDisconnected) + api?.off('connected', onConnected) + api?.off('disconnected', onDisconnected) } - }, []) + }, [api]) useEffect(() => { if (!showNotification) { diff --git a/packages/ui/src/common/components/statistics/DurationStatistics.stories.tsx b/packages/ui/src/common/components/statistics/DurationStatistics.stories.tsx index 9e83ff3ee2..dc41466cfe 100644 --- a/packages/ui/src/common/components/statistics/DurationStatistics.stories.tsx +++ b/packages/ui/src/common/components/statistics/DurationStatistics.stories.tsx @@ -8,20 +8,26 @@ import { DurationStatistics, DurationStatisticsProps, Statistics } from '.' export default { title: 'Common/Statistics/DurationStatistics', component: DurationStatistics, - argTypes: { value: { control: 'date' } }, + argTypes: { value: { control: 'date' }, from: { control: 'date' } }, + parameters: { controls: { exclude: ['className', 'title'] } }, } as Meta -const Template: Story = ({ value, ...props }) => ( +const Template: Story = ({ value, from, ...props }) => ( - + + ) export const Default = Template.bind({}) Default.args = { value: new Date(Date.now() + A_DAY + 10 * A_MINUTE).toISOString(), - - title: 'Statistic title', + from: new Date(Date.now() + A_DAY).toISOString(), tooltipText: 'Text to help', tooltipTitle: 'Title to help', tooltipLinkText: 'More info', diff --git a/packages/ui/src/common/components/statistics/DurationStatistics.tsx b/packages/ui/src/common/components/statistics/DurationStatistics.tsx index dbfaf64466..37b3d38862 100644 --- a/packages/ui/src/common/components/statistics/DurationStatistics.tsx +++ b/packages/ui/src/common/components/statistics/DurationStatistics.tsx @@ -17,10 +17,11 @@ const format = splitDuration([ export interface DurationStatisticsProps extends StatisticItemProps { value: string + from?: string } export const DurationStatistics = (props: DurationStatisticsProps) => { - const duration = Date.parse(props.value) - Date.now() + const duration = Date.parse(props.value) - (props.from ? Date.parse(props.from) : Date.now()) return ( {duration > A_MINUTE ? ( diff --git a/packages/ui/src/common/hooks/useCouncilSize.ts b/packages/ui/src/common/hooks/useCouncilSize.ts index af2ddfb82d..5b4c1e0cef 100644 --- a/packages/ui/src/common/hooks/useCouncilSize.ts +++ b/packages/ui/src/common/hooks/useCouncilSize.ts @@ -2,5 +2,5 @@ import { useApi } from './useApi' export const useCouncilSize = () => { const { api } = useApi() - return api.consts.council.councilSize.toNumber() + return api?.consts.council.councilSize.toNumber() } diff --git a/packages/ui/src/common/hooks/useCurrentBlockNumber.ts b/packages/ui/src/common/hooks/useCurrentBlockNumber.ts index 0f4efafff1..684ec4f39a 100644 --- a/packages/ui/src/common/hooks/useCurrentBlockNumber.ts +++ b/packages/ui/src/common/hooks/useCurrentBlockNumber.ts @@ -4,5 +4,5 @@ import { useObservable } from './useObservable' export function useCurrentBlockNumber() { const { api, connectionState } = useApi() - return useObservable(api.rpc.chain.subscribeNewHeads(), [connectionState])?.number.toBn() + return useObservable(api?.rpc.chain.subscribeNewHeads(), [connectionState])?.number.toBn() } diff --git a/packages/ui/src/common/providers/api/context.tsx b/packages/ui/src/common/providers/api/context.tsx index 116f6e96bc..8e7b58092a 100644 --- a/packages/ui/src/common/providers/api/context.tsx +++ b/packages/ui/src/common/providers/api/context.tsx @@ -1,10 +1,9 @@ -import { ApiRx } from '@polkadot/api' import { createContext } from 'react' import { UseApi } from './provider' export const ApiContext = createContext({ isConnected: false, - api: ({} as unknown) as ApiRx, + api: undefined, connectionState: 'connecting', }) diff --git a/packages/ui/src/common/providers/api/provider.tsx b/packages/ui/src/common/providers/api/provider.tsx index c704eb5f02..eff8dafa40 100644 --- a/packages/ui/src/common/providers/api/provider.tsx +++ b/packages/ui/src/common/providers/api/provider.tsx @@ -13,14 +13,34 @@ interface Props { children: ReactNode } -type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error' +type ConnectionState = 'connecting' | 'connected' | 'disconnected' -export interface UseApi { - api: ApiRx +interface BaseAPI { + api?: ApiRx isConnected: boolean connectionState: ConnectionState } +interface APIConnecting extends BaseAPI { + api: undefined + isConnected: false + connectionState: 'connecting' +} + +interface APIConnected extends BaseAPI { + api: ApiRx + isConnected: true + connectionState: 'connected' +} + +interface APIDisconnected extends BaseAPI { + api: ApiRx + isConnected: false + connectionState: 'disconnected' +} + +export type UseApi = APIConnecting | APIConnected | APIDisconnected + const endpoints: Record = { local: 'ws://127.0.0.1:9944', 'olympia-testnet': 'wss://olympia-dev.joystream.app/rpc', @@ -48,9 +68,47 @@ export const ApiContextProvider = ({ children }: Props) => { }) }, [api]) - return ( - - {children} - - ) + if (connectionState === 'connecting') { + return ( + + {children} + + ) + } + + if (connectionState === 'connected') { + return ( + + {children} + + ) + } + + if (connectionState === 'disconnected') { + return ( + + {children} + + ) + } + + return null } diff --git a/packages/ui/src/forum/hooks/useForumCategories.ts b/packages/ui/src/forum/hooks/useForumCategories.ts new file mode 100644 index 0000000000..47eeb24fe6 --- /dev/null +++ b/packages/ui/src/forum/hooks/useForumCategories.ts @@ -0,0 +1,11 @@ +import { useGetForumCategoriesQuery } from '@/forum/queries/__generated__/forum.generated' +import { asForumCategory } from '@/forum/types' + +export const useForumCategories = () => { + const { loading, data } = useGetForumCategoriesQuery() + + return { + isLoading: loading, + forumCategories: data?.forumCategories.map(asForumCategory) ?? [], + } +} diff --git a/packages/ui/src/forum/hooks/useForumCategoryThreads.ts b/packages/ui/src/forum/hooks/useForumCategoryThreads.ts new file mode 100644 index 0000000000..c2c022f4d2 --- /dev/null +++ b/packages/ui/src/forum/hooks/useForumCategoryThreads.ts @@ -0,0 +1,11 @@ +import { useGetForumThreadsQuery } from '@/forum/queries/__generated__/forum.generated' +import { asForumThread } from '@/forum/types' + +export const useForumCategoryThreads = (categoryId: string) => { + const { loading, data } = useGetForumThreadsQuery({ variables: { where: { category: { id_eq: categoryId } } } }) + + return { + isLoading: loading, + threads: data?.forumThreads.map(asForumThread) ?? [], + } +} diff --git a/packages/ui/src/forum/queries/__generated__/forum.generated.tsx b/packages/ui/src/forum/queries/__generated__/forum.generated.tsx new file mode 100644 index 0000000000..0d5366ef03 --- /dev/null +++ b/packages/ui/src/forum/queries/__generated__/forum.generated.tsx @@ -0,0 +1,136 @@ +import * as Types from '../../../common/api/queries/__generated__/baseTypes.generated' + +import { gql } from '@apollo/client' +import * as Apollo from '@apollo/client' +const defaultOptions = {} +export type ForumCategoryFieldsFragment = { + __typename: 'ForumCategory' + id: string + title: string + description: string +} + +export type ForumThreadFieldsFragment = { + __typename: 'ForumThread' + id: string + isSticky: boolean + categoryId: string + title: string +} + +export type GetForumCategoriesQueryVariables = Types.Exact<{ [key: string]: never }> + +export type GetForumCategoriesQuery = { + __typename: 'Query' + forumCategories: Array<{ __typename: 'ForumCategory' } & ForumCategoryFieldsFragment> +} + +export type GetForumThreadsQueryVariables = Types.Exact<{ + where?: Types.Maybe +}> + +export type GetForumThreadsQuery = { + __typename: 'Query' + forumThreads: Array<{ __typename: 'ForumThread' } & ForumThreadFieldsFragment> +} + +export const ForumCategoryFieldsFragmentDoc = gql` + fragment ForumCategoryFields on ForumCategory { + id + title + description + } +` +export const ForumThreadFieldsFragmentDoc = gql` + fragment ForumThreadFields on ForumThread { + id + isSticky + categoryId + title + } +` +export const GetForumCategoriesDocument = gql` + query GetForumCategories { + forumCategories { + ...ForumCategoryFields + } + } + ${ForumCategoryFieldsFragmentDoc} +` + +/** + * __useGetForumCategoriesQuery__ + * + * To run a query within a React component, call `useGetForumCategoriesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetForumCategoriesQuery` 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 query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetForumCategoriesQuery({ + * variables: { + * }, + * }); + */ +export function useGetForumCategoriesQuery( + baseOptions?: Apollo.QueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useQuery(GetForumCategoriesDocument, options) +} +export function useGetForumCategoriesLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useLazyQuery( + GetForumCategoriesDocument, + options + ) +} +export type GetForumCategoriesQueryHookResult = ReturnType +export type GetForumCategoriesLazyQueryHookResult = ReturnType +export type GetForumCategoriesQueryResult = Apollo.QueryResult< + GetForumCategoriesQuery, + GetForumCategoriesQueryVariables +> +export const GetForumThreadsDocument = gql` + query GetForumThreads($where: ForumThreadWhereInput) { + forumThreads(where: $where) { + ...ForumThreadFields + } + } + ${ForumThreadFieldsFragmentDoc} +` + +/** + * __useGetForumThreadsQuery__ + * + * To run a query within a React component, call `useGetForumThreadsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetForumThreadsQuery` 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 query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetForumThreadsQuery({ + * variables: { + * where: // value for 'where' + * }, + * }); + */ +export function useGetForumThreadsQuery( + baseOptions?: Apollo.QueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useQuery(GetForumThreadsDocument, options) +} +export function useGetForumThreadsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useLazyQuery(GetForumThreadsDocument, options) +} +export type GetForumThreadsQueryHookResult = ReturnType +export type GetForumThreadsLazyQueryHookResult = ReturnType +export type GetForumThreadsQueryResult = Apollo.QueryResult diff --git a/packages/ui/src/forum/queries/forum.graphql b/packages/ui/src/forum/queries/forum.graphql new file mode 100644 index 0000000000..ce68253257 --- /dev/null +++ b/packages/ui/src/forum/queries/forum.graphql @@ -0,0 +1,24 @@ +fragment ForumCategoryFields on ForumCategory { + id + title + description +} + +fragment ForumThreadFields on ForumThread { + id + isSticky + categoryId + title +} + +query GetForumCategories { + forumCategories { + ...ForumCategoryFields + } +} + +query GetForumThreads($where: ForumThreadWhereInput) { + forumThreads(where: $where) { + ...ForumThreadFields + } +} diff --git a/packages/ui/src/forum/queries/index.ts b/packages/ui/src/forum/queries/index.ts new file mode 100644 index 0000000000..cea20fd27f --- /dev/null +++ b/packages/ui/src/forum/queries/index.ts @@ -0,0 +1 @@ +export * from './__generated__/forum.generated' diff --git a/packages/ui/src/forum/types/ForumCategory.ts b/packages/ui/src/forum/types/ForumCategory.ts new file mode 100644 index 0000000000..97252b1998 --- /dev/null +++ b/packages/ui/src/forum/types/ForumCategory.ts @@ -0,0 +1,13 @@ +import { ForumCategoryFieldsFragment } from '@/forum/queries/__generated__/forum.generated' + +export interface ForumCategory { + id: string + title: string + description: string +} + +export const asForumCategory = (fields: ForumCategoryFieldsFragment): ForumCategory => ({ + id: fields.id, + title: fields.title, + description: fields.description, +}) diff --git a/packages/ui/src/forum/types/ForumThread.ts b/packages/ui/src/forum/types/ForumThread.ts new file mode 100644 index 0000000000..08ce5a863d --- /dev/null +++ b/packages/ui/src/forum/types/ForumThread.ts @@ -0,0 +1,15 @@ +import { ForumThreadFieldsFragment } from '@/forum/queries/__generated__/forum.generated' + +export interface ForumThread { + id: string + title: string + isSticky: boolean + categoryId: string +} + +export const asForumThread = (fields: ForumThreadFieldsFragment): ForumThread => ({ + id: fields.id, + title: fields.title, + isSticky: fields.isSticky, + categoryId: fields.categoryId, +}) diff --git a/packages/ui/src/forum/types/index.ts b/packages/ui/src/forum/types/index.ts new file mode 100644 index 0000000000..dcf2af312a --- /dev/null +++ b/packages/ui/src/forum/types/index.ts @@ -0,0 +1,2 @@ +export * from './ForumCategory' +export * from './ForumThread' diff --git a/packages/ui/src/memberships/components/MemberListItem/Member.tsx b/packages/ui/src/memberships/components/MemberListItem/Member.tsx index 9bb46463d0..6b9940451e 100644 --- a/packages/ui/src/memberships/components/MemberListItem/Member.tsx +++ b/packages/ui/src/memberships/components/MemberListItem/Member.tsx @@ -15,13 +15,11 @@ import { CountInfo, Info, MemberColumn, MemberItemWrap, MemberRolesColumn } from export const MemberListItem = ({ member }: { member: Member }) => { const { showModal } = useModal() - const showMemberModal = useCallback(() => { showModal({ modal: 'Member', data: { id: member.id } }) }, [member.id]) - - const { api } = useApi() - const council = useObservable(api.query.council.councilMembers(), []) + const { api, connectionState } = useApi() + const council = useObservable(api?.query.council.councilMembers(), [connectionState]) const councilMembersIds = council?.map(({ membership_id }) => membership_id.toNumber()) ?? [] const isCouncil = (id: number) => councilMembersIds.includes(id) diff --git a/packages/ui/src/memberships/hooks/useTransferInviteFee.ts b/packages/ui/src/memberships/hooks/useTransferInviteFee.ts index 63fa33bc75..34f44675e7 100644 --- a/packages/ui/src/memberships/hooks/useTransferInviteFee.ts +++ b/packages/ui/src/memberships/hooks/useTransferInviteFee.ts @@ -5,10 +5,10 @@ import { useApi } from '../../common/hooks/useApi' import { Member } from '../types' export function useTransferInviteFee(member?: Member) { - const { api } = useApi() - const transaction = useMemo(() => (member ? api.tx?.members?.transferInvites(member.id, member.id, 1) : undefined), [ - member, - ]) + const { api, connectionState } = useApi() + const transaction = useMemo(() => { + return member ? api?.tx?.members?.transferInvites(member.id, member.id, 1) : undefined + }, [member, connectionState]) return useTransactionFee(member?.controllerAccount, transaction) } diff --git a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx index fbcdbdedef..47ea448c1c 100644 --- a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx +++ b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipFormModal.tsx @@ -67,7 +67,7 @@ export interface MemberFormFields { } export const BuyMembershipFormModal = ({ onClose, onSubmit, membershipPrice }: CreateProps) => { - const { api } = useApi() + const { api, connectionState } = useApi() const initializer = { name: '', @@ -88,7 +88,10 @@ export const BuyMembershipFormModal = ({ onClose, onSubmit, membershipPrice }: C const filterController = useCallback(filterAccount(rootAccount), [rootAccount]) const handleHash = blake2AsHex(handle) - const potentialMemberIdSize = useObservable(api.query.members.memberIdByHandleHash.size(handleHash), [handle]) + const potentialMemberIdSize = useObservable(api?.query.members.memberIdByHandleHash.size(handleHash), [ + handle, + connectionState, + ]) useEffect(() => { setContext({ size: potentialMemberIdSize }) @@ -218,11 +221,11 @@ export const BuyMembershipFormModal = ({ onClose, onSubmit, membershipPrice }: C changeField('hasTerms', value)}> I agree to the{' '} - + Terms of Service {' '} and{' '} - + Privacy Policy . diff --git a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipModal.tsx b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipModal.tsx index 2e7a703ba5..e1916ec71e 100644 --- a/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipModal.tsx +++ b/packages/ui/src/memberships/modals/BuyMembershipModal/BuyMembershipModal.tsx @@ -14,8 +14,8 @@ import { buyMembershipMachine } from './machine' export const BuyMembershipModal = () => { const { hideModal } = useModal() - const { api } = useApi() - const membershipPrice = useObservable(api.query.members.membershipPrice(), []) + const { api, connectionState } = useApi() + const membershipPrice = useObservable(api?.query.members.membershipPrice(), [connectionState]) const [state, send] = useMachine(buyMembershipMachine) if (state.matches('prepare')) { @@ -24,7 +24,7 @@ export const BuyMembershipModal = () => { return } - if (state.matches('transaction')) { + if (state.matches('transaction') && api) { const transaction = api.tx.members.buyMembership(toMemberTransactionParams(state.context.form)) const { form } = state.context const service = state.children.transaction diff --git a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberFormModal.tsx b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberFormModal.tsx index 8d9832ed09..da313a5789 100644 --- a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberFormModal.tsx +++ b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberFormModal.tsx @@ -54,7 +54,7 @@ export const InviteMemberFormModal = ({ onClose, onSubmit }: InviteProps) => { const onCreate = () => onSubmit(fields) const handleHash = blake2AsHex(handle) - const potentialMemberIdSize = useObservable(api.query.members.memberIdByHandleHash.size(handleHash), [handle]) + const potentialMemberIdSize = useObservable(api?.query.members.memberIdByHandleHash.size(handleHash), [handle, api]) useEffect(() => { setContext({ size: potentialMemberIdSize, keyring }) diff --git a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberModal.tsx b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberModal.tsx index 58e61803c1..78889421a8 100644 --- a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberModal.tsx +++ b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberModal.tsx @@ -19,9 +19,9 @@ interface MembershipModalProps { } export function InviteMemberModal({ onClose }: MembershipModalProps) { - const { api } = useApi() - const workingGroupBudget = useObservable(api.query.membershipWorkingGroup.budget(), []) - const membershipPrice = useObservable(api.query.members.membershipPrice(), []) + const { api, connectionState } = useApi() + const workingGroupBudget = useObservable(api?.query.membershipWorkingGroup.budget(), [connectionState]) + const membershipPrice = useObservable(api?.query.members.membershipPrice(), [connectionState]) const [state, send] = useMachine(inviteMemberMachine) useEffect(() => { @@ -43,7 +43,7 @@ export function InviteMemberModal({ onClose }: MembershipModalProps) { return send({ type: 'DONE', form: params })} /> } - if (state.matches('transaction')) { + if (state.matches('transaction') && api) { const transaction = api.tx.members.inviteMember(toMemberTransactionParams(state.context.form)) const transactionService = state.children.transaction diff --git a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberRequirementsModal.tsx b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberRequirementsModal.tsx index 18e7e466ba..a29d730b30 100644 --- a/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberRequirementsModal.tsx +++ b/packages/ui/src/memberships/modals/InviteMemberModal/InviteMemberRequirementsModal.tsx @@ -11,8 +11,8 @@ interface Props { export const InviteMemberRequirementsModal = ({ onClose }: Props) => { const { api } = useApi() - const workingGroupBudget = useObservable(api.query.membershipWorkingGroup.budget(), []) - const membershipPrice = useObservable(api.query.members.membershipPrice(), []) + const workingGroupBudget = useObservable(api?.query.membershipWorkingGroup.budget(), [api]) + const membershipPrice = useObservable(api?.query.members.membershipPrice(), [api]) return ( diff --git a/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteSignModal.tsx b/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteSignModal.tsx index eee5d86a47..22f3c28345 100644 --- a/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteSignModal.tsx +++ b/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteSignModal.tsx @@ -26,12 +26,13 @@ interface Props { } export const TransferInviteSignModal = ({ onClose, sourceMember, targetMember, amount, service }: Props) => { - const { api } = useApi() + const { api, connectionState } = useApi() const { allAccounts } = useMyAccounts() - const transaction = useMemo(() => api.tx?.members?.transferInvites(sourceMember.id, targetMember.id, amount), [ + const transaction = useMemo(() => api?.tx?.members?.transferInvites(sourceMember.id, targetMember.id, amount), [ sourceMember.id, targetMember.id, amount, + connectionState, ]) const signerAddress = sourceMember.controllerAccount const { paymentInfo, sign, isReady } = useSignAndSendTransaction({ diff --git a/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx b/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx index b1e14870c3..08e6b73d71 100644 --- a/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx +++ b/packages/ui/src/memberships/modals/UpdateMembershipModal/UpdateMembershipFormModal.tsx @@ -42,7 +42,7 @@ const UpdateMemberSchema = Yup.object().shape({ }) export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) => { - const { api } = useApi() + const { api, connectionState } = useApi() const { allAccounts } = useMyAccounts() const initializer = { @@ -62,7 +62,10 @@ export const UpdateMembershipFormModal = ({ onClose, onSubmit, member }: Props) const filterController = useCallback(filterAccount(rootAccount), [rootAccount]) const handleHash = blake2AsHex(handle || '') - const potentialMemberIdSize = useObservable(api.query.members.memberIdByHandleHash.size(handleHash), [handle]) + const potentialMemberIdSize = useObservable(api?.query.members.memberIdByHandleHash.size(handleHash), [ + handle, + connectionState, + ]) const canUpdate = isValid && hasAnyEdits(fields, member) diff --git a/packages/ui/src/mocks/data/raw/forumCategories.json b/packages/ui/src/mocks/data/raw/forumCategories.json new file mode 100644 index 0000000000..44aa4c9e25 --- /dev/null +++ b/packages/ui/src/mocks/data/raw/forumCategories.json @@ -0,0 +1,27 @@ +[ + { + "id": "0", + "title": "consequatur quasi omnis est", + "description": "Tempora error perspiciatis sapiente vero sequi adipisci voluptatum explicabo sed. Odit quis nihil non aut eligendi dolorem in et voluptatem." + }, + { + "id": "1", + "title": "doloribus ducimus ut omnis magnam", + "description": "Necessitatibus dolorem et a explicabo itaque. Voluptatum rerum aspernatur incidunt qui ratione quis ipsum amet debitis. Deleniti veniam dolorum earum qui iste qui." + }, + { + "id": "2", + "title": "quo quisquam et molestias", + "description": "Inventore nulla eius quam voluptas unde consequatur quod est. Debitis maxime rem quia temporibus enim perferendis et ut quisquam. Sed et voluptatem pariatur." + }, + { + "id": "3", + "title": "officia debitis mollitia reiciendis", + "description": "Est dolorem ut sit. Optio aut blanditiis consequatur et." + }, + { + "id": "4", + "title": "soluta iste et", + "description": "Fugiat consequatur tempora delectus. Ut quas quis officia ut dolor. Odit facere ut qui eius. Quasi quidem dicta incidunt. Asperiores facilis rerum laborum consequuntur minima ea." + } +] diff --git a/packages/ui/src/mocks/data/raw/forumThreads.json b/packages/ui/src/mocks/data/raw/forumThreads.json new file mode 100644 index 0000000000..e59d00160a --- /dev/null +++ b/packages/ui/src/mocks/data/raw/forumThreads.json @@ -0,0 +1,194 @@ +[ + { + "id": "0", + "categoryId": "0", + "isSticky": false, + "title": "ducimus aut inventore nobis optio iste est minus" + }, + { + "id": "1", + "categoryId": "0", + "isSticky": false, + "title": "quia itaque alias provident" + }, + { + "id": "2", + "categoryId": "0", + "isSticky": false, + "title": "eveniet eligendi voluptatem eveniet" + }, + { + "id": "3", + "categoryId": "0", + "isSticky": false, + "title": "quo vel blanditiis dolores praesentium sit sunt" + }, + { + "id": "4", + "categoryId": "0", + "isSticky": true, + "title": "assumenda earum est tempora sint molestiae quisquam" + }, + { + "id": "5", + "categoryId": "0", + "isSticky": false, + "title": "voluptas quos exercitationem numquam quo" + }, + { + "id": "6", + "categoryId": "1", + "isSticky": false, + "title": "dolores soluta omnis enim dolore" + }, + { + "id": "7", + "categoryId": "1", + "isSticky": false, + "title": "voluptatem qui velit reprehenderit beatae illum doloremque" + }, + { + "id": "8", + "categoryId": "1", + "isSticky": false, + "title": "quia commodi temporibus placeat quis" + }, + { + "id": "9", + "categoryId": "1", + "isSticky": true, + "title": "rerum est optio repellendus sequi consectetur repellat" + }, + { + "id": "10", + "categoryId": "1", + "isSticky": false, + "title": "est voluptatibus nesciunt esse et impedit et voluptatem" + }, + { + "id": "11", + "categoryId": "1", + "isSticky": false, + "title": "qui fugit impedit sint ea voluptate" + }, + { + "id": "12", + "categoryId": "1", + "isSticky": false, + "title": "molestias incidunt quasi laboriosam recusandae inventore" + }, + { + "id": "13", + "categoryId": "2", + "isSticky": false, + "title": "ut ullam id cum maiores veritatis" + }, + { + "id": "14", + "categoryId": "2", + "isSticky": true, + "title": "aut fugiat quam quia maiores ipsum" + }, + { + "id": "15", + "categoryId": "2", + "isSticky": false, + "title": "ut aut mollitia qui" + }, + { + "id": "16", + "categoryId": "2", + "isSticky": false, + "title": "voluptates cupiditate quibusdam exercitationem quasi asperiores" + }, + { + "id": "17", + "categoryId": "2", + "isSticky": false, + "title": "excepturi perferendis excepturi nobis dignissimos libero ipsa" + }, + { + "id": "18", + "categoryId": "3", + "isSticky": false, + "title": "mollitia id ipsa laudantium a possimus ab est" + }, + { + "id": "19", + "categoryId": "3", + "isSticky": true, + "title": "fuga deserunt aut itaque a" + }, + { + "id": "20", + "categoryId": "3", + "isSticky": false, + "title": "voluptas ea expedita velit voluptas aspernatur quisquam quod" + }, + { + "id": "21", + "categoryId": "3", + "isSticky": false, + "title": "consequatur aut aut modi eveniet qui accusamus" + }, + { + "id": "22", + "categoryId": "3", + "isSticky": false, + "title": "porro alias et vel quisquam" + }, + { + "id": "23", + "categoryId": "3", + "isSticky": false, + "title": "omnis nobis eum est" + }, + { + "id": "24", + "categoryId": "3", + "isSticky": true, + "title": "saepe quidem aperiam aut quo repudiandae" + }, + { + "id": "25", + "categoryId": "3", + "isSticky": false, + "title": "fugit quisquam consequatur sunt accusantium architecto est" + }, + { + "id": "26", + "categoryId": "4", + "isSticky": false, + "title": "temporibus minus et repudiandae quis" + }, + { + "id": "27", + "categoryId": "4", + "isSticky": false, + "title": "voluptatum fugiat quis et eligendi nam" + }, + { + "id": "28", + "categoryId": "4", + "isSticky": false, + "title": "ut earum dolorum ut qui eos ut" + }, + { + "id": "29", + "categoryId": "4", + "isSticky": true, + "title": "nisi dolores quia culpa quam recusandae" + }, + { + "id": "30", + "categoryId": "4", + "isSticky": false, + "title": "quidem sit laudantium corporis quos expedita" + }, + { + "id": "31", + "categoryId": "4", + "isSticky": false, + "title": "aut porro nihil quae quibusdam" + } +] diff --git a/packages/ui/src/mocks/data/seedForum.ts b/packages/ui/src/mocks/data/seedForum.ts new file mode 100644 index 0000000000..0c6b9ab2d3 --- /dev/null +++ b/packages/ui/src/mocks/data/seedForum.ts @@ -0,0 +1,38 @@ +import rawForumCategories from './raw/forumCategories.json' +import rawForumThreads from './raw/forumThreads.json' + +export const categoriesData = rawForumCategories.map((rawForumCategory) => ({ ...rawForumCategory })) +export const threadsData = rawForumThreads.map((rawForumThread) => ({ ...rawForumThread })) + +export interface RawForumCategoryMock { + id: string + title: string + description: string +} + +export interface RawForumThreadMock { + id: string + categoryId: string + isSticky: boolean + title: string +} + +export function seedForumCategory(forumCategoryData: RawForumCategoryMock, server: any) { + return server.schema.create('ForumCategory', { + ...forumCategoryData, + }) +} + +export const seedForumCategories = (server: any) => { + categoriesData.map((forumCategoryData) => seedForumCategory(forumCategoryData, server)) +} + +export function seedForumThread(data: RawForumThreadMock, server: any) { + return server.schema.create('ForumThread', { + ...data, + }) +} + +export const seedForumThreads = (server: any) => { + threadsData.map((data) => seedForumThread(data, server)) +} diff --git a/packages/ui/src/mocks/server.ts b/packages/ui/src/mocks/server.ts index 736c9926fb..0b8da73b65 100644 --- a/packages/ui/src/mocks/server.ts +++ b/packages/ui/src/mocks/server.ts @@ -2,6 +2,8 @@ import { createGraphQLHandler } from '@miragejs/graphql' import { createServer, Server } from 'miragejs' import { AnyRegistry } from 'miragejs/-types' +import { seedForumCategories, seedForumThreads } from '@/mocks/data/seedForum' + import schema from '../common/api/schemas/schema.graphql' import { @@ -57,44 +59,46 @@ export const makeServer = (environment = 'development') => { root: undefined, resolvers: { Query: { + applicationFormQuestionAnswers: getWhereResolver('ApplicationFormQuestionAnswer'), + applicationWithdrawnEvents: getWhereResolver('ApplicationWithdrawnEvent'), + appliedOnOpeningEvents: getWhereResolver('AppliedOnOpeningEvent'), + budgetSetEvents: getWhereResolver('BudgetSetEvent'), + budgetSpendingEvents: getWhereResolver('BudgetSpendingEvent'), + forumCategories: getWhereResolver('ForumCategory'), + forumThreads: getWhereResolver('ForumThread'), membershipByUniqueInput: getUniqueResolver('Membership'), memberships: getWhereResolver('Membership'), - searchMemberships: searchMembersResolver, membershipsConnection: getConnectionResolver('MembershipConnection'), - workingGroups: getWhereResolver('WorkingGroup'), - workingGroupByUniqueInput: getUniqueResolver('WorkingGroup'), - workingGroupOpenings: getWhereResolver('WorkingGroupOpening'), - workingGroupOpeningsConnection: getConnectionResolver('WorkingGroupOpeningConnection'), - workingGroupOpeningByUniqueInput: getUniqueResolver('WorkingGroupOpening'), - workers: getWhereResolver('Worker'), - workersConnection: getConnectionResolver('WorkerConnection'), - workerByUniqueInput: getUniqueResolver('Worker'), - workingGroupApplications: getWhereResolver('WorkingGroupApplication'), - applicationFormQuestionAnswers: getWhereResolver('ApplicationFormQuestionAnswer'), - upcomingWorkingGroupOpenings: getWhereResolver('UpcomingWorkingGroupOpening'), - upcomingWorkingGroupOpeningByUniqueInput: getUniqueResolver('UpcomingWorkingGroupOpening'), - proposals: getWhereResolver('Proposal'), + openingAddedEvents: getWhereResolver('OpeningAddedEvent'), + openingCanceledEvents: getWhereResolver('OpeningCanceledEvent'), + openingFilledEvents: getWhereResolver('OpeningFilledEvent'), proposalByUniqueInput: getUniqueResolver('Proposal'), proposalVotedEventByUniqueInput: getUniqueResolver('ProposalVotedEvent'), - runtimeWasmBytecodeByUniqueInput: getUniqueResolver('RuntimeWasmBytecode'), + proposals: getWhereResolver('Proposal'), rewardPaidEvents: getWhereResolver('RewardPaidEvent'), - budgetSpendingEvents: getWhereResolver('BudgetSpendingEvent'), - appliedOnOpeningEvents: getWhereResolver('AppliedOnOpeningEvent'), - applicationWithdrawnEvents: getWhereResolver('ApplicationWithdrawnEvent'), + runtimeWasmBytecodeByUniqueInput: getUniqueResolver('RuntimeWasmBytecode'), + searchMemberships: searchMembersResolver, stakeDecreasedEvents: getWhereResolver('StakeDecreasedEvent'), stakeIncreasedEvents: getWhereResolver('StakeIncreasedEvent'), stakeSlashedEvents: getWhereResolver('StakeSlashedEvent'), - openingFilledEvents: getWhereResolver('OpeningFilledEvent'), - workerExitedEvents: getWhereResolver('WorkerExitedEvent'), - workerStartedLeavingEvents: getWhereResolver('WorkerStartedLeavingEvent'), statusTextChangedEvents: getWhereResolver('StatusTextChangedEvent'), - openingAddedEvents: getWhereResolver('OpeningAddedEvent'), - openingCanceledEvents: getWhereResolver('OpeningCanceledEvent'), - terminatedWorkerEvents: getWhereResolver('TerminatedWorkerEvent'), terminatedLeaderEvents: getWhereResolver('TerminatedLeaderEvent'), - budgetSetEvents: getWhereResolver('BudgetSetEvent'), + terminatedWorkerEvents: getWhereResolver('TerminatedWorkerEvent'), + upcomingWorkingGroupOpeningByUniqueInput: getUniqueResolver('UpcomingWorkingGroupOpening'), + upcomingWorkingGroupOpenings: getWhereResolver('UpcomingWorkingGroupOpening'), + workerByUniqueInput: getUniqueResolver('Worker'), + workerExitedEvents: getWhereResolver('WorkerExitedEvent'), workerRewardAccountUpdatedEvents: getWhereResolver('WorkerRewardAccountUpdatedEvent'), workerRewardAmountUpdatedEvents: getWhereResolver('WorkerRewardAmountUpdatedEvent'), + workerStartedLeavingEvents: getWhereResolver('WorkerStartedLeavingEvent'), + workers: getWhereResolver('Worker'), + workersConnection: getConnectionResolver('WorkerConnection'), + workingGroupApplications: getWhereResolver('WorkingGroupApplication'), + workingGroupByUniqueInput: getUniqueResolver('WorkingGroup'), + workingGroupOpeningByUniqueInput: getUniqueResolver('WorkingGroupOpening'), + workingGroupOpenings: getWhereResolver('WorkingGroupOpening'), + workingGroupOpeningsConnection: getConnectionResolver('WorkingGroupOpeningConnection'), + workingGroups: getWhereResolver('WorkingGroup'), }, }, }) @@ -114,6 +118,8 @@ export const makeServer = (environment = 'development') => { updateWorkingGroups(server) seedProposals(server) seedEvents(server) + seedForumCategories(server) + seedForumThreads(server) }, }) } diff --git a/packages/ui/src/proposals/hooks/useConstants.ts b/packages/ui/src/proposals/hooks/useConstants.ts index 9097d4e021..c4cc986bf2 100644 --- a/packages/ui/src/proposals/hooks/useConstants.ts +++ b/packages/ui/src/proposals/hooks/useConstants.ts @@ -19,7 +19,7 @@ export const useConstants = (proposalType?: ProposalType): ProposalConstants | n if (!constantKey) { return null } - const constants = api.consts.proposalsCodex[constantKey] + const constants = api?.consts.proposalsCodex[constantKey] return constants ? asProposalConstants(constants) : null }, [proposalType, isConnected]) diff --git a/packages/ui/src/proposals/modals/AddNewProposal/AddNewProposalModal.tsx b/packages/ui/src/proposals/modals/AddNewProposal/AddNewProposalModal.tsx index 3d64d16445..8cde6ba672 100644 --- a/packages/ui/src/proposals/modals/AddNewProposal/AddNewProposalModal.tsx +++ b/packages/ui/src/proposals/modals/AddNewProposal/AddNewProposalModal.tsx @@ -58,7 +58,7 @@ const isLastStepActive = (steps: Step[]) => { } export const AddNewProposalModal = () => { - const { api } = useApi() + const { api, connectionState } = useApi() const { active: member } = useMyMemberships() const { hideModal, showModal } = useModal() const [state, send, service] = useMachine(addNewProposalMachine) @@ -77,12 +77,12 @@ export const AddNewProposalModal = () => { } const transaction = useMemo(() => { - if (member) { + if (member && api) { const txSpecificParameters = getSpecificParameters(api, state as AddNewProposalMachineState) return api.tx.proposalsCodex.createProposal(txBaseParams, txSpecificParameters) } - }, [JSON.stringify(txBaseParams), JSON.stringify(state.context.specifics)]) + }, [JSON.stringify(txBaseParams), JSON.stringify(state.context.specifics), connectionState]) const feeInfo = useTransactionFee(member?.controllerAccount, transaction) useEffect((): any => { diff --git a/packages/ui/src/proposals/modals/AddNewProposal/machine.ts b/packages/ui/src/proposals/modals/AddNewProposal/machine.ts index 3d98ee896e..052a7d45bf 100644 --- a/packages/ui/src/proposals/modals/AddNewProposal/machine.ts +++ b/packages/ui/src/proposals/modals/AddNewProposal/machine.ts @@ -206,6 +206,13 @@ export const addNewProposalMachine = createMachine { + if (context.type !== (event as SetTypeEvent).proposalType) { + return {} + } + + return context.specifics + }, type: (context, event) => (event as SetTypeEvent).proposalType, }), }, diff --git a/packages/ui/src/working-groups/modals/ApplyForRoleModal/ApplyForRoleModal.tsx b/packages/ui/src/working-groups/modals/ApplyForRoleModal/ApplyForRoleModal.tsx index 3951d79179..82ac2e23a6 100644 --- a/packages/ui/src/working-groups/modals/ApplyForRoleModal/ApplyForRoleModal.tsx +++ b/packages/ui/src/working-groups/modals/ApplyForRoleModal/ApplyForRoleModal.tsx @@ -33,7 +33,7 @@ export type OpeningParams = Exclude< > export const ApplyForRoleModal = () => { - const { api } = useApi() + const { api, connectionState } = useApi() const { active } = useMyMemberships() const { hideModal, modalData, showModal } = useModal() const [state, send, service] = useMachine(applyForRoleMachine) @@ -41,7 +41,7 @@ export const ApplyForRoleModal = () => { const requiredStake = opening.stake.toNumber() const { hasRequiredStake, transferableAccounts, accountsWithLockedFounds } = useHasRequiredStake(requiredStake) const transaction = useMemo(() => { - if (active) { + if (active && api) { return getGroup(api, opening.groupName as GroupName)?.applyOnOpening({ member_id: active?.id, opening_id: opening.runtimeId, @@ -53,7 +53,7 @@ export const ApplyForRoleModal = () => { }, }) } - }, [active?.id]) + }, [active?.id, connectionState]) const feeInfo = useTransactionFee(active?.controllerAccount, transaction) useEffect(() => { diff --git a/packages/ui/test/accounts/hooks/useMyTotalBalances.test.tsx b/packages/ui/test/accounts/hooks/useMyTotalBalances.test.tsx index 0cc59e6442..f9e4655d89 100644 --- a/packages/ui/test/accounts/hooks/useMyTotalBalances.test.tsx +++ b/packages/ui/test/accounts/hooks/useMyTotalBalances.test.tsx @@ -7,13 +7,14 @@ import React, { ReactNode } from 'react' import { useMyTotalBalances } from '@/accounts/hooks/useMyTotalBalances' import { AccountsContextProvider } from '@/accounts/providers/accounts/provider' import { ApiContext } from '@/common/providers/api/context' +import { UseApi } from '@/common/providers/api/provider' import { createBalance } from '../../_mocks/chainTypes' import { MockKeyringProvider } from '../../_mocks/providers' import { stubApi, stubBalances } from '../../_mocks/transactions' describe('useMyTotalBalances', () => { - const useApi = stubApi() + let useApi: UseApi = stubApi() jest.useFakeTimers() @@ -26,7 +27,11 @@ describe('useMyTotalBalances', () => { }) it('Returns zero balances when API not ready', () => { - useApi.isConnected = false + useApi = { + connectionState: 'connecting', + isConnected: false, + api: undefined, + } const { result } = renderUseTotalBalances() expect(result.current).toEqual({ diff --git a/packages/ui/test/common/components/ConnectionStatus.test.tsx b/packages/ui/test/common/components/ConnectionStatus.test.tsx index f248019786..ee41d12045 100644 --- a/packages/ui/test/common/components/ConnectionStatus.test.tsx +++ b/packages/ui/test/common/components/ConnectionStatus.test.tsx @@ -10,7 +10,7 @@ import { UseApi } from '@/common/providers/api/provider' describe('UI: Connection status component', () => { let eventEmitter: EventEmitter const useApi = { - api: {}, + api: undefined, isConnected: false, connectionState: 'connecting', } as UseApi diff --git a/packages/ui/test/working-groups/pages/MyRoles.test.tsx b/packages/ui/test/working-groups/pages/MyRoles.test.tsx index 435c227c2e..2bc22faa1c 100644 --- a/packages/ui/test/working-groups/pages/MyRoles.test.tsx +++ b/packages/ui/test/working-groups/pages/MyRoles.test.tsx @@ -1,5 +1,5 @@ import { cryptoWaitReady } from '@polkadot/util-crypto' -import { render, waitForElementToBeRemoved } from '@testing-library/react' +import { render, waitForElementToBeRemoved, screen } from '@testing-library/react' import React from 'react' import { MemoryRouter } from 'react-router' import { Route } from 'react-router-dom' @@ -83,6 +83,18 @@ describe('UI: My Role Page', () => { expect(queryByText('Minimal Stake:')).toBeNull() }) }) + + it('Not own role', async () => { + const worker = { ...WORKER_DATA, membershipId: 5, status: 'active' } + seedWorker(worker, mockServer.server) + + renderPage() + + await waitForElementToBeRemoved(() => screen.getByText('Loading...')) + expect(screen.queryByText(/leave this position/i)).toBeNull() + expect(screen.queryByText(/change role account/i)).toBeNull() + expect(screen.queryByText(/change reward account/i)).toBeNull() + }) }) function renderPage() {