Skip to content

Commit

Permalink
Add a Me DAOs page and improve inactive DAO handling (#1495)
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso authored Nov 30, 2023
1 parent 1d5d165 commit 42c2d56
Show file tree
Hide file tree
Showing 28 changed files with 427 additions and 53 deletions.
6 changes: 5 additions & 1 deletion packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@
"addedToDaoFollowPrompt_withoutTimestamp": "You were added as a member to this DAO. Follow it to receive updates.",
"advancedVotingConfigWarning": "These are advanced features. If you configure them without fully understanding how they work, you can lock your DAO, making it impossible for proposals to pass.",
"allAccountsCreated": "All accounts have been created.",
"allChains": "All chains",
"andNumMore": "and {{count}} more",
"anyone": "Anyone",
"appsProposalDescription": "Add a title and description, and then review the actions generated by the app. Once it all looks good, publish it to the DAO for voting.",
Expand Down Expand Up @@ -833,6 +834,8 @@
"govTokenAddress": "Governance Token",
"groupAddress": "CW4 Group",
"historySinceDate": "History since {{date}}",
"inactiveDaoTooltip": "This DAO has no proposals and may be inactive or a duplicate.",
"inactiveDaosTooltip": "These DAOs have no proposals and may be inactive or duplicates.",
"inboxConfigPreferencesDescription": "Choose where you want to receive notifications. Website notifications appear here on the Inbox page.",
"inboxDescription": "Your notification inbox.",
"inboxEmailTooltip": "Receive inbox notifications in your email.",
Expand Down Expand Up @@ -896,7 +899,6 @@
"noPayrollDescription": "Disable all payroll features and hide them from the DAO's home page.",
"noPostsFound": "No posts found.",
"noPriceData": "No price data.",
"noProposalsTooltip": "This DAO has no proposals and may be inactive or a duplicate.",
"noProposalsYet": "No proposals to vote on yet.",
"noPushNotificationSubscriptions": "You do not have any push notification subscriptions.",
"noSubDaosYet": "No SubDAOs yet.",
Expand Down Expand Up @@ -980,6 +982,7 @@
"reviewYourProposal": "Review your proposal...",
"reviewYourTransaction": "Review your transaction...",
"saveTransactionDescription": "Save this transaction so you can reuse it later. Using an existing name will overwrite the previous save.",
"searchDaosPlaceholder": "Find a DAO...",
"searchDraftPlaceholder": "Find a draft...",
"searchForChain": "Search for a chain...",
"searchForToken": "Search for a token...",
Expand Down Expand Up @@ -1298,6 +1301,7 @@
"holdings": "Holdings",
"home": "Home",
"identity": "Identity",
"inactiveDaos": "Inactive DAOs",
"inbox": "Inbox",
"inboxConfiguration": "Inbox configuration",
"inboxWithCount": "Inbox ({{count}})",
Expand Down
8 changes: 3 additions & 5 deletions packages/state/indexer/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MeiliSearch from 'meilisearch'

import { IndexerDumpState, WithChainId } from '@dao-dao/types'
import {
INACTIVE_DAO_NAMES,
SEARCH_API_KEY,
SEARCH_HOST,
getSupportedChainConfig,
Expand Down Expand Up @@ -52,11 +53,8 @@ export const searchDaos = async ({
const results = await index.search<Omit<DaoSearchResult, 'chainId'>>(query, {
limit,
filter: [
// Only show DAOs with proposals to reduce clutter/spam.
//
// UPDATE: Commenting this out for now, since many DAOs have trouble
// finding themselves before they've made a proposal.
// `(NOT value.proposalCount EXISTS) OR (value.proposalCount > 0)`,
// Exclude inactive DAOs.
`NOT value.config.name IN ["${INACTIVE_DAO_NAMES.join('", "')}"]`,
...(exclude?.length
? // Exclude DAOs that are in the exclude list.
[`NOT contractAddress IN ["${exclude.join('", "')}"]`]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ export const InnerCommandModalContextView = ({
...section,
items: itemsWithSection
.filter((optionWithSection) => optionWithSection.section === section)
.map(({ item: option }) => option),
.map(({ item: option }) => option)
.sort((a, b) =>
a.sortLast && !b.sortLast ? 1 : !a.sortLast && b.sortLast ? -1 : 0
),
}))

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const useFollowingAndFilteredDaosSections = ({
chainId: chain.chain_id,
query: options.filter,
limit,
// Exclude following DAOs from search since they show in a separate
// section.
// Exclude following DAOs from search since they show in a
// separate section.
exclude: followingDaosLoading.loading
? undefined
: followingDaosLoading.data
Expand Down Expand Up @@ -85,7 +85,8 @@ export const useFollowingAndFilteredDaosSections = ({
// tooltip to indicate that it may not be active.
...(proposalCount === 0 && {
className: 'opacity-50',
tooltip: t('info.noProposalsTooltip'),
tooltip: t('info.inactiveDaoTooltip'),
sortLast: true,
}),
})
)
Expand Down
5 changes: 3 additions & 2 deletions packages/stateful/components/ChainSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ export const ChainSwitcher = ({
<StatelessChainSwitcher
excludeChainIds={excludeChainIds}
loading={loading}
onSelect={({ chain_id }) =>
onSelect ? onSelect(chain_id) : setChainId(chain_id)
onSelect={(chain) =>
chain &&
(onSelect ? onSelect(chain.chain_id) : setChainId(chain.chain_id))
}
selected={overrideChainId || chainId}
/>
Expand Down
54 changes: 54 additions & 0 deletions packages/stateful/components/dao/LazyDaoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'

import { useCachedLoadingWithError } from '@dao-dao/stateless'
import { LazyDaoCardProps } from '@dao-dao/types'
import { processError } from '@dao-dao/utils'

import { daoCardInfoSelector } from '../../recoil'
import { DaoCard } from './DaoCard'

export const LazyDaoCard = (props: LazyDaoCardProps) => {
const { t } = useTranslation()

const daoCardInfo = useCachedLoadingWithError(
daoCardInfoSelector({
chainId: props.chainId,
coreAddress: props.coreAddress,
})
)

return daoCardInfo.loading ? (
<DaoCard
{...props}
className={clsx('animate-pulse', props.className)}
lazyData={{
loading: true,
}}
polytoneProxies={{}}
showingEstimatedUsdValue={false}
tokenDecimals={0}
tokenSymbol=""
/>
) : daoCardInfo.errored || !daoCardInfo.data ? (
<DaoCard
{...props}
description={processError(
daoCardInfo.errored ? daoCardInfo.error : t('error.loadingData'),
{
forceCapture: false,
}
)}
lazyData={{
loading: true,
}}
name={t('error.unexpectedError')}
polytoneProxies={{}}
showingEstimatedUsdValue={false}
tokenDecimals={0}
tokenSymbol=""
/>
) : (
<DaoCard {...daoCardInfo.data} />
)
}
1 change: 1 addition & 0 deletions packages/stateful/components/dao/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export * from './DaoTreasuryHistory'
export * from './DiscordNotifierConfigureModal'
export * from './DaoWidgets'
export * from './DaoProviders'
export * from './LazyDaoCard'
export * from './SdaDaoHome'
2 changes: 2 additions & 0 deletions packages/stateful/components/pages/Me.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ConnectWallet } from '../ConnectWallet'
import { ProfileDisconnectedCard, ProfileHomeCard } from '../profile'
import { SuspenseLoader } from '../SuspenseLoader'
import { MeBalances } from './MeBalances'
import { MeDaos } from './MeDaos'
import { MeTransactionBuilder } from './MeTransactionBuilder'

export const Me: NextPage = () => {
Expand Down Expand Up @@ -64,6 +65,7 @@ export const Me: NextPage = () => {
<StatelessMe
ChainSwitcher={ChainSwitcher}
MeBalances={MeBalances}
MeDaos={MeDaos}
MeTransactionBuilder={MeTransactionBuilder}
profileData={profileData}
rightSidebarContent={<ProfileHomeCard />}
Expand Down
10 changes: 10 additions & 0 deletions packages/stateful/components/pages/MeDaos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useWallet } from '../../hooks/useWallet'
import { WalletDaos } from '../wallet'

export const MeDaos = () => {
const { address: walletAddress } = useWallet({
loadAccount: false,
})

return walletAddress ? <WalletDaos walletAddress={walletAddress} /> : null
}
1 change: 1 addition & 0 deletions packages/stateful/components/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './Inbox'
export * from './Me'
export * from './MeBalances'
export * from './MeDaos'
export * from './MeTransactionBuilder'
26 changes: 26 additions & 0 deletions packages/stateful/components/wallet/WalletDaos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
WalletDaos as StatelessWalletDaos,
useCachedLoadingWithError,
} from '@dao-dao/stateless'
import { StatefulWalletDaosProps } from '@dao-dao/types'

import { allWalletDaosSelector, walletDaosSelector } from '../../recoil'
import { LazyDaoCard } from '../dao'

export const WalletDaos = ({
walletAddress,
chainId,
}: StatefulWalletDaosProps) => {
const walletDaos = useCachedLoadingWithError(
chainId
? walletDaosSelector({
walletAddress,
chainId,
})
: allWalletDaosSelector({
walletAddress,
})
)

return <StatelessWalletDaos LazyDaoCard={LazyDaoCard} daos={walletDaos} />
}
1 change: 1 addition & 0 deletions packages/stateful/components/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './ConnectedWalletDisplay'
export * from './DisconnectWallet'
export * from './WalletDaos'
export * from './WalletModals'
export * from './WalletUi'
79 changes: 79 additions & 0 deletions packages/stateful/recoil/selectors/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ import {
refreshWalletBalancesIdAtom,
} from '@dao-dao/state/recoil'
import {
LazyDaoCardProps,
LazyNftCardInfo,
MeTransactionSave,
TokenCardInfo,
TokenType,
WithChainId,
} from '@dao-dao/types'
import { Config } from '@dao-dao/types/contracts/DaoCore.v2'
import {
HIDDEN_BALANCE_PREFIX,
INACTIVE_DAO_NAMES,
KVPK_API_BASE,
ME_SAVED_TX_PREFIX,
convertMicroDenomToDenomWithDecimals,
getFallbackImage,
getNativeTokenForChainId,
getSupportedChains,
transformBech32Address,
Expand Down Expand Up @@ -325,3 +329,78 @@ export const allWalletNftsSelector = selectorFamily<
return [...nativeNfts, ...nativeStakedNfts]
},
})

// Get DAOs this wallet is a member of.
export const walletDaosSelector = selectorFamily<
LazyDaoCardProps[],
// Can be any wallet address.
WithChainId<{ walletAddress: string }>
>({
key: 'walletDaos',
get:
({ chainId, walletAddress }) =>
({ get }) => {
const daos: {
dao: string
config: Config
proposalCount: number
}[] = get(
queryWalletIndexerSelector({
chainId,
walletAddress,
formula: 'daos/memberOf',
})
)
if (!daos || !Array.isArray(daos)) {
return []
}

const lazyDaoCards = daos
.map(
({ dao, config, proposalCount }): LazyDaoCardProps => ({
chainId,
coreAddress: dao,
name: config.name,
description: config.description,
imageUrl: config.image_url || getFallbackImage(dao),
isInactive:
INACTIVE_DAO_NAMES.includes(config.name) || proposalCount === 0,
})
)
.sort((a, b) => a.name.localeCompare(b.name))

return lazyDaoCards
},
})

// Get DAOs across all DAO DAO-supported chains.
export const allWalletDaosSelector = selectorFamily<
LazyDaoCardProps[],
// Can be any wallet address.
{ walletAddress: string }
>({
key: 'allWalletDaos',
get:
({ walletAddress }) =>
({ get }) => {
const chains = getSupportedChains()

const allLazyDaoCards = get(
waitForAll(
chains.map(({ chain }) =>
walletDaosSelector({
chainId: chain.chain_id,
walletAddress: transformBech32Address(
walletAddress,
chain.chain_id
),
})
)
)
)
.flat()
.sort((a, b) => a.name.localeCompare(b.name))

return allLazyDaoCards
},
})
4 changes: 4 additions & 0 deletions packages/stateless/components/Collapsible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { CollapsibleProps } from '@dao-dao/types/stateless/Collapsible'
import { toAccessibleImageUrl } from '@dao-dao/utils'

import { DropdownIconButton } from './icon_buttons/DropdownIconButton'
import { TooltipInfoIcon } from './tooltip'

const titleClassName =
'flex grow flex-row items-center gap-2 overflow-hidden py-2 transition-opacity hover:opacity-70 active:opacity-60 cursor-pointer'

export const Collapsible = ({
label,
tooltip,
imageUrl,
link,
defaultCollapsed = false,
Expand Down Expand Up @@ -41,6 +43,8 @@ export const Collapsible = ({
)}

<p className="link-text truncate text-text-body">{label}</p>

{tooltip && <TooltipInfoIcon size="sm" title={tooltip} />}
</>
)

Expand Down
Loading

1 comment on commit 42c2d56

@vercel
Copy link

@vercel vercel bot commented on 42c2d56 Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.