From d09a79803bd5e65b01ba5633cd835e1faaba897f Mon Sep 17 00:00:00 2001 From: noah Date: Thu, 12 Oct 2023 16:41:04 -0700 Subject: [PATCH] Added DAO tabs, SubDAOs, and polytone addresses to command modal context. Allow larger command modal with consistent input height. (#1416) --- apps/dapp/pages/dao/[address]/[[...slug]].tsx | 19 +- apps/sda/pages/[address]/[[...slug]].tsx | 3 +- packages/i18n/locales/dog/translation.json | 4 +- packages/i18n/locales/en/translation.json | 5 +- .../components/CommandModalContextView.tsx | 18 +- .../stateful/command/contexts/generic/dao.ts | 116 ---------- .../stateful/command/contexts/generic/dao.tsx | 212 ++++++++++++++++++ .../useFollowingAndFilteredDaosSections.ts | 20 +- packages/stateful/components/SdaLayout.tsx | 3 +- .../stateful/components/dao/CreateDaoForm.tsx | 1 + .../stateful/components/dao/DaoProviders.tsx | 48 +++- packages/stateful/hooks/useDaoTabs.ts | 48 ++-- .../stateful/recoil/selectors/dao/cards.ts | 35 +++ .../components/HorizontalScroller.stories.tsx | 1 + .../components/command/CommandModal.tsx | 2 +- .../components/dao/DaoCard.stories.tsx | 1 + packages/types/command.ts | 25 ++- packages/types/dao.ts | 2 +- packages/types/stateless/DaoCard.tsx | 3 +- 19 files changed, 387 insertions(+), 179 deletions(-) delete mode 100644 packages/stateful/command/contexts/generic/dao.ts create mode 100644 packages/stateful/command/contexts/generic/dao.tsx diff --git a/apps/dapp/pages/dao/[address]/[[...slug]].tsx b/apps/dapp/pages/dao/[address]/[[...slug]].tsx index 7c71a0490..5025580ba 100644 --- a/apps/dapp/pages/dao/[address]/[[...slug]].tsx +++ b/apps/dapp/pages/dao/[address]/[[...slug]].tsx @@ -12,7 +12,6 @@ import { DaoInfoBar, DaoPageWrapper, DaoPageWrapperProps, - DaoWidgets, LinkWrapper, ProfileDaoHomeCard, SuspenseLoader, @@ -22,14 +21,13 @@ import { } from '@dao-dao/stateful' import { useActionForKey } from '@dao-dao/stateful/actions' import { makeGetDaoStaticProps } from '@dao-dao/stateful/server' -import { useWidgets } from '@dao-dao/stateful/widgets' import { DaoDappTabbedHome, useChain, useDaoInfoContext, useDaoNavHelpers, } from '@dao-dao/stateless' -import { ActionKey, DaoPageMode, WidgetLocation } from '@dao-dao/types' +import { ActionKey, DaoPageMode } from '@dao-dao/types' import { SITE_URL, getDaoPath, @@ -152,20 +150,7 @@ const InnerDaoHome = () => { useFollowingDaos(daoInfo.chainId) const following = isFollowing(daoInfo.coreAddress) - // Add home tab with widgets if any widgets exist. - const loadingDaoWidgets = useWidgets({ - // Load widgets before rendering so that home is selected if there are - // widgets. - suspendWhileLoading: true, - // Only load home widgets. - location: WidgetLocation.Home, - }) - const hasHomeWidgets = - !loadingDaoWidgets.loading && loadingDaoWidgets.data.length > 0 - - const tabs = useDaoTabs({ - includeHome: hasHomeWidgets ? DaoWidgets : undefined, - }) + const tabs = useDaoTabs() const firstTabId = tabs[0].id // Pre-fetch tabs. diff --git a/apps/sda/pages/[address]/[[...slug]].tsx b/apps/sda/pages/[address]/[[...slug]].tsx index 1acb30f96..bebdee093 100644 --- a/apps/sda/pages/[address]/[[...slug]].tsx +++ b/apps/sda/pages/[address]/[[...slug]].tsx @@ -7,7 +7,6 @@ import React, { useEffect } from 'react' import { ProfileDaoHomeCard, - SdaDaoHome, SuspenseLoader, useDaoTabs, } from '@dao-dao/stateful' @@ -25,7 +24,7 @@ const DaoHomePage: NextPage = () => { const { coreAddress } = useDaoInfoContext() const { getDaoPath } = useDaoNavHelpers() - const tabs = useDaoTabs({ includeHome: SdaDaoHome }) + const tabs = useDaoTabs() const firstTabId = tabs[0].id // Pre-fetch tabs. diff --git a/packages/i18n/locales/dog/translation.json b/packages/i18n/locales/dog/translation.json index 050b31f3c..4793e691e 100644 --- a/packages/i18n/locales/dog/translation.json +++ b/packages/i18n/locales/dog/translation.json @@ -20,7 +20,7 @@ "continue": "continnue", "copy": "copee", "copyAddressToClipboard": "copee addres 2 clipboar", - "copyDaoAddress": "copee da0 addres", + "copyDaoChainAddress": "copee {{chain}} addres", "copyToClipboard": "copee 2 clipboar", "create": "cre8", "createAProposal": "cre8 a puppozal", @@ -273,7 +273,7 @@ "configureWalletModalExplanation": "u hav keplr installd, but it dont seem lyke uv set oop a wallit. 2 continue, oopen da keplr extenshun n set oop a wallit. 2 oopen da keplr extenshun,press da puzzul icon in da top rite ov ur browzer n den press da keplr buttun. wunce uv dun dat, a new pag will oopen wer ull be abbl 2 create a new accoont. configur ur wallit 2 continue", "considerReturningHome": "considar retorning homm", "copiedAddressToClipboard": "copy adress 2 clipboar", - "copiedDaoAddress": "coppied da0 addres", + "copiedDaoChainAddress": "coppied {{chain}} addres", "copiedLinkToClipboard": "coopied link 2 clibpoard", "copiedToClipboard": "coopied 2 clipboar", "copyWalletAddressTooltip": "copy wallit adress", diff --git a/packages/i18n/locales/en/translation.json b/packages/i18n/locales/en/translation.json index 04d58c3cd..8f1d60275 100644 --- a/packages/i18n/locales/en/translation.json +++ b/packages/i18n/locales/en/translation.json @@ -71,7 +71,7 @@ "copy": "Copy", "copyAddress": "Copy address", "copyAddressToClipboard": "Copy address to clipboard", - "copyDaoAddress": "Copy DAO address", + "copyDaoChainAddress": "Copy {{chain}} address", "copyToClipboard": "Copy to clipboard", "create": "Create", "createAProposal": "Create a proposal", @@ -728,7 +728,7 @@ "configureWalletModalExplanation": "You have a wallet extension installed, but it doesn't seem like you've set up a wallet. Create a wallet to continue.", "connectedTo": "Connected to {{name}}", "copiedAddressToClipboard": "Copy address to clipboard", - "copiedDaoAddress": "Copied DAO address", + "copiedDaoChainAddress": "Copied {{chain}} address", "copiedLinkToClipboard": "Copied link to clipboard.", "copiedToClipboard": "Copied to clipboard.", "copyWalletAddressTooltip": "Copy wallet address", @@ -1322,6 +1322,7 @@ "other": "Other", "otherMembers": "Other Members", "owner": "Owner", + "pages": "Pages", "passingThreshold": "Passing threshold", "payout": "Payout", "payroll": "Payroll", diff --git a/packages/stateful/command/components/CommandModalContextView.tsx b/packages/stateful/command/components/CommandModalContextView.tsx index 0384197cc..4d3b8d94c 100644 --- a/packages/stateful/command/components/CommandModalContextView.tsx +++ b/packages/stateful/command/components/CommandModalContextView.tsx @@ -1,5 +1,5 @@ import Fuse from 'fuse.js' -import { useMemo } from 'react' +import { Fragment, useMemo } from 'react' import { CommandModalContextViewLoader, @@ -20,11 +20,17 @@ export interface CommandModalContextViewProps { export const CommandModalContextView = ( props: CommandModalContextViewProps -) => ( - }> - - -) +) => { + const Wrapper = props.contexts[props.contexts.length - 1]?.Wrapper ?? Fragment + + return ( + }> + + + + + ) +} export const InnerCommandModalContextView = ({ filter, diff --git a/packages/stateful/command/contexts/generic/dao.ts b/packages/stateful/command/contexts/generic/dao.ts deleted file mode 100644 index 540235c22..000000000 --- a/packages/stateful/command/contexts/generic/dao.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { - Check, - CheckRounded, - CopyAll, - HomeOutlined, - InboxOutlined, -} from '@mui/icons-material' -import { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useRecoilState } from 'recoil' - -import { navigatingToHrefAtom } from '@dao-dao/state' -import { useDaoNavHelpers } from '@dao-dao/stateless' -import { - CommandModalContextMaker, - CommandModalContextSection, - CommandModalDaoInfo, -} from '@dao-dao/types/command' - -import { useFollowingDaos } from '../../../hooks' - -export const makeGenericDaoContext: CommandModalContextMaker<{ - dao: CommandModalDaoInfo -}> = ({ dao: { chainId, coreAddress, name, imageUrl } }) => { - const useSections = () => { - const { t } = useTranslation() - const { getDaoPath, getDaoProposalPath, router } = useDaoNavHelpers() - - const { isFollowing, setFollowing, setUnfollowing, updatingFollowing } = - useFollowingDaos(chainId) - const following = isFollowing(coreAddress) - - const [copied, setCopied] = useState(false) - // Debounce clearing copied. - useEffect(() => { - const timeout = setTimeout(() => setCopied(false), 2000) - return () => clearTimeout(timeout) - }, [copied]) - - const [navigatingToHref, setNavigatingToHref] = - useRecoilState(navigatingToHrefAtom) - const daoPageHref = getDaoPath(coreAddress) - const createProposalHref = getDaoProposalPath(coreAddress, 'create') - - // Pre-fetch routes. - useEffect(() => { - router.prefetch(daoPageHref) - router.prefetch(createProposalHref) - }, [createProposalHref, daoPageHref, router]) - - const actionsSection: CommandModalContextSection< - { href: string } | { onChoose: () => void } - > = { - name: t('title.actions'), - onChoose: (item) => { - if ('onChoose' in item) { - return item.onChoose() - } - - //! 'href' in item - // Open remote links in new tab. - if (item.href.startsWith('http')) { - window.open(item.href, '_blank') - } else { - // Navigate to local links. - router.push(item.href) - - // If not on destination page, set navigating state. If already there, - // do nothing. - if (router.asPath !== item.href) { - setNavigatingToHref(item.href) - } - } - }, - items: [ - { - name: t('button.goToDaoPage'), - Icon: HomeOutlined, - href: daoPageHref, - loading: navigatingToHref === daoPageHref, - }, - { - name: t('button.createAProposal'), - Icon: InboxOutlined, - href: createProposalHref, - loading: navigatingToHref === createProposalHref, - }, - { - name: copied - ? t('info.copiedDaoAddress') - : t('button.copyDaoAddress'), - Icon: copied ? Check : CopyAll, - onChoose: () => { - navigator.clipboard.writeText(coreAddress) - setCopied(true) - }, - }, - { - name: following ? t('button.unfollow') : t('button.follow'), - Icon: CheckRounded, - onChoose: () => - following ? setUnfollowing(coreAddress) : setFollowing(coreAddress), - loading: updatingFollowing, - }, - ], - } - - return [actionsSection] - } - - return { - name, - imageUrl, - useSections, - } -} diff --git a/packages/stateful/command/contexts/generic/dao.tsx b/packages/stateful/command/contexts/generic/dao.tsx new file mode 100644 index 000000000..19cb63b0e --- /dev/null +++ b/packages/stateful/command/contexts/generic/dao.tsx @@ -0,0 +1,212 @@ +import { + Check, + CheckRounded, + CopyAll, + HomeOutlined, + InboxOutlined, +} from '@mui/icons-material' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useRecoilState } from 'recoil' +import useDeepCompareEffect from 'use-deep-compare-effect' + +import { navigatingToHrefAtom } from '@dao-dao/state' +import { + useCachedLoading, + useDaoInfoContext, + useDaoNavHelpers, +} from '@dao-dao/stateless' +import { ContractVersion } from '@dao-dao/types' +import { + CommandModalContextMaker, + CommandModalContextSection, + CommandModalContextWrapper, + CommandModalDaoInfo, +} from '@dao-dao/types/command' +import { getChainForChainId, getFallbackImage } from '@dao-dao/utils' + +import { DaoProvidersWithoutInfo } from '../../../components' +import { useDaoTabs, useFollowingDaos } from '../../../hooks' +import { subDaoInfosSelector } from '../../../recoil' + +export const makeGenericDaoContext: CommandModalContextMaker<{ + dao: CommandModalDaoInfo +}> = ({ + dao: { chainId, coreAddress, name, imageUrl, polytoneProxies }, + ...options +}) => { + const useSections = () => { + const { t } = useTranslation() + const { getDaoPath, getDaoProposalPath, router } = useDaoNavHelpers() + const { coreVersion } = useDaoInfoContext() + const tabs = useDaoTabs() + + const { isFollowing, setFollowing, setUnfollowing, updatingFollowing } = + useFollowingDaos(chainId) + const following = isFollowing(coreAddress) + + const [copied, setCopied] = useState() + // Debounce clearing copied. + useEffect(() => { + const timeout = setTimeout(() => setCopied(undefined), 2000) + return () => clearTimeout(timeout) + }, [copied]) + + const [navigatingToHref, setNavigatingToHref] = + useRecoilState(navigatingToHrefAtom) + const daoPageHref = getDaoPath(coreAddress) + const createProposalHref = getDaoProposalPath(coreAddress, 'create') + + const subDaosLoading = useCachedLoading( + coreVersion === ContractVersion.V1 + ? // Only v2 DAOs have SubDAOs. Passing undefined here returns an infinite loading state, which is fine because it's never used. + undefined + : subDaoInfosSelector({ + chainId, + coreAddress, + }), + [] + ) + + // Pre-fetch routes. + const routes = [ + daoPageHref, + createProposalHref, + ...tabs.map(({ id }) => getDaoPath(coreAddress, id)), + ] + useDeepCompareEffect(() => { + routes.forEach((url) => router.prefetch(url)) + }, [routes]) + + const chains = [[chainId, coreAddress], ...Object.entries(polytoneProxies)] + + const actionsSection: CommandModalContextSection< + { href: string } | { onChoose: () => void } + > = { + name: t('title.actions'), + onChoose: (item) => { + if ('onChoose' in item) { + return item.onChoose() + } + + //! 'href' in item + // Open remote links in new tab. + if (item.href.startsWith('http')) { + window.open(item.href, '_blank') + } else { + // Navigate to local links. + router.push(item.href) + + // If not on destination page, set navigating state. If already there, + // do nothing. + if (router.asPath !== item.href) { + setNavigatingToHref(item.href) + } + } + }, + items: [ + { + name: t('button.goToDaoPage'), + Icon: HomeOutlined, + href: daoPageHref, + loading: navigatingToHref === daoPageHref, + }, + { + name: t('button.createAProposal'), + Icon: InboxOutlined, + href: createProposalHref, + loading: navigatingToHref === createProposalHref, + }, + { + name: following ? t('button.unfollow') : t('button.follow'), + Icon: CheckRounded, + onChoose: () => + following ? setUnfollowing(coreAddress) : setFollowing(coreAddress), + loading: updatingFollowing, + }, + ...chains.map(([chainId, address]) => ({ + name: + copied === chainId + ? t('info.copiedDaoChainAddress', { + chain: getChainForChainId(chainId).pretty_name, + }) + : t('button.copyDaoChainAddress', { + chain: getChainForChainId(chainId).pretty_name, + }), + Icon: copied === chainId ? Check : CopyAll, + onChoose: () => { + navigator.clipboard.writeText(address) + setCopied(chainId) + }, + })), + ], + } + + const pagesSection: CommandModalContextSection<{ href: string }> = { + name: t('title.pages'), + onChoose: ({ href }) => { + router.push(href) + + // If not on destination page, set navigating state. If already there, + // do nothing. + if (router.asPath !== href) { + setNavigatingToHref(href) + } + }, + items: tabs.map(({ id, label, Icon }) => { + const href = getDaoPath(coreAddress, id) + + return { + name: label, + Icon, + href, + loading: navigatingToHref === href, + } + }), + } + + const subDaosSection: CommandModalContextSection = { + name: t('title.subDaos'), + onChoose: (dao) => + options.openContext( + makeGenericDaoContext({ + ...options, + dao, + }) + ), + loading: subDaosLoading.loading, + items: subDaosLoading.loading + ? [] + : subDaosLoading.data.map( + ({ + chainId, + coreAddress, + name, + imageUrl, + polytoneProxies, + }): CommandModalDaoInfo => ({ + chainId, + coreAddress, + name, + imageUrl: imageUrl || getFallbackImage(coreAddress), + polytoneProxies, + }) + ), + } + + return [actionsSection, pagesSection, subDaosSection] + } + + const Wrapper: CommandModalContextWrapper = ({ children }) => ( + + {children} + + ) + + return { + name, + imageUrl, + useSections, + Wrapper, + } +} diff --git a/packages/stateful/command/hooks/useFollowingAndFilteredDaosSections.ts b/packages/stateful/command/hooks/useFollowingAndFilteredDaosSections.ts index a822730da..935471da5 100644 --- a/packages/stateful/command/hooks/useFollowingAndFilteredDaosSections.ts +++ b/packages/stateful/command/hooks/useFollowingAndFilteredDaosSections.ts @@ -9,7 +9,11 @@ import { CommandModalContextUseSectionsOptions, CommandModalDaoInfo, } from '@dao-dao/types' -import { getFallbackImage, getSupportedChains } from '@dao-dao/utils' +import { + getFallbackImage, + getSupportedChains, + polytoneNoteProxyMapToChainIdMap, +} from '@dao-dao/utils' import { useLoadingFeaturedDaoCardInfos, @@ -74,7 +78,9 @@ export const useFollowingAndFilteredDaosSections = ({ coreAddress: contractAddress, name, imageUrl: image_url || getFallbackImage(contractAddress), - polytoneProxies: Object.values(polytoneProxies || {}), + polytoneProxies: polytoneProxies + ? polytoneNoteProxyMapToChainIdMap(chainId, polytoneProxies) + : {}, // If DAO has no proposals, make it less visible and give it a // tooltip to indicate that it may not be active. ...(proposalCount === 0 && { @@ -88,6 +94,12 @@ export const useFollowingAndFilteredDaosSections = ({ ? [] : featuredDaosLoading.data + // When filter present, use search results. Otherwise use featured DAOs. + const daosLoading = options.filter + ? queryResults.state === 'loading' || + (queryResults.state === 'hasValue' && queryResults.updating) + : featuredDaosLoading.loading || !!featuredDaosLoading.updating + const followingSection: CommandModalContextSection = { name: t('title.following'), onChoose, @@ -102,9 +114,7 @@ export const useFollowingAndFilteredDaosSections = ({ name: t('title.daos'), onChoose, items: daos, - loading: - queryResults.state === 'loading' || - (queryResults.state === 'hasValue' && queryResults.updating), + loading: daosLoading, } return [followingSection, daosSection] diff --git a/packages/stateful/components/SdaLayout.tsx b/packages/stateful/components/SdaLayout.tsx index 8731cdb8f..5e24c5d27 100644 --- a/packages/stateful/components/SdaLayout.tsx +++ b/packages/stateful/components/SdaLayout.tsx @@ -20,7 +20,6 @@ import { import { useDaoTabs, useWallet, useWalletInfo } from '../hooks' import { daoCreatedCardPropsAtom } from '../recoil/atoms/newDao' import { ConnectWallet } from './ConnectWallet' -import { SdaDaoHome } from './dao' import { IconButtonLink } from './IconButtonLink' import { LinkWrapper } from './LinkWrapper' import { SidebarWallet } from './SidebarWallet' @@ -55,7 +54,7 @@ export const SdaLayout = ({ children }: { children: ReactNode }) => { daoCreatedCardPropsAtom ) - const tabs = useDaoTabs({ includeHome: SdaDaoHome }) + const tabs = useDaoTabs() return ( ( ) + +export type DaoProvidersWithoutInfoProps = { + chainId: string + coreAddress: string + children: ReactNode +} + +export const DaoProvidersWithoutInfo = ({ + chainId, + coreAddress, + children, +}: DaoProvidersWithoutInfoProps) => { + const { t } = useTranslation() + const infoLoading = useCachedLoadingWithError( + daoInfoSelector({ + chainId, + coreAddress, + }) + ) + + return ( + } forceFallback={infoLoading.loading}> + {!infoLoading.loading && + (infoLoading.errored ? ( + +
+              {infoLoading.error instanceof Error
+                ? infoLoading.error.message
+                : `${infoLoading.error}`}
+            
+
+ ) : ( + {children} + ))} +
+ ) +} diff --git a/packages/stateful/hooks/useDaoTabs.ts b/packages/stateful/hooks/useDaoTabs.ts index 96f486426..1c0fc0562 100644 --- a/packages/stateful/hooks/useDaoTabs.ts +++ b/packages/stateful/hooks/useDaoTabs.ts @@ -3,32 +3,34 @@ import { FiberSmartRecordOutlined, HomeOutlined, HowToVoteOutlined, + QuestionMark, WebOutlined, } from '@mui/icons-material' -import { ComponentType } from 'react' import { useTranslation } from 'react-i18next' -import { useDaoInfoContext } from '@dao-dao/stateless' -import { DaoTabId, DaoTabWithComponent, WidgetLocation } from '@dao-dao/types' +import { useAppContext, useDaoInfoContext } from '@dao-dao/stateless' +import { + DaoPageMode, + DaoTabId, + DaoTabWithComponent, + WidgetLocation, +} from '@dao-dao/types' import { BrowserTab, + DaoWidgets, ProposalsTab, + SdaDaoHome, SubDaosTab, TreasuryAndNftsTab, } from '../components' import { useVotingModuleAdapter } from '../voting-module-adapter' import { useWidgets } from '../widgets' -export type UseDaoTabsOptions = { - includeHome?: ComponentType -} - -export const useDaoTabs = ({ - includeHome, -}: UseDaoTabsOptions = {}): DaoTabWithComponent[] => { +export const useDaoTabs = (): DaoTabWithComponent[] => { const { t } = useTranslation() + const { mode } = useAppContext() const { components: { extraTabs }, } = useVotingModuleAdapter() @@ -49,18 +51,38 @@ export const useDaoTabs = ({ }): DaoTabWithComponent => ({ id, label: title, - Icon, + // Icon should always be defined for tab widgets, but just in case... + Icon: Icon || QuestionMark, Component: WidgetComponent, }) ) + // Add home tab with widgets if any widgets exist. + const loadingDaoHomeWidgets = useWidgets({ + // In dApp, load widgets before rendering to decide if home with widgets is + // shown so that we know to select home by default when present. In SDA, no + // need to load widgets before rendering since the home is always shown. + suspendWhileLoading: mode === DaoPageMode.Dapp, + // Only load home widgets. + location: WidgetLocation.Home, + }) + const hasHomeWidgets = + !loadingDaoHomeWidgets.loading && loadingDaoHomeWidgets.data.length > 0 + + const HomeTab = + mode === DaoPageMode.Sda + ? SdaDaoHome + : mode === DaoPageMode.Dapp && hasHomeWidgets + ? DaoWidgets + : undefined + return [ - ...(includeHome + ...(HomeTab ? [ { id: DaoTabId.Home, label: t('title.home'), - Component: includeHome, + Component: HomeTab, Icon: HomeOutlined, }, ] diff --git a/packages/stateful/recoil/selectors/dao/cards.ts b/packages/stateful/recoil/selectors/dao/cards.ts index 49f95d8d3..847b5c46c 100644 --- a/packages/stateful/recoil/selectors/dao/cards.ts +++ b/packages/stateful/recoil/selectors/dao/cards.ts @@ -13,6 +13,7 @@ import { DaoCardInfo, DaoCardInfoLazyData, DaoDropdownInfo, + DaoInfo, IndexerDumpState, WithChainId, } from '@dao-dao/types' @@ -38,6 +39,7 @@ import { proposalModuleAdapterProposalCountSelector } from '../../../proposal-mo import { daoCoreProposalModulesSelector, daoCw20GovernanceTokenAddressSelector, + daoInfoSelector, } from './misc' export const daoCardInfoSelector = selectorFamily< @@ -76,6 +78,13 @@ export const daoCardInfoSelector = selectorFamily< contractInstantiateTimeSelector({ address: coreAddress, chainId }) ) + const polytoneProxies = get( + DaoCoreV2Selectors.polytoneProxiesSelector({ + chainId, + contractAddress: coreAddress, + }) + ) + // Get parent DAO if exists. let parentDao: DaoCardInfo['parentDao'] if ( @@ -208,6 +217,7 @@ export const daoCardInfoSelector = selectorFamily< name: config.name, description: config.description, imageUrl: config.image_url || getFallbackImage(coreAddress), + polytoneProxies, established, parentDao, tokenDecimals: 6, @@ -309,6 +319,31 @@ export const subDaoCardInfosSelector = selectorFamily< }, }) +export const subDaoInfosSelector = selectorFamily< + DaoInfo[], + WithChainId<{ coreAddress: string }> +>({ + key: 'subDaoInfos', + get: + ({ coreAddress: contractAddress, chainId }) => + ({ get }) => { + const subdaos = get( + DaoCoreV2Selectors.listAllSubDaosSelector({ + contractAddress, + chainId, + }) + ) + + return get( + waitForAll( + subdaos.map(({ addr }) => + daoInfoSelector({ coreAddress: addr, chainId }) + ) + ) + ) + }, +}) + export const daoDropdownInfoSelector: ( params: WithChainId<{ coreAddress: string diff --git a/packages/stateless/components/HorizontalScroller.stories.tsx b/packages/stateless/components/HorizontalScroller.stories.tsx index 9b3ceae70..e81d84c50 100644 --- a/packages/stateless/components/HorizontalScroller.stories.tsx +++ b/packages/stateless/components/HorizontalScroller.stories.tsx @@ -23,6 +23,7 @@ const makeFeaturedDao = (): DaoCardInfo => ({ description: 'This approach allows us to implement a completely custom component design without writing a single line of custom CSS.', imageUrl: `/placeholders/${(id % 5) + 1}.svg`, + polytoneProxies: {}, established: new Date('May 14, 2022 00:00:00'), tokenSymbol: 'JUNO', showingEstimatedUsdValue: false, diff --git a/packages/stateless/components/command/CommandModal.tsx b/packages/stateless/components/command/CommandModal.tsx index f520011d7..03a3e56e3 100644 --- a/packages/stateless/components/command/CommandModal.tsx +++ b/packages/stateless/components/command/CommandModal.tsx @@ -30,7 +30,7 @@ export const CommandModal = ({ return ( ({ description: 'This approach allows us to implement a completely custom component design without writing a single line of custom CSS.', imageUrl: `/placeholders/${id % 5}.svg`, + polytoneProxies: {}, // Random date in the past 12 months. established: new Date( Date.now() - Math.floor(Math.random() * 12 * 30 * 24 * 60 * 60 * 1000) diff --git a/packages/types/command.ts b/packages/types/command.ts index ca45b6ede..1cf6b130a 100644 --- a/packages/types/command.ts +++ b/packages/types/command.ts @@ -1,7 +1,9 @@ -import { ComponentType } from 'react' +import { ComponentType, ReactNode } from 'react' import { TFunction } from 'react-i18next' -export interface StatefulCommandModalProps { +import { PolytoneProxies } from './dao' + +export type StatefulCommandModalProps = { visible: boolean setVisible: (visible: boolean) => void // Root context maker can take no extra options. @@ -23,13 +25,11 @@ export type CommandModalContextSectionItem< } | { imageUrl?: never - Icon: ComponentType<{ className?: string }> + Icon: ComponentType<{ className: string }> } ) -export interface CommandModalContextSection< - ExtraItemProperties extends {} = {} -> { +export type CommandModalContextSection = { name: string items: CommandModalContextSectionItem[] onChoose: (item: CommandModalContextSectionItem) => void @@ -42,7 +42,7 @@ export interface CommandModalContextSection< loading?: boolean } -export interface CommandModalContextUseSectionsOptions { +export type CommandModalContextUseSectionsOptions = { filter: string } @@ -50,12 +50,17 @@ export type CommandModalContextUseSections = ( options: CommandModalContextUseSectionsOptions ) => CommandModalContextSection[] -export interface CommandModalContext { +export type CommandModalContext = { useSections: CommandModalContextUseSections name: string imageUrl?: string + // If defined, will wrap the context with this component around where + // `useSections` will be called. + Wrapper?: CommandModalContextWrapper } +export type CommandModalContextWrapper = ComponentType<{ children: ReactNode }> + export type CommandModalContextMakerOptions = MakerOptions & { t: TFunction @@ -66,10 +71,10 @@ export type CommandModalContextMaker = ( options: CommandModalContextMakerOptions ) => CommandModalContext -export interface CommandModalDaoInfo { +export type CommandModalDaoInfo = { chainId: string coreAddress: string name: string imageUrl: string - polytoneProxies?: string[] + polytoneProxies: PolytoneProxies } diff --git a/packages/types/dao.ts b/packages/types/dao.ts index faf6183a6..0b23229d8 100644 --- a/packages/types/dao.ts +++ b/packages/types/dao.ts @@ -289,7 +289,7 @@ export type DaoTab = { // ID used in URL hash. id: DaoTabId | string label: string - Icon?: ComponentType<{ className: string }> + Icon: ComponentType<{ className: string }> } export type DaoTabWithComponent = DaoTab & { diff --git a/packages/types/stateless/DaoCard.tsx b/packages/types/stateless/DaoCard.tsx index 98e9b5c05..676998ee7 100644 --- a/packages/types/stateless/DaoCard.tsx +++ b/packages/types/stateless/DaoCard.tsx @@ -1,6 +1,6 @@ import { ComponentType } from 'react' -import { DaoParentInfo } from '../dao' +import { DaoParentInfo, PolytoneProxies } from '../dao' import { LoadingData } from './common' import { IconButtonLinkProps } from './IconButtonLink' import { LinkWrapperProps } from './LinkWrapper' @@ -18,6 +18,7 @@ export interface DaoCardInfo { name: string description: string imageUrl: string + polytoneProxies: PolytoneProxies established?: Date className?: string showIsMember?: boolean