From 623674aa9d793d79f4afb8b3406db7de02b5b3c9 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Sun, 17 Apr 2022 19:43:28 -0700 Subject: [PATCH 01/16] Only show abstain clarification once people vote. --- apps/dapp/components/ProposalDetailsSidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dapp/components/ProposalDetailsSidebar.tsx b/apps/dapp/components/ProposalDetailsSidebar.tsx index 1272b3c63..b0a29d9d7 100644 --- a/apps/dapp/components/ProposalDetailsSidebar.tsx +++ b/apps/dapp/components/ProposalDetailsSidebar.tsx @@ -661,7 +661,7 @@ export const ProposalDetailsVoteStatus = ({ )} - {abstainVotes === turnoutTotal && ( + {turnoutTotal > 0 && abstainVotes === turnoutTotal && (

All abstain clarification

From 2bed03c393bbfd5ace7cd8accfb75a3e065a3783 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Tue, 22 Mar 2022 13:38:06 -0700 Subject: [PATCH 02/16] Split staking modal into stateless and statefull components. Display executed proposal TX hashes with link to Mintscan (#441) * Display executed proposal TX hash in sidebar. * Added link to executed proposal TX on mintscan. * Improved user messages about tx hash. * Made TX hash loadable in case the query takes some time. * Only add link to proposal tx hash if NEXT_PUBLIC_CHAIN_TXN_URL_PREFIX present. * Formatted. * Generalized copy to clipboard message. Improve proposal template UX (#440) * Preview proposal message JSON when creating. Renamed messages to actions on proposal create page. * Recognize and display message templates on existing proposals. * Fixed spend cosmos msg detection. * Formatted. * PR fixes. * Fixed prettier lint error. Add utils package newline at the end of tsconfig.json remove debugging console.log Update packages/utils/package.json Co-authored-by: Noah Saso Show second by second countdown when unstaking. (#456) Icons package (#428) * Init 'icons' package. * Add init assets to 'icons' package. * Use Wallet from @dao-dao/icons. Added "Add Token" Button (#458) * Added "Add Token" Button * Use "addTokenCallback" in "StarButton" * Moved "Add Token" inside "DaoTreasury" Fix formatting issue in Button.tsx. (#461) Disable check for staked balance at proposal creation time. (#464) Currently there is an issue with querying past chain state from before the chain halted. This makes voting on proposals created before the chain halt not possible as that check requires those queries to work. This removes that check for now. Next week, once those proposals have cleared we can re enable it. Allow voting abstain on proposals. (#454) Migrate away from Daisy UI and bring design closer to spec. (#457) * WIP - migrate away from Daisy UI. * Use text sizes from spec in pages covered so far. Have mostly completed: - /starred - DAO homepage - staking modal - nav bar * Proposal view working and new proposal template selection modal. * WIP - proposal voting page. * Update ProposalDetails and allow voting. * Add proposal execute button. * Add loading state to button. * Update multisig page visuals. * Fix build errors. * Updated DAO create page. * DAO create page visual improvements. * Update Multisig create page. * Remove unused component files. * Checkpoint don't commit this Zeke. * Added weeks and days to seconds formatter. * Add Token image field and update most templates. * Prompt to stake if no voting power for voting. * Accent DAO / Multisig page with average color of logo (#460) * Use average color from DAO's logo as accent color. * Added colorization to multisig page. * Ran next formatter. * Fixed yarn lock URLs. * Failsafe and load page if client fails to connect when averaging color. * Address feedback from Sager. * Add label to DAO / Multisig name field (#470) * WIP - migrate away from Daisy UI. * Use text sizes from spec in pages covered so far. Have mostly completed: - /starred - DAO homepage - staking modal - nav bar * Proposal view working and new proposal template selection modal. * WIP - proposal voting page. * Update ProposalDetails and allow voting. * Add proposal execute button. * Add loading state to button. * Update multisig page visuals. * Fix build errors. * Updated DAO create page. * DAO create page visual improvements. * Update Multisig create page. * Remove unused component files. * Checkpoint don't commit this Zeke. * Added weeks and days to seconds formatter. * Add Token image field and update most templates. * Prompt to stake if no voting power for voting. * Address feedback from Sager. * Accent DAO / Multisig page with average color of logo (#460) * Use average color from DAO's logo as accent color. * Added colorization to multisig page. * Ran next formatter. * Fixed yarn lock URLs. * Failsafe and load page if client fails to connect when averaging color. * Add label to DAO / Multisig name field (#470) * Misc fixes. * Swap to a tooltip library that actually works. * Marketing token image & ensure sufficent contrast in accent color. * Prompt to configure keplr if installed but not configured. * Don't perform accent color check in useEffect. Pls vercel. * Final fixups from testing. * Feedback from elgorithm. * Correctly center background token images. Co-authored-by: Noah Saso Co-authored-by: Jake Hartnell Persist description newlines on ContractView (#476) Reuse @cosmjs clients (#480) * ChainClientRouter and its integration * upd comment * pr feedback Make connect wallet and wallet display button same sizes. (#482) feat(ui): update reach tooltip styles (#485) Fix treasury typo (#488) * Fix treasury typo * lint Update cosmJS dependency to reduce initial JS load size. (#479) Resolves #19. Better display when user did not vote on proposal. (#478) Swap positions of submit and preview in proposal form. (#477) * Merge development into main! (#473) * Add utils package * newline at the end of tsconfig.json * remove debugging console.log * Crispier toasts (#442) * Increased duration toasts display on screen. * Display notifications above suspense loader. * Fixed vscode eslint fixing incorrect rules, and reformatted. * Display executed proposal TX hashes with link to Mintscan (#441) * Display executed proposal TX hash in sidebar. * Added link to executed proposal TX on mintscan. * Improved user messages about tx hash. * Made TX hash loadable in case the query takes some time. * Only add link to proposal tx hash if NEXT_PUBLIC_CHAIN_TXN_URL_PREFIX present. * Formatted. * Generalized copy to clipboard message. * Improve proposal template UX (#440) * Preview proposal message JSON when creating. Renamed messages to actions on proposal create page. * Recognize and display message templates on existing proposals. * Fixed spend cosmos msg detection. * Formatted. * PR fixes. * Fixed prettier lint error. * Update packages/utils/package.json Co-authored-by: Noah Saso * Add check for connected wallet (#452) * Show second by second countdown when unstaking. (#456) * Icons package (#428) * Init 'icons' package. * Add init assets to 'icons' package. * Use Wallet from @dao-dao/icons. * Added "Add Token" Button (#458) * Added "Add Token" Button * Use "addTokenCallback" in "StarButton" * Moved "Add Token" inside "DaoTreasury" * Fix formatting issue in Button.tsx. (#461) * Disable check for staked balance at proposal creation time. (#464) Currently there is an issue with querying past chain state from before the chain halted. This makes voting on proposals created before the chain halt not possible as that check requires those queries to work. This removes that check for now. Next week, once those proposals have cleared we can re enable it. * Allow voting abstain on proposals. (#454) * Use version number from package.json. (#455) * Migrate away from Daisy UI and bring design closer to spec. (#457) * WIP - migrate away from Daisy UI. * Use text sizes from spec in pages covered so far. Have mostly completed: - /starred - DAO homepage - staking modal - nav bar * Proposal view working and new proposal template selection modal. * WIP - proposal voting page. * Update ProposalDetails and allow voting. * Add proposal execute button. * Add loading state to button. * Update multisig page visuals. * Fix build errors. * Updated DAO create page. * DAO create page visual improvements. * Update Multisig create page. * Remove unused component files. * Checkpoint don't commit this Zeke. * Added weeks and days to seconds formatter. * Add Token image field and update most templates. * Prompt to stake if no voting power for voting. * Accent DAO / Multisig page with average color of logo (#460) * Use average color from DAO's logo as accent color. * Added colorization to multisig page. * Ran next formatter. * Fixed yarn lock URLs. * Failsafe and load page if client fails to connect when averaging color. * Address feedback from Sager. * Add label to DAO / Multisig name field (#470) * WIP - migrate away from Daisy UI. * Use text sizes from spec in pages covered so far. Have mostly completed: - /starred - DAO homepage - staking modal - nav bar * Proposal view working and new proposal template selection modal. * WIP - proposal voting page. * Update ProposalDetails and allow voting. * Add proposal execute button. * Add loading state to button. * Update multisig page visuals. * Fix build errors. * Updated DAO create page. * DAO create page visual improvements. * Update Multisig create page. * Remove unused component files. * Checkpoint don't commit this Zeke. * Added weeks and days to seconds formatter. * Add Token image field and update most templates. * Prompt to stake if no voting power for voting. * Address feedback from Sager. * Accent DAO / Multisig page with average color of logo (#460) * Use average color from DAO's logo as accent color. * Added colorization to multisig page. * Ran next formatter. * Fixed yarn lock URLs. * Failsafe and load page if client fails to connect when averaging color. * Add label to DAO / Multisig name field (#470) * Misc fixes. * Swap to a tooltip library that actually works. * Marketing token image & ensure sufficent contrast in accent color. * Prompt to configure keplr if installed but not configured. * Don't perform accent color check in useEffect. Pls vercel. * Final fixups from testing. * Feedback from elgorithm. * Correctly center background token images. Co-authored-by: Noah Saso Co-authored-by: Jake Hartnell Co-authored-by: Sagar Saija Co-authored-by: Noah Saso Co-authored-by: Jake Hartnell Co-authored-by: Joshua Van Deren Co-authored-by: ebaker <430383+ebaker@users.noreply.github.com> * Add missing daotoken image. (#474) * Swap positions of submit and preview in proposal form. Resolves #475 * fixup! correctly line wrap. Co-authored-by: Sagar Saija Co-authored-by: Noah Saso Co-authored-by: Jake Hartnell Co-authored-by: Joshua Van Deren Co-authored-by: ebaker <430383+ebaker@users.noreply.github.com> Better empty state for new users (#449) * Empty state contract cards * Hover animation * Moved to components/ * Renamed Mystery to Empty cards feat: improve eslint and prettier integration (#487) New proposal status UI (#489) chore: format and lint all sources (#493) Redeploy Vercel. Create proposal details card & make proposal details page responsive (#494) * Turned proposal details into card, and created details card and vote status components. * Made proposal details page responsive! feat(dapp): add create dao field tooltips (#486) Minor hindsight proposal status UI tweaks (#498) * Increased height of vertical bars on progress bars, added message when proposal is passing, cleaned up unnecessary classes, and removed unused label prop from vertical bars on progress bars. * Improved passing threshold indicator language to be less prescriptive. * Added helpful status text for threshold and quorum cases for clarification purpses. Only show time left when proposal is open. Split StakingModal into stateless and stateful components. --- apps/dapp/components/BetaWarning.tsx | 9 +- apps/dapp/components/ChainEnableModal.tsx | 5 +- apps/dapp/components/Claims.tsx | 8 +- apps/dapp/components/CodeIdSelect.tsx | 3 +- apps/dapp/components/ConnectWalletButton.tsx | 23 +- apps/dapp/components/ContractCard.tsx | 31 +- apps/dapp/components/ContractView.tsx | 21 +- apps/dapp/components/CopyToClipboard.tsx | 4 +- apps/dapp/components/CosmosMessageDisplay.tsx | 2 +- apps/dapp/components/DaoContractInfo.tsx | 12 +- apps/dapp/components/DaoTreasury.tsx | 3 +- apps/dapp/components/EmptyContractCard.tsx | 2 +- apps/dapp/components/Execute.tsx | 6 +- apps/dapp/components/FormCard.tsx | 2 +- apps/dapp/components/GradientWrapper.tsx | 2 +- apps/dapp/components/HomepageLayout.tsx | 2 +- apps/dapp/components/InstallKeplr.tsx | 6 +- apps/dapp/components/Layout.tsx | 2 +- apps/dapp/components/MultisigContractInfo.tsx | 4 +- apps/dapp/components/Nav.tsx | 2 +- apps/dapp/components/NoKeplrAccountModal.tsx | 6 +- apps/dapp/components/Paginator.tsx | 2 +- apps/dapp/components/ProposalDetails.tsx | 6 +- .../components/ProposalDetailsSidebar.tsx | 49 +- apps/dapp/components/ProposalForm.tsx | 5 +- apps/dapp/components/ProposalList.tsx | 13 +- .../ProposalStatus/ProposalStatus.tsx | 2 +- apps/dapp/components/SidebarLayout.tsx | 2 +- apps/dapp/components/StakingModal.tsx | 451 ++++-------------- apps/dapp/components/TemplateSelector.tsx | 7 +- apps/dapp/components/ThemeToggle.tsx | 2 +- apps/dapp/components/Transfers.tsx | 4 +- apps/dapp/components/Vote.tsx | 2 +- apps/dapp/components/index.ts | 3 - .../dapp/components/input/CodeMirrorInput.tsx | 2 +- apps/dapp/components/input/ImageSelector.tsx | 3 +- apps/dapp/next.config.js | 2 +- apps/dapp/package.json | 4 +- apps/dapp/pages/_app.tsx | 4 +- apps/dapp/pages/_document.tsx | 2 +- .../pages/dao/[contractAddress]/index.tsx | 8 +- apps/dapp/pages/dao/create.tsx | 8 +- apps/dapp/pages/dao/list.tsx | 9 +- apps/dapp/pages/index.tsx | 4 +- .../multisig/[contractAddress]/index.tsx | 2 +- apps/dapp/pages/multisig/create.tsx | 6 +- apps/dapp/pages/multisig/list.tsx | 4 +- apps/dapp/pages/starred.tsx | 6 +- apps/dapp/selectors/cosm.ts | 5 +- apps/dapp/selectors/daos.ts | 2 +- apps/dapp/selectors/multisigs.ts | 2 +- apps/dapp/selectors/proposals.ts | 19 + apps/dapp/services/keplr.tsx | 2 +- apps/dapp/tailwind.config.js | 2 +- apps/dapp/templates/changeMembers.tsx | 2 +- apps/dapp/templates/configUpdate.tsx | 2 +- apps/dapp/templates/mint.tsx | 2 +- apps/dapp/templates/removeToken.tsx | 2 +- apps/dapp/templates/spend.tsx | 5 +- apps/dapp/templates/stake.tsx | 5 +- apps/dapp/util/messagehelpers.ts | 8 +- apps/dapp/util/proposal.ts | 2 +- .../Button/Button.stories.tsx | 0 .../ui/{ => components}/Button/Button.tsx | 0 packages/ui/{ => components}/Button/index.ts | 0 .../dapp => packages/ui}/components/Modal.tsx | 0 .../components/StakingModal/ActionButton.tsx | 24 + .../StakingModal/AmountSelector.tsx | 44 ++ .../ui/components/StakingModal/ModeButton.tsx | 24 + .../StakingModal/PercentSelector.tsx | 43 ++ .../components/StakingModal/StakingModal.tsx | 249 ++++++++++ packages/ui/components/StakingModal/index.tsx | 1 + packages/ui/components/Tooltip.tsx | 15 + packages/ui/index.tsx | 6 +- packages/ui/package.json | 7 +- packages/ui/tsconfig.json | 2 +- .../dapp/util => packages/utils}/constants.ts | 0 .../util => packages/utils}/conversion.ts | 99 +--- packages/utils/duration.ts | 19 + packages/utils/ibc.ts | 34 ++ .../util => packages/utils}/ibc_assets.json | 0 packages/utils/index.tsx | 5 + packages/utils/time.ts | 28 ++ yarn.lock | 5 + 84 files changed, 794 insertions(+), 638 deletions(-) delete mode 100644 apps/dapp/components/index.ts rename packages/ui/{ => components}/Button/Button.stories.tsx (100%) rename packages/ui/{ => components}/Button/Button.tsx (100%) rename packages/ui/{ => components}/Button/index.ts (100%) rename {apps/dapp => packages/ui}/components/Modal.tsx (100%) create mode 100644 packages/ui/components/StakingModal/ActionButton.tsx create mode 100644 packages/ui/components/StakingModal/AmountSelector.tsx create mode 100644 packages/ui/components/StakingModal/ModeButton.tsx create mode 100644 packages/ui/components/StakingModal/PercentSelector.tsx create mode 100644 packages/ui/components/StakingModal/StakingModal.tsx create mode 100644 packages/ui/components/StakingModal/index.tsx create mode 100644 packages/ui/components/Tooltip.tsx rename {apps/dapp/util => packages/utils}/constants.ts (100%) rename {apps/dapp/util => packages/utils}/conversion.ts (58%) create mode 100644 packages/utils/duration.ts create mode 100644 packages/utils/ibc.ts rename {apps/dapp/util => packages/utils}/ibc_assets.json (100%) create mode 100644 packages/utils/time.ts diff --git a/apps/dapp/components/BetaWarning.tsx b/apps/dapp/components/BetaWarning.tsx index fa7eba027..0079f9e92 100644 --- a/apps/dapp/components/BetaWarning.tsx +++ b/apps/dapp/components/BetaWarning.tsx @@ -1,7 +1,6 @@ +import { Button } from '@dao-dao/ui' import { ChevronRightIcon, XIcon } from '@heroicons/react/outline' -import { Button } from '@components' - import SvgMessage from 'components/icons/Message' export function BetaWarningModal({ onAccept }: { onAccept: Function }) { @@ -48,14 +47,14 @@ export function BetaWarningModal({ onAccept }: { onAccept: Function }) { export function BetaNotice({ onClose }: { onClose: Function }) { return ( -
+

DAO DAO is in
beta!

)} {partyMode && ( -
+
- ) -} - -function AmountSelector({ - onIncrease, - onDecrease, - onChange, - amount, - max, -}: { - onIncrease: MouseEventHandler - onDecrease: MouseEventHandler - onChange: ChangeEventHandler - amount: string - max: number -}) { - return ( -
- - - -
- ) -} - -function PercentSelector({ - max, - amount, - setAmount, -}: { - max: number - amount: number - setAmount: Dispatch> -}) { - const active = (p: number) => max * p == amount - const getClassName = (p: number) => - 'rounded-md transition hover:bg-secondary link-text font-normal px-2 py-1' + - (active(p) ? ' bg-secondary border border-inactive' : '') - const getOnClick = (p: number) => () => { - setAmount( - (p * max) - // Need to specify 'en' here or otherwise different langauges - // (ex german) will insert '.' as seperators which will mess - // with our replace logic :) - .toLocaleString('en', { maximumFractionDigits: 6 }) - .replaceAll(',', '') - ) - } - - return ( -
- - - - - -
- ) -} - -function durationIsNonZero(d: Duration) { - if ('height' in d) { - return d.height !== 0 - } - return d.time !== 0 -} +} from '@dao-dao/utils' function executeUnstakeAction( - denomAmount: string, + denomAmount: number, tokenInfo: TokenInfoResponse, stakingAddress: string, signingClient: SigningCosmWasmClient | null, @@ -219,7 +68,7 @@ function executeUnstakeAction( } function executeStakeAction( - denomAmount: string, + denomAmount: number, tokenAddress: string, tokenInfo: TokenInfoResponse, stakingAddress: string, @@ -295,20 +144,20 @@ function executeClaimAction( export function StakingModal({ defaultMode, contractAddress, - claimAmount, + claimableTokens, onClose, beforeExecute, afterExecute, }: { defaultMode: StakingMode contractAddress: string - claimAmount: number - onClose: MouseEventHandler + claimableTokens: number + onClose: () => void beforeExecute: Function afterExecute: Function }) { const [mode, setMode] = useState(defaultMode) - const [amount, setAmount] = useState('0') + const [amount, setAmount] = useState(0) const walletAddress = useRecoilValue(walletAddressSelector) const signingClient = useRecoilValue(cosmWasmSigningClient) @@ -320,215 +169,105 @@ export function StakingModal({ useRecoilValue(walletTokenBalance(daoInfo?.gov_token)).amount, tokenInfo.decimals ) + const tokenBalanceLoading = useRecoilValue( + walletTokenBalanceLoading(walletAddress) + ) const stakedGovTokenBalance = convertMicroDenomToDenomWithDecimals( useRecoilValue(walletStakedTokenBalance(daoInfo?.staking_contract)).amount, tokenInfo.decimals ) - const unstakeDuration = useRecoilValue( - unstakingDuration(daoInfo.staking_contract) + const unstakingDuration = useRecoilValue( + unstakingDurationSelector(daoInfo.staking_contract) ) const setWalletTokenBalanceUpdateCount = useSetRecoilState( walletTokenBalanceUpdateCountAtom(walletAddress) ) - const maxTx = Number( - mode === StakingMode.Stake ? govTokenBalance : stakedGovTokenBalance - ) - const canClaim = claimAmount !== 0 - - const invalidAmount = (): string => { - if (amount === '') { - return 'Unspecified amount' - } - if (Number(amount) > maxTx || Number(amount) <= 0) { - return `Invalid amount` - } - return '' - } - const walletDisconnected = (): string => { + const walletDisconnected = (): string | undefined => { if (!signingClient || !walletAddress) { return 'Please connect your wallet' } - return '' + return undefined } - const error = invalidAmount() || walletDisconnected() - const ready = !error + const error = walletDisconnected() - const ActionButton = ({ ready }: { ready: boolean }) => { - return ( - - ) + ) + break + case StakingMode.Unstake: + executeUnstakeAction( + amount, + tokenInfo, + daoInfo.staking_contract, + signingClient, + walletAddress, + setLoading, + () => { + setAmount(0) + // New staking balances will not appear until the next block has been added. + setTimeout(() => { + setWalletTokenBalanceUpdateCount((p) => p + 1) + afterExecute() + }, 6500) + } + ) + break + case StakingMode.Claim: + executeClaimAction( + convertMicroDenomToDenomWithDecimals(amount, tokenInfo.decimals), + daoInfo.staking_contract, + signingClient, + walletAddress, + setLoading, + () => { + setTimeout(() => { + setWalletTokenBalanceUpdateCount((p) => p + 1) + afterExecute() + }, 6500) + } + ) + break + default: + toast.error('Internal error while staking. Unrecognized mode.') + } } return ( - -
- - -
-

Manage staking

-
-
- setMode(StakingMode.Stake)} - > - Stake - - setMode(StakingMode.Unstake)} - > - Unstake - - {canClaim && ( - setMode(StakingMode.Claim)} - > - Claim - - )} -
- {mode !== StakingMode.Claim && ( - <> -
-

Choose your token amount

- setAmount(e?.target?.value)} - onDecrease={() => setAmount((a) => (Number(a) - 1).toString())} - onIncrease={() => setAmount((a) => (Number(a) + 1).toString())} - /> - {Number(amount) > maxTx && ( - - Can{"'"}t {stakingModeString(mode)} more ${tokenInfo.symbol}{' '} - than you own - - )} - - Max available {maxTx} ${tokenInfo.symbol} - -
- -
-
- {mode === StakingMode.Unstake && - durationIsNonZero(unstakeDuration) && ( - <> -
-
-

- Unstaking period: {humanReadableDuration(unstakeDuration)} -

-

- There will be {humanReadableDuration(unstakeDuration)}{' '} - between the time you decide to unstake your tokens and the - time you can redeem them. -

-
- - )} -
- -
- - )} - {mode === StakingMode.Claim && ( - <> -
-

- {convertMicroDenomToDenomWithDecimals( - claimAmount, - tokenInfo.decimals - )}{' '} - ${tokenInfo.symbol} avaliable -

-

- Claim them to increase your voting power. -

-
- - - -
-
- - )} -
-
+ setAmount(newAmount)} + onClose={onClose} + claimableTokens={claimableTokens} + stakableTokens={govTokenBalance} + unstakableTokens={stakedGovTokenBalance} + unstakingDuration={unstakingDuration} + tokenSymbol={tokenInfo.symbol} + tokenDecimals={tokenInfo.decimals} + loading={loading || tokenBalanceLoading} + error={error} + onAction={onAction} + /> ) } diff --git a/apps/dapp/components/TemplateSelector.tsx b/apps/dapp/components/TemplateSelector.tsx index acfb00e6d..4b72b4189 100644 --- a/apps/dapp/components/TemplateSelector.tsx +++ b/apps/dapp/components/TemplateSelector.tsx @@ -1,10 +1,9 @@ +import { Modal } from '@dao-dao/ui' import { XIcon } from '@heroicons/react/outline' import { ContractSupport, MessageTemplate } from 'templates/templateList' import { Config } from 'util/contractConfigWrapper' -import { Modal } from './Modal' - export function MessageTemplateDisplayItem({ template, onClick, @@ -21,7 +20,7 @@ export function MessageTemplateDisplayItem({ return ( + +) diff --git a/packages/ui/components/StakingModal/AmountSelector.tsx b/packages/ui/components/StakingModal/AmountSelector.tsx new file mode 100644 index 000000000..dedbad9b0 --- /dev/null +++ b/packages/ui/components/StakingModal/AmountSelector.tsx @@ -0,0 +1,44 @@ +import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/outline' + +import { ChangeEvent, FC } from 'react' + +export interface AmountSelectorProps { + setAmount: (newValue: number) => void + amount: number + max: number +} + +export const AmountSelector: FC = ({ + setAmount, + amount, + max, +}) => ( +
+ + ) => + setAmount(e.target.valueAsNumber) + } + type="number" + value={amount} + /> + +
+) diff --git a/packages/ui/components/StakingModal/ModeButton.tsx b/packages/ui/components/StakingModal/ModeButton.tsx new file mode 100644 index 000000000..fa7b592fe --- /dev/null +++ b/packages/ui/components/StakingModal/ModeButton.tsx @@ -0,0 +1,24 @@ +import { FC, ReactNode } from 'react' + +export interface ModeButtonProps { + onClick: () => void + active: boolean + children: ReactNode +} + +export const ModeButton: FC = ({ + onClick, + active, + children, +}) => ( + +) diff --git a/packages/ui/components/StakingModal/PercentSelector.tsx b/packages/ui/components/StakingModal/PercentSelector.tsx new file mode 100644 index 000000000..a775d0583 --- /dev/null +++ b/packages/ui/components/StakingModal/PercentSelector.tsx @@ -0,0 +1,43 @@ +import { FC } from 'react' + +export interface PercentSelectorProps { + max: number + amount: number + tokenDecimals: number + setAmount: (newAmount: number) => void +} + +export const PercentSelector: FC = ({ + max, + amount, + setAmount, + tokenDecimals, +}) => { + const active = (p: number) => max * p === amount + const getClassName = (p: number) => + 'rounded-md transition hover:bg-secondary link-text font-normal px-2 py-1' + + (active(p) ? ' bg-secondary border border-inactive' : '') + const getOnClick = (p: number) => () => { + setAmount(Number((p * max).toFixed(tokenDecimals))) + } + + return ( +
+ + + + + +
+ ) +} diff --git a/packages/ui/components/StakingModal/StakingModal.tsx b/packages/ui/components/StakingModal/StakingModal.tsx new file mode 100644 index 000000000..a8dd7b9f3 --- /dev/null +++ b/packages/ui/components/StakingModal/StakingModal.tsx @@ -0,0 +1,249 @@ +import { FC, useState } from 'react' +import { Modal } from '../Modal' +import { ModeButton } from './ModeButton' +import { AmountSelector } from './AmountSelector' +import { PercentSelector } from './PercentSelector' +import { ActionButton } from './ActionButton' +import { Duration } from '@dao-dao/types/contracts/cw3-dao' +import { + durationIsNonZero, + humanReadableDuration, + convertMicroDenomToDenomWithDecimals, +} from '@dao-dao/utils' + +import { XIcon } from '@heroicons/react/outline' + +export enum StakingMode { + Stake, + Unstake, + Claim, +} + +export const stakingModeString = (mode: StakingMode) => { + switch (mode) { + case StakingMode.Stake: + return 'stake' + case StakingMode.Unstake: + return 'unstake' + case StakingMode.Claim: + return 'claim' + default: + return 'internal error' + } +} + +export interface StakingModalProps { + // The mode to start the staking modal in. + defaultMode: StakingMode + // The number of tokens in question. + amount: number + // Sets the number of tokens in question. + setAmount: (newAmount: number) => void + // Called when the staking modal is closed. + onClose: () => void + // The number of tokens that are currently claimable. + claimableTokens: number + // The number of tokens that are unstakable. + unstakableTokens: number + // The number of tokens that are stakable. + stakableTokens: number + // The duration for unstaking. + unstakingDuration: Duration + // Symbol for the token that is being staked. + tokenSymbol: string + // Decimals for the token that is being staked. + tokenDecimals: number + // Is there an error? + error: string | undefined + // Are we ready to stake? Ex: is wallet connected? + loading: boolean + // Triggered when the stake / unstake / claim button is pressed. + onAction: (mode: StakingMode, amount: number) => void +} + +export const StakingModal: FC = ({ + defaultMode, + amount, + setAmount, + onClose, + claimableTokens, + stakableTokens, + unstakableTokens, + unstakingDuration, + tokenSymbol, + tokenDecimals, + loading, + error, + onAction, +}) => { + const [mode, setMode] = useState(defaultMode) + const canClaim = !!claimableTokens + + const maxTx = mode === StakingMode.Stake ? stakableTokens : unstakableTokens + + const invalidAmount = (): string | undefined => { + if (amount <= 0) { + return `Can't ${stakingModeString(mode)} zero tokens.` + } + if (amount > maxTx) { + return "Can't ${stakingModeString(mode)} more tokens than you own." + } + return undefined + } + error = error || invalidAmount() + + return ( + +
+ + +
+

Manage staking

+
+ +
+ setMode(StakingMode.Stake)} + > + Stake + + setMode(StakingMode.Unstake)} + > + Unstake + + {canClaim && ( + setMode(StakingMode.Claim)} + > + Claim + + )} +
+ {mode === StakingMode.Stake && ( + setAmount(amount)} + max={stakableTokens} + tokenDecimals={tokenDecimals} + /> + )} + {mode === StakingMode.Unstake && ( + setAmount(amount)} + max={unstakableTokens} + unstakingDuration={unstakingDuration} + tokenDecimals={tokenDecimals} + /> + )} + {mode === StakingMode.Claim && canClaim && ( + + )} +
+ onAction(mode, amount)} + /> +
+
+
+ ) +} + +interface AmountSelectionProps { + amount: number + max: number + setAmount: (newAmount: number) => void + tokenDecimals: number +} + +const AmountSelectionBody: FC = ({ + amount, + setAmount, + max, + tokenDecimals, +}) => ( +
+

Choose your token amount

+ + {amount > max && ( + + Can{"'"}t stake more tokens than you own. + + )} + Max available {max} +
+ +
+
+) + +const UnstakingModeBody: FC< + AmountSelectionProps & { unstakingDuration: Duration } +> = ({ amount, setAmount, max, unstakingDuration, tokenDecimals }) => ( + <> + + {durationIsNonZero(unstakingDuration) && ( + <> +
+
+

+ Unstaking period: {humanReadableDuration(unstakingDuration)} +

+

+ There will be {humanReadableDuration(unstakingDuration)} between the + time you decide to unstake your tokens and the time you can redeem + them. +

+
+ + )} + +) + +interface ClaimBodyProps { + amount: number + tokenDecimals: number + tokenSymbol: string +} + +const ClaimBody: FC = ({ + amount, + tokenSymbol, + tokenDecimals, +}) => ( +
+

+ {convertMicroDenomToDenomWithDecimals(amount, tokenDecimals)} $ + {tokenSymbol} avaliable +

+

+ Claim them to increase your voting power. +

+
+) diff --git a/packages/ui/components/StakingModal/index.tsx b/packages/ui/components/StakingModal/index.tsx new file mode 100644 index 000000000..92f4237c9 --- /dev/null +++ b/packages/ui/components/StakingModal/index.tsx @@ -0,0 +1 @@ +export { StakingMode, StakingModal } from './StakingModal' diff --git a/packages/ui/components/Tooltip.tsx b/packages/ui/components/Tooltip.tsx new file mode 100644 index 000000000..071fee347 --- /dev/null +++ b/packages/ui/components/Tooltip.tsx @@ -0,0 +1,15 @@ +import { FC, ReactNode } from 'react' + +import ReachTooltip from '@reach/tooltip' + +export interface TooltipProps { + label: string | undefined + children: ReactNode +} + +export const Tooltip: FC = ({ label, children }) => + !!label ? ( + {children} + ) : ( + <>{children} + ) diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index bf3c22fc2..3e5573027 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -1,3 +1,5 @@ -import * as React from 'react' -export * from './Button' +export * from './components/Button' export * from './theme' +export * from './components/StakingModal' +export * from './components/Modal' +export * from './components/Tooltip' diff --git a/packages/ui/package.json b/packages/ui/package.json index d1028c79e..47371d060 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,5 +1,5 @@ { - "name": "ui", + "name": "@dao-dao/ui", "version": "0.0.0", "main": "./index.tsx", "types": "./index.tsx", @@ -13,6 +13,11 @@ "storybook": "start-storybook --ci -p 6006 --no-manager-cache", "build-storybook": "build-storybook" }, + "dependencies": { + "@dao-dao/utils": "*", + "@heroicons/react": "^1.0.5", + "@dao-dao/types": "^0.0.8" + }, "devDependencies": { "@babel/core": "^7.17.5", "@dao-dao/config": "*", diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index cd6c94d6e..945987d41 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "tsconfig/react-library.json", + "extends": "tsconfig/nextjs.json", "include": ["."], "exclude": ["dist", "build", "node_modules"] } diff --git a/apps/dapp/util/constants.ts b/packages/utils/constants.ts similarity index 100% rename from apps/dapp/util/constants.ts rename to packages/utils/constants.ts diff --git a/apps/dapp/util/conversion.ts b/packages/utils/conversion.ts similarity index 58% rename from apps/dapp/util/conversion.ts rename to packages/utils/conversion.ts index 512084f3e..c865af55f 100644 --- a/apps/dapp/util/conversion.ts +++ b/packages/utils/conversion.ts @@ -1,14 +1,10 @@ import { - Duration, Expiration, Threshold as DaoThreshold, ThresholdResponse, } from '@dao-dao/types/contracts/cw3-dao' import { Threshold as SigThreshold } from '@dao-dao/types/contracts/cw3-multisig' -import { NATIVE_DECIMALS, NATIVE_DENOM } from './constants' -import ibcAssets from './ibc_assets.json' - export function convertMicroDenomToDenomWithDecimals( amount: number | string, decimals: number @@ -31,19 +27,6 @@ export function convertDenomToMicroDenomWithDecimals( return isNaN(amount) ? '0' : String(amount) } -export function convertFromMicroDenom(denom: string) { - return denom?.substring(1).toUpperCase() -} - -export function convertToFixedDecimals(amount: number | string): string { - if (typeof amount === 'string') { - amount = Number(amount) - } - if (amount > 0.01) { - return amount.toFixed(2) - } else return String(amount) -} - export function convertDenomToHumanReadableDenom(denom: string): string { if (denom.startsWith('u')) { return denom.substring(1) @@ -58,14 +41,17 @@ export function convertDenomToContractReadableDenom(denom: string): string { return 'u' + denom } -export const zeroVotingCoin = { - amount: '0', - denom: 'ucredits', +export function convertFromMicroDenom(denom: string) { + return denom?.substring(1).toUpperCase() } -export const zeroStakingCoin = { - amount: '0', - denom: process.env.NEXT_PUBLIC_STAKING_DENOM || 'ujuno', +export function convertToFixedDecimals(amount: number | string): string { + if (typeof amount === 'string') { + amount = Number(amount) + } + if (amount > 0.01) { + return amount.toFixed(2) + } else return String(amount) } export const getDaoThresholdAndQuorum = ( @@ -134,73 +120,6 @@ export const thresholdString = ( } } -export function humanReadableDuration(d: Duration) { - if ('height' in d) { - return `${d.height} blocks` - } - if (d.time == 0) { - return '0 seconds' - } - return `${secondsToWdhms(d.time.toString())}` -} - -const secPerDay = 24 * 60 * 60 -export function secondsToWdhms(seconds: string | number, numUnits = 5): string { - const secondsInt = Number(seconds) - const w = Math.floor(secondsInt / (secPerDay * 7)) - const d = Math.floor((secondsInt % (secPerDay * 7)) / secPerDay) - const h = Math.floor((secondsInt % secPerDay) / 3600) - const m = Math.floor((secondsInt % 3600) / 60) - const s = Math.floor(secondsInt % 60) - - const wDisplay = w ? w + (w === 1 ? ' wk' : ' wks') : null - const dDisplay = d ? d + (d === 1 ? ' day' : ' days') : null - const hDisplay = h ? h + (h === 1 ? ' hr' : ' hrs') : null - const mDisplay = m ? m + (m === 1 ? ' min' : ' mins') : null - const sDisplay = s ? s + (s === 1 ? ' sec' : ' secs') : null - - return ( - [wDisplay, dDisplay, hDisplay, mDisplay, sDisplay] - // Ignore empty values. - .filter(Boolean) - // Only keep certain precision of units. - .slice(0, numUnits) - // Separate with commas. - .join(', ') - ) -} - -export function nativeTokenLabel(denom: string): string { - // Search IBC asset strings (junoDenom) if denom is in IBC format. - // Otherwise just check microdenoms. - const asset = denom.startsWith('ibc') - ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) - : ibcAssets.tokens.find(({ denom: d }) => d === denom) - // If no asset, assume it's already a microdenom. - return asset?.symbol || convertFromMicroDenom(denom) -} - -export function nativeTokenLogoURI(denom: string): string | undefined { - if (denom === 'ujuno' || denom == 'ujunox') { - return '/juno-symbol.png' - } - - const asset = denom.startsWith('ibc') - ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) - : ibcAssets.tokens.find(({ denom: d }) => d === denom) - return asset?.logoURI -} - -export function nativeTokenDecimals(denom: string): number | undefined { - if (denom === NATIVE_DENOM) { - return NATIVE_DECIMALS - } - const asset = denom.startsWith('ibc') - ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) - : ibcAssets.tokens.find(({ denom: d }) => d === denom) - return asset?.decimals -} - export const expirationAtTimeToSecondsFromNow = (exp: Expiration) => { if (!('at_time' in exp)) { return undefined diff --git a/packages/utils/duration.ts b/packages/utils/duration.ts new file mode 100644 index 000000000..187d795ac --- /dev/null +++ b/packages/utils/duration.ts @@ -0,0 +1,19 @@ +import { Duration } from '@dao-dao/types/contracts/cw3-dao' +import { secondsToWdhms } from './time' + +export const durationIsNonZero = (d: Duration) => { + if ('height' in d) { + return d.height !== 0 + } + return d.time !== 0 +} + +export const humanReadableDuration = (d: Duration) => { + if ('height' in d) { + return `${d.height} blocks` + } + if (d.time == 0) { + return '0 seconds' + } + return `${secondsToWdhms(d.time.toString())}` +} diff --git a/packages/utils/ibc.ts b/packages/utils/ibc.ts new file mode 100644 index 000000000..d7d52f729 --- /dev/null +++ b/packages/utils/ibc.ts @@ -0,0 +1,34 @@ +import ibcAssets from './ibc_assets.json' +import { convertFromMicroDenom } from './conversion' +import { NATIVE_DENOM, NATIVE_DECIMALS } from './constants' + +export function nativeTokenLabel(denom: string): string { + // Search IBC asset strings (junoDenom) if denom is in IBC format. + // Otherwise just check microdenoms. + const asset = denom.startsWith('ibc') + ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) + : ibcAssets.tokens.find(({ denom: d }) => d === denom) + // If no asset, assume it's already a microdenom. + return asset?.symbol || convertFromMicroDenom(denom) +} + +export function nativeTokenLogoURI(denom: string): string | undefined { + if (denom === 'ujuno' || denom == 'ujunox') { + return '/juno-symbol.png' + } + + const asset = denom.startsWith('ibc') + ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) + : ibcAssets.tokens.find(({ denom: d }) => d === denom) + return asset?.logoURI +} + +export function nativeTokenDecimals(denom: string): number | undefined { + if (denom === NATIVE_DENOM) { + return NATIVE_DECIMALS + } + const asset = denom.startsWith('ibc') + ? ibcAssets.tokens.find(({ junoDenom }) => junoDenom === denom) + : ibcAssets.tokens.find(({ denom: d }) => d === denom) + return asset?.decimals +} diff --git a/apps/dapp/util/ibc_assets.json b/packages/utils/ibc_assets.json similarity index 100% rename from apps/dapp/util/ibc_assets.json rename to packages/utils/ibc_assets.json diff --git a/packages/utils/index.tsx b/packages/utils/index.tsx index cb877561a..76eb73cce 100644 --- a/packages/utils/index.tsx +++ b/packages/utils/index.tsx @@ -1 +1,6 @@ export * from './isValidAddress' +export * from './duration' +export * from './time' +export * from './conversion' +export * from './ibc' +export * from './constants' diff --git a/packages/utils/time.ts b/packages/utils/time.ts new file mode 100644 index 000000000..74f4184a1 --- /dev/null +++ b/packages/utils/time.ts @@ -0,0 +1,28 @@ +const secPerDay = 24 * 60 * 60 +export const secondsToWdhms = ( + seconds: string | number, + numUnits = 5 +): string => { + const secondsInt = Number(seconds) + const w = Math.floor(secondsInt / (secPerDay * 7)) + const d = Math.floor((secondsInt % (secPerDay * 7)) / secPerDay) + const h = Math.floor((secondsInt % secPerDay) / 3600) + const m = Math.floor((secondsInt % 3600) / 60) + const s = Math.floor(secondsInt % 60) + + const wDisplay = w ? w + (w === 1 ? ' wk' : ' wks') : null + const dDisplay = d ? d + (d === 1 ? ' day' : ' days') : null + const hDisplay = h ? h + (h === 1 ? ' hr' : ' hrs') : null + const mDisplay = m ? m + (m === 1 ? ' min' : ' mins') : null + const sDisplay = s ? s + (s === 1 ? ' sec' : ' secs') : null + + return ( + [wDisplay, dDisplay, hDisplay, mDisplay, sDisplay] + // Ignore empty values. + .filter(Boolean) + // Only keep certain precision of units. + .slice(0, numUnits) + // Separate with commas. + .join(', ') + ) +} diff --git a/yarn.lock b/yarn.lock index d1bbdd973..e55aa4794 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3660,6 +3660,11 @@ "@typescript-eslint/types" "5.10.1" "@typescript-eslint/typescript-estree" "5.10.1" debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" "@typescript-eslint/parser@^5.19.0": version "5.19.0" From 18d2b4188504c6d567853151798087e099819660 Mon Sep 17 00:00:00 2001 From: Zeke Medley Date: Fri, 15 Apr 2022 12:55:14 -0700 Subject: [PATCH 03/16] Pre moving templates to package rollback point oh boy. --- apps/dapp/atoms/localStorageEffect.ts | 2 +- apps/dapp/components/BetaWarning.tsx | 6 +- apps/dapp/components/ChainEnableModal.tsx | 2 +- apps/dapp/components/ConnectWalletButton.tsx | 6 +- apps/dapp/components/ContractCard.tsx | 48 ++----- apps/dapp/components/ContractView.tsx | 2 +- apps/dapp/components/CopyToClipboard.tsx | 2 +- apps/dapp/components/DaoContractInfo.tsx | 2 +- apps/dapp/components/EmptyContractCard.tsx | 2 +- apps/dapp/components/Execute.tsx | 4 +- apps/dapp/components/FormCard.tsx | 2 +- apps/dapp/components/InstallKeplr.tsx | 2 +- apps/dapp/components/MarkdownPreview.tsx | 9 -- apps/dapp/components/MultisigContractInfo.tsx | 2 +- apps/dapp/components/NoKeplrAccountModal.tsx | 2 +- apps/dapp/components/ProposalDetails.tsx | 12 +- .../components/ProposalDetailsSidebar.tsx | 18 +-- apps/dapp/components/ProposalForm.tsx | 5 +- apps/dapp/components/ProposalList.tsx | 2 +- apps/dapp/components/StakingModal.tsx | 28 ++-- apps/dapp/components/TemplateSelector.tsx | 4 +- apps/dapp/components/Transfers.tsx | 4 +- apps/dapp/components/Vote.tsx | 2 +- .../dapp/components/input/CodeMirrorInput.tsx | 2 +- apps/dapp/components/input/ImageSelector.tsx | 2 +- apps/dapp/components/input/TextAreaInput.tsx | 1 - apps/dapp/models/proposal/proposal.ts | 12 -- .../pages/dao/[contractAddress]/index.tsx | 4 +- apps/dapp/pages/dao/create.tsx | 14 +- apps/dapp/pages/dao/list.tsx | 13 +- apps/dapp/pages/index.tsx | 6 +- .../multisig/[contractAddress]/index.tsx | 4 +- apps/dapp/pages/multisig/create.tsx | 4 +- apps/dapp/pages/multisig/list.tsx | 12 +- apps/dapp/pages/starred.tsx | 8 +- apps/dapp/selectors/cosm.ts | 2 +- apps/dapp/selectors/daos.ts | 2 +- apps/dapp/selectors/multisigs.ts | 2 +- apps/dapp/services/keplr.tsx | 3 +- apps/dapp/templates/changeMembers.tsx | 2 +- apps/dapp/templates/configUpdate.tsx | 17 +-- apps/dapp/templates/mint.tsx | 8 +- apps/dapp/templates/removeToken.tsx | 2 +- apps/dapp/templates/spend.tsx | 18 +-- apps/dapp/templates/stake.tsx | 18 +-- apps/dapp/templates/templateList.tsx | 3 +- apps/dapp/util/formValidation.ts | 2 +- apps/dapp/util/messagehelpers.ts | 129 ++---------------- apps/dapp/util/proposal.ts | 2 +- .../ui}/components/CosmosMessageDisplay.tsx | 12 +- packages/ui/components/MarkdownPreview.tsx | 12 ++ packages/ui/components/Modal.tsx | 18 +-- .../ProposalDetails/ProposalDetails.tsx | 30 ++++ .../ProposalMessageTemplateList.tsx | 73 ++++++++++ .../ui/components/ProposalDetails/index.tsx | 1 + packages/ui/index.tsx | 1 + packages/ui/package.json | 5 +- packages/utils/index.tsx | 1 + packages/utils/messages.ts | 102 ++++++++++++++ 59 files changed, 387 insertions(+), 328 deletions(-) delete mode 100644 apps/dapp/components/MarkdownPreview.tsx rename {apps/dapp => packages/ui}/components/CosmosMessageDisplay.tsx (81%) create mode 100644 packages/ui/components/MarkdownPreview.tsx create mode 100644 packages/ui/components/ProposalDetails/ProposalDetails.tsx create mode 100644 packages/ui/components/ProposalDetails/ProposalMessageTemplateList.tsx create mode 100644 packages/ui/components/ProposalDetails/index.tsx create mode 100644 packages/utils/messages.ts diff --git a/apps/dapp/atoms/localStorageEffect.ts b/apps/dapp/atoms/localStorageEffect.ts index 5dc13c470..c71588593 100644 --- a/apps/dapp/atoms/localStorageEffect.ts +++ b/apps/dapp/atoms/localStorageEffect.ts @@ -2,7 +2,7 @@ import { AtomEffect } from 'recoil' export const localStorageEffect: (key: string) => AtomEffect = (key) => - ({ setSelf, onSet, node }) => { + ({ setSelf, onSet, node: _ }) => { const savedValue = localStorage.getItem(key) if (savedValue != null) { const json = JSON.parse(savedValue) diff --git a/apps/dapp/components/BetaWarning.tsx b/apps/dapp/components/BetaWarning.tsx index 0079f9e92..c3c31e057 100644 --- a/apps/dapp/components/BetaWarning.tsx +++ b/apps/dapp/components/BetaWarning.tsx @@ -47,14 +47,14 @@ export function BetaWarningModal({ onAccept }: { onAccept: Function }) { export function BetaNotice({ onClose }: { onClose: Function }) { return ( -
+

DAO DAO is in
beta!

)} {partyMode && ( -
+
-
- )} - {proposal.status === 'passed' && ( - <> -

Status

- - executeProposalExecute( - proposalId, - contractAddress, - signingClient, - wallet, - () => { - setProposalUpdates((n) => n + 1) - setProposalsUpdated((p) => - p.includes(proposalId) ? p : p.concat([proposalId]) - ) - setTreasuryTokenListUpdates((n) => n + 1) - }, - setLoading - ) - } - /> - - )} -

Vote

- {proposal.status === 'open' && !walletVote && votingPower !== 0 && ( - - executeProposalVote( - position, - proposalId, - contractAddress, - signingClient, - wallet, - () => { - setProposalUpdates((n) => n + 1) - setProposalsUpdated((p) => - p.includes(proposalId) ? p : p.concat([proposalId]) - ) - }, - setLoading + { + const data = messageTemplateAndValuesForDecodedCosmosMsg( + message, + fromCosmosMsgProps + ) + if (data) { + const ThisIsAComponentBecauseReactIsAnnoying = () => { + // Can't call `useForm` in a callback. + const formMethods = useForm({ defaultValues: data.values }) + return ( + +
+ field} + multisig={multisig} + readOnly + /> + +
) } - voterWeight={weightPercent} + return + } + return ( + + ) + }} + onExecute={() => + executeProposalExecute( + proposalId, + contractAddress, + signingClient, + wallet, + () => { + setProposalUpdates((n) => n + 1) + setProposalsUpdated((p) => + p.includes(proposalId) ? p : p.concat([proposalId]) + ) + setTreasuryTokenListUpdates((n) => n + 1) + }, + setLoading + ) + } + onVote={(choice) => + executeProposalVote( + choice, + proposalId, + contractAddress, + signingClient, + wallet, + () => { + setProposalUpdates((n) => n + 1) + setProposalsUpdated((p) => + p.includes(proposalId) ? p : p.concat([proposalId]) + ) + }, + setLoading + ) + } + proposal={proposal} + setShowStaking={(s) => setShowStaking(s)} + showStaking={showStaking} + stakingModal={ + setTokenBalancesLoading(false)} + beforeExecute={() => setTokenBalancesLoading(true)} + claimableTokens={0} + contractAddress={contractAddress} + defaultMode={StakingMode.Stake} + onClose={() => setShowStaking(false)} /> - )} - {walletVote && ( -

You voted {walletVote} on this proposal.

- )} - {proposal.status !== 'open' && !walletVote && ( -

You did not vote on this proposal.

- )} - {votingPower === 0 && ( -

- You must have voting power at the time of proposal creation to vote.{' '} - {!multisig && ( - - )} - {!multisig && showStaking && ( - setTokenBalancesLoading(false)} - beforeExecute={() => setTokenBalancesLoading(true)} - claimableTokens={0} - contractAddress={contractAddress} - defaultMode={StakingMode.Stake} - onClose={() => setShowStakng(false)} - /> - )} -

- )} -
+ } + walletVote={walletVote} + walletWeightPercent={weightPercent} + /> ) } diff --git a/apps/dapp/components/ProposalForm.tsx b/apps/dapp/components/ProposalForm.tsx index 6ce23cfda..fd52681f4 100644 --- a/apps/dapp/components/ProposalForm.tsx +++ b/apps/dapp/components/ProposalForm.tsx @@ -2,8 +2,20 @@ import { useState } from 'react' import { useRecoilValue } from 'recoil' +import { Airplane } from '@dao-dao/icons' import { CosmosMsgFor_Empty } from '@dao-dao/types/contracts/cw3-dao' -import { Button, Tooltip, MarkdownPreview } from '@dao-dao/ui' +import { + Button, + Tooltip, + MarkdownPreview, + CosmosMessageDisplay, +} from '@dao-dao/ui' +import { + InputErrorMessage, + InputLabel, + TextareaInput, + TextInput, +} from '@dao-dao/ui' import { EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@heroicons/react/outline' import { FormProvider, useFieldArray, useForm } from 'react-hook-form' @@ -21,12 +33,6 @@ import { import { validateRequired } from 'util/formValidation' import { decodedMessagesString } from 'util/messagehelpers' -import { CosmosMessageDisplay } from './CosmosMessageDisplay' -import SvgAirplane from './icons/Airplane' -import { InputErrorMessage } from './input/InputErrorMessage' -import { InputLabel } from './input/InputLabel' -import { TextareaInput } from './input/TextAreaInput' -import { TextInput } from './input/TextInput' import { ProposalTemplateSelector } from './TemplateSelector' interface FormProposalData { @@ -220,7 +226,7 @@ export function ProposalForm({ >
)} @@ -93,7 +95,7 @@ export const Execute: FC = ({ onExecute, messages, loading }) => { loading={loading} onClick={() => onExecute()} > - Execute + Execute
diff --git a/packages/ui/components/ProposalDetails/ProposalDetails.tsx b/packages/ui/components/ProposalDetails/ProposalDetails.tsx index 1f8832ac3..dd8b394bd 100644 --- a/packages/ui/components/ProposalDetails/ProposalDetails.tsx +++ b/packages/ui/components/ProposalDetails/ProposalDetails.tsx @@ -1,15 +1,45 @@ import { ProposalResponse } from '@dao-dao/types/contracts/cw3-dao' -import { FC } from 'react' +import { FC, ReactNode, useState } from 'react' import { MarkdownPreview } from '../MarkdownPreview' +import { Button } from '../Button' +import { Execute } from '../Execute' +import { Vote, VoteChoice } from '../Vote' +import { EyeOffIcon, EyeIcon } from '@heroicons/react/outline' +import { ProposalMessageTemplateList } from './ProposalMessageTemplateList' import { CosmosMessageDisplay } from '../CosmosMessageDisplay' import { decodedMessagesString, decodeMessages } from '@dao-dao/utils' export interface ProposalDetailsProps { proposal: ProposalResponse + walletVote: string | undefined + walletWeightPercent: number + loading: boolean + showStaking: boolean + setShowStaking: (value: boolean) => void + stakingModal?: ReactNode + // Transformer to convert a decoded message into a displayable ReactNode. The + // caller will likely use this to transform these messages into template + // components. Once we have a state package we will want to move + // templates into their own package and then this can likely be removed. + messageToDisplay: (message: { [key: string]: any }) => ReactNode + onExecute: () => void + onVote: (choice: VoteChoice) => void } -export const ProposalDetails: FC = ({ proposal }) => { +export const ProposalDetails: FC = ({ + proposal, + walletVote, + walletWeightPercent, + loading, + stakingModal, + showStaking, + setShowStaking, + messageToDisplay, + onExecute, + onVote, +}) => { const decodedMessages = decodeMessages(proposal.msgs) + const [showRaw, setShowRaw] = useState(false) return (
@@ -20,10 +50,79 @@ export const ProposalDetails: FC = ({ proposal }) => {
Messages
- {decodedMessages?.length ? ( - - ) : ( -
[]
+
+ {decodedMessages?.length ? ( + showRaw ? ( + + ) : ( + + ) + ) : ( +
[]
+ )} +
+ {!!decodedMessages.length && ( +
+ +
+ )} + {proposal.status === 'passed' && ( + <> +

Status

+ + + )} +

Vote

+ {proposal.status === 'open' && + !walletVote && + walletWeightPercent !== 0 && ( + + )} + {walletVote && ( +

You voted {walletVote} on this proposal.

+ )} + {proposal.status !== 'open' && !walletVote && ( +

You did not vote on this proposal.

+ )} + {walletWeightPercent === 0 && ( +

+ You must have voting power at the time of proposal creation to vote.{' '} + {stakingModal && ( + + )} + {stakingModal && showStaking && stakingModal} +

)}
) diff --git a/packages/ui/components/ProposalDetails/ProposalMessageTemplateList.tsx b/packages/ui/components/ProposalDetails/ProposalMessageTemplateList.tsx index 0d250348e..92e43a56c 100644 --- a/packages/ui/components/ProposalDetails/ProposalMessageTemplateList.tsx +++ b/packages/ui/components/ProposalDetails/ProposalMessageTemplateList.tsx @@ -1,72 +1,18 @@ import { CosmosMsgFor_Empty } from '@dao-dao/types/contracts/cw3-dao' -import { FormProvider, useForm } from 'react-hook-form' import { FC, ReactNode } from 'react' -import { decodeMessages } from '@dao-dao/util' -import { CosmosMessageDisplay } from '../CosmosMessageDisplay' - -export interface ProposalMessageTemplateListItemProps { - template: any // MessageTemplate // !!FIXME!! break templates into package. - values: any - contractAddress: string - multisig?: boolean -} - -export const ProposalMessageTemplateListItem: FC< - ProposalMessageTemplateListItemProps -> = ({ template, values, contractAddress, multisig }) => { - const formMethods = useForm({ - defaultValues: values, - }) - - return ( - -
- field} - multisig={multisig} - readOnly - /> - -
- ) -} +import { decodeMessages } from '@dao-dao/utils' interface ProposalMessageTemplateListProps { msgs: CosmosMsgFor_Empty[] - contractAddress: string - multisig?: boolean - fromCosmosMsgProps: FromCosmosMsgProps + messageToDisplay: (message: { [key: string]: any }) => ReactNode } -function ProposalMessageTemplateList({ - msgs, - contractAddress, - multisig, - fromCosmosMsgProps, -}: ProposalMessageTemplateListProps) { +export const ProposalMessageTemplateList: FC< + ProposalMessageTemplateListProps +> = ({ msgs, messageToDisplay }: ProposalMessageTemplateListProps) => { const components: ReactNode[] = msgs.map((msg, index) => { const decoded = decodeMessages([msg])[0] - const data = messageTemplateAndValuesForDecodedCosmosMsg( - decoded, - fromCosmosMsgProps - ) - - return data ? ( - - ) : ( - // If no message template found, render raw message. - - ) + return
{messageToDisplay(decoded)}
}) return <>{components} diff --git a/apps/dapp/components/Vote.tsx b/packages/ui/components/Vote.tsx similarity index 93% rename from apps/dapp/components/Vote.tsx rename to packages/ui/components/Vote.tsx index 5f18dcf6d..3cc542d91 100644 --- a/apps/dapp/components/Vote.tsx +++ b/packages/ui/components/Vote.tsx @@ -3,8 +3,7 @@ import { FC, useState } from 'react' import { Button } from '@dao-dao/ui' import { CheckIcon, XIcon } from '@heroicons/react/outline' -import SvgAbstain from './icons/Abstain' -import SvgAirplane from './icons/Airplane' +import { Abstain, Airplane } from '@dao-dao/icons' export enum VoteChoice { Yes, @@ -64,7 +63,7 @@ export const Vote: FC = ({ onVote, voterWeight, loading }) => { } variant="secondary" > - + Abstain
) diff --git a/apps/dapp/components/input/AddressInput.tsx b/packages/ui/components/input/AddressInput.tsx similarity index 91% rename from apps/dapp/components/input/AddressInput.tsx rename to packages/ui/components/input/AddressInput.tsx index 6360054ca..58807b622 100644 --- a/apps/dapp/components/input/AddressInput.tsx +++ b/packages/ui/components/input/AddressInput.tsx @@ -8,7 +8,7 @@ import { Validate, } from 'react-hook-form' -import SvgWallet from '@components/icons/Wallet' +import { Wallet } from '@dao-dao/icons' export function AddressInput>({ label, @@ -34,7 +34,7 @@ export function AddressInput>({ className={`flex items-center gap-1 bg-transparent rounded-lg px-3 py-2 transition focus-within:ring-1 focus-within:outline-none ring-brand ring-offset-0 border-default border border-default text-sm font-mono ${error ? ' ring-error ring-1' : ''}`} > - +
diff --git a/apps/dapp/components/input/InputErrorMessage.tsx b/packages/ui/components/input/InputErrorMessage.tsx similarity index 100% rename from apps/dapp/components/input/InputErrorMessage.tsx rename to packages/ui/components/input/InputErrorMessage.tsx diff --git a/apps/dapp/components/input/InputLabel.tsx b/packages/ui/components/input/InputLabel.tsx similarity index 100% rename from apps/dapp/components/input/InputLabel.tsx rename to packages/ui/components/input/InputLabel.tsx diff --git a/apps/dapp/components/input/NumberInput.tsx b/packages/ui/components/input/NumberInput.tsx similarity index 100% rename from apps/dapp/components/input/NumberInput.tsx rename to packages/ui/components/input/NumberInput.tsx diff --git a/apps/dapp/components/input/SelectInput.tsx b/packages/ui/components/input/SelectInput.tsx similarity index 100% rename from apps/dapp/components/input/SelectInput.tsx rename to packages/ui/components/input/SelectInput.tsx diff --git a/apps/dapp/components/input/TextAreaInput.tsx b/packages/ui/components/input/TextAreaInput.tsx similarity index 100% rename from apps/dapp/components/input/TextAreaInput.tsx rename to packages/ui/components/input/TextAreaInput.tsx diff --git a/apps/dapp/components/input/TextInput.tsx b/packages/ui/components/input/TextInput.tsx similarity index 100% rename from apps/dapp/components/input/TextInput.tsx rename to packages/ui/components/input/TextInput.tsx diff --git a/apps/dapp/components/input/ToggleInput.tsx b/packages/ui/components/input/ToggleInput.tsx similarity index 100% rename from apps/dapp/components/input/ToggleInput.tsx rename to packages/ui/components/input/ToggleInput.tsx diff --git a/apps/dapp/components/input/TokenAmountInput.tsx b/packages/ui/components/input/TokenAmountInput.tsx similarity index 100% rename from apps/dapp/components/input/TokenAmountInput.tsx rename to packages/ui/components/input/TokenAmountInput.tsx diff --git a/packages/ui/components/input/index.tsx b/packages/ui/components/input/index.tsx new file mode 100644 index 000000000..965b3dc90 --- /dev/null +++ b/packages/ui/components/input/index.tsx @@ -0,0 +1,11 @@ +export * from './AddressInput' +export * from './CodeMirrorInput' +export * from './ImageSelector' +export * from './InputErrorMessage' +export * from './InputLabel' +export * from './NumberInput' +export * from './SelectInput' +export * from './TextAreaInput' +export * from './TextInput' +export * from './ToggleInput' +export * from './TokenAmountInput' diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index e011be02a..cf6bf0694 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -1,6 +1,11 @@ -export * from './components/Button' +export * from './components/Button/index' export * from './theme' -export * from './components/StakingModal' +export * from './components/StakingModal/index' export * from './components/Modal' export * from './components/Tooltip' -export * from './components/ProposalDetails' +export * from './components/ProposalDetails/index' +export * from './components/MarkdownPreview' +export * from './components/CosmosMessageDisplay' +export * from './components/input/index' +export * from './components/Execute' +export * from './components/Vote' diff --git a/packages/ui/package.json b/packages/ui/package.json index 372d861d3..0f9c2599b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -15,10 +15,11 @@ }, "dependencies": { "@dao-dao/utils": "*", + "@dao-dao/icons": "*", "@heroicons/react": "^1.0.5", "@dao-dao/types": "^0.0.8", "react-markdown": "^8.0.0", - "react-hook-form": "^7.20.4", + "react-hook-form": "^7.20.4", "codemirror": "^5.65.0" }, "devDependencies": { From 65f779065d845f1959a11addfcf312e467ae3562 Mon Sep 17 00:00:00 2001 From: Zeke Medley Date: Sat, 16 Apr 2022 19:46:34 -0700 Subject: [PATCH 05/16] Migrate icons to icons package. --- apps/dapp/assets/icons/Abstain.svg | 3 --- apps/dapp/assets/icons/Discord.svg | 3 --- apps/dapp/assets/icons/MemberCheck.svg | 4 ---- apps/dapp/assets/icons/arrowUpRight.svg | 3 --- apps/dapp/assets/icons/connect.svg | 5 ---- apps/dapp/assets/icons/copy.svg | 5 ---- apps/dapp/assets/icons/dao.svg | 3 --- apps/dapp/assets/icons/executed.svg | 3 --- apps/dapp/assets/icons/github.svg | 3 --- apps/dapp/assets/icons/message.svg | 3 --- apps/dapp/assets/icons/open.svg | 3 --- apps/dapp/assets/icons/passed.svg | 3 --- apps/dapp/assets/icons/pencil.svg | 3 --- apps/dapp/assets/icons/rejected.svg | 3 --- apps/dapp/assets/icons/twitter.svg | 3 --- apps/dapp/assets/icons/votes.svg | 3 --- apps/dapp/assets/icons/wallet.svg | 3 --- apps/dapp/components/BetaWarning.tsx | 5 ++-- apps/dapp/components/ConnectWalletButton.tsx | 10 ++++---- apps/dapp/components/ContractCard.tsx | 16 ++++--------- apps/dapp/components/CopyToClipboard.tsx | 5 ++-- apps/dapp/components/DaoContractInfo.tsx | 8 +++---- apps/dapp/components/MultisigContractInfo.tsx | 4 ++-- .../components/ProposalDetailsSidebar.tsx | 5 ++-- .../components/StatusIcons/StatusIcons.tsx | 15 ++++-------- apps/dapp/components/icons/Abstain.tsx | 17 ------------- apps/dapp/components/icons/Airplane.tsx | 24 ------------------- apps/dapp/components/icons/ArrowUpRight.tsx | 24 ------------------- apps/dapp/components/icons/Connect.tsx | 24 ------------------- apps/dapp/components/icons/Copy.tsx | 22 ----------------- apps/dapp/components/icons/Dao.tsx | 20 ---------------- apps/dapp/components/icons/Discord.tsx | 22 ----------------- apps/dapp/components/icons/Draft.tsx | 23 ------------------ apps/dapp/components/icons/Executed.tsx | 23 ------------------ apps/dapp/components/icons/Github.tsx | 22 ----------------- apps/dapp/components/icons/MemberCheck.tsx | 21 ---------------- apps/dapp/components/icons/Message.tsx | 20 ---------------- apps/dapp/components/icons/Open.tsx | 23 ------------------ apps/dapp/components/icons/Passed.tsx | 23 ------------------ apps/dapp/components/icons/Pencil.tsx | 20 ---------------- apps/dapp/components/icons/Rejected.tsx | 23 ------------------ apps/dapp/components/icons/Twitter.tsx | 22 ----------------- apps/dapp/components/icons/Votes.tsx | 17 ------------- apps/dapp/components/icons/Wallet.tsx | 23 ------------------ .../pages/dao/[contractAddress]/index.tsx | 7 +++--- apps/dapp/pages/dao/create.tsx | 4 ++-- apps/dapp/pages/index.tsx | 19 +++++---------- apps/dapp/pages/multisig/create.tsx | 4 ++-- .../icons/assets/TriangleUp.svg | 8 +------ 49 files changed, 39 insertions(+), 540 deletions(-) delete mode 100644 apps/dapp/assets/icons/Abstain.svg delete mode 100644 apps/dapp/assets/icons/Discord.svg delete mode 100644 apps/dapp/assets/icons/MemberCheck.svg delete mode 100644 apps/dapp/assets/icons/arrowUpRight.svg delete mode 100644 apps/dapp/assets/icons/connect.svg delete mode 100644 apps/dapp/assets/icons/copy.svg delete mode 100644 apps/dapp/assets/icons/dao.svg delete mode 100644 apps/dapp/assets/icons/executed.svg delete mode 100644 apps/dapp/assets/icons/github.svg delete mode 100644 apps/dapp/assets/icons/message.svg delete mode 100644 apps/dapp/assets/icons/open.svg delete mode 100644 apps/dapp/assets/icons/passed.svg delete mode 100644 apps/dapp/assets/icons/pencil.svg delete mode 100644 apps/dapp/assets/icons/rejected.svg delete mode 100644 apps/dapp/assets/icons/twitter.svg delete mode 100644 apps/dapp/assets/icons/votes.svg delete mode 100644 apps/dapp/assets/icons/wallet.svg delete mode 100644 apps/dapp/components/icons/Abstain.tsx delete mode 100644 apps/dapp/components/icons/Airplane.tsx delete mode 100644 apps/dapp/components/icons/ArrowUpRight.tsx delete mode 100644 apps/dapp/components/icons/Connect.tsx delete mode 100644 apps/dapp/components/icons/Copy.tsx delete mode 100644 apps/dapp/components/icons/Dao.tsx delete mode 100644 apps/dapp/components/icons/Discord.tsx delete mode 100644 apps/dapp/components/icons/Draft.tsx delete mode 100644 apps/dapp/components/icons/Executed.tsx delete mode 100644 apps/dapp/components/icons/Github.tsx delete mode 100644 apps/dapp/components/icons/MemberCheck.tsx delete mode 100644 apps/dapp/components/icons/Message.tsx delete mode 100644 apps/dapp/components/icons/Open.tsx delete mode 100644 apps/dapp/components/icons/Passed.tsx delete mode 100644 apps/dapp/components/icons/Pencil.tsx delete mode 100644 apps/dapp/components/icons/Rejected.tsx delete mode 100644 apps/dapp/components/icons/Twitter.tsx delete mode 100644 apps/dapp/components/icons/Votes.tsx delete mode 100644 apps/dapp/components/icons/Wallet.tsx rename apps/dapp/components/icons/TriangleUp.tsx => packages/icons/assets/TriangleUp.svg (58%) diff --git a/apps/dapp/assets/icons/Abstain.svg b/apps/dapp/assets/icons/Abstain.svg deleted file mode 100644 index 5a2b1f7b6..000000000 --- a/apps/dapp/assets/icons/Abstain.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/Discord.svg b/apps/dapp/assets/icons/Discord.svg deleted file mode 100644 index ba63a0e3e..000000000 --- a/apps/dapp/assets/icons/Discord.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/MemberCheck.svg b/apps/dapp/assets/icons/MemberCheck.svg deleted file mode 100644 index 7749247e4..000000000 --- a/apps/dapp/assets/icons/MemberCheck.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/dapp/assets/icons/arrowUpRight.svg b/apps/dapp/assets/icons/arrowUpRight.svg deleted file mode 100644 index 6332fc372..000000000 --- a/apps/dapp/assets/icons/arrowUpRight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/connect.svg b/apps/dapp/assets/icons/connect.svg deleted file mode 100644 index 9128d6da6..000000000 --- a/apps/dapp/assets/icons/connect.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/apps/dapp/assets/icons/copy.svg b/apps/dapp/assets/icons/copy.svg deleted file mode 100644 index 59e7dc3c1..000000000 --- a/apps/dapp/assets/icons/copy.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/apps/dapp/assets/icons/dao.svg b/apps/dapp/assets/icons/dao.svg deleted file mode 100644 index d12d7f55d..000000000 --- a/apps/dapp/assets/icons/dao.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/executed.svg b/apps/dapp/assets/icons/executed.svg deleted file mode 100644 index e4cb197f9..000000000 --- a/apps/dapp/assets/icons/executed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/github.svg b/apps/dapp/assets/icons/github.svg deleted file mode 100644 index 9b2f5fef6..000000000 --- a/apps/dapp/assets/icons/github.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/message.svg b/apps/dapp/assets/icons/message.svg deleted file mode 100644 index 8880b9771..000000000 --- a/apps/dapp/assets/icons/message.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/open.svg b/apps/dapp/assets/icons/open.svg deleted file mode 100644 index 9319b03cd..000000000 --- a/apps/dapp/assets/icons/open.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/passed.svg b/apps/dapp/assets/icons/passed.svg deleted file mode 100644 index d9cb1c83d..000000000 --- a/apps/dapp/assets/icons/passed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/pencil.svg b/apps/dapp/assets/icons/pencil.svg deleted file mode 100644 index e68adfe47..000000000 --- a/apps/dapp/assets/icons/pencil.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/rejected.svg b/apps/dapp/assets/icons/rejected.svg deleted file mode 100644 index 02f5b2023..000000000 --- a/apps/dapp/assets/icons/rejected.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/twitter.svg b/apps/dapp/assets/icons/twitter.svg deleted file mode 100644 index 1cb8a6447..000000000 --- a/apps/dapp/assets/icons/twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/votes.svg b/apps/dapp/assets/icons/votes.svg deleted file mode 100644 index 8d0c1ec36..000000000 --- a/apps/dapp/assets/icons/votes.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/assets/icons/wallet.svg b/apps/dapp/assets/icons/wallet.svg deleted file mode 100644 index 55800008e..000000000 --- a/apps/dapp/assets/icons/wallet.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/dapp/components/BetaWarning.tsx b/apps/dapp/components/BetaWarning.tsx index c3c31e057..87f7c971c 100644 --- a/apps/dapp/components/BetaWarning.tsx +++ b/apps/dapp/components/BetaWarning.tsx @@ -1,8 +1,7 @@ +import { Message } from '@dao-dao/icons' import { Button } from '@dao-dao/ui' import { ChevronRightIcon, XIcon } from '@heroicons/react/outline' -import SvgMessage from 'components/icons/Message' - export function BetaWarningModal({ onAccept }: { onAccept: Function }) { return (
@@ -49,7 +48,7 @@ export function BetaNotice({ onClose }: { onClose: Function }) { return (
- +

DAO DAO is in
beta!

diff --git a/apps/dapp/components/ConnectWalletButton.tsx b/apps/dapp/components/ConnectWalletButton.tsx index e3e0307f0..e4f0a282e 100644 --- a/apps/dapp/components/ConnectWalletButton.tsx +++ b/apps/dapp/components/ConnectWalletButton.tsx @@ -2,6 +2,7 @@ import { useCallback, useState } from 'react' import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil' +import { Copy, Wallet } from '@dao-dao/icons' import { Button, Tooltip } from '@dao-dao/ui' import { CHAIN_ID, @@ -24,9 +25,6 @@ import { } from 'selectors/cosm' import { connectKeplrWithoutAlerts } from 'services/keplr' -import SvgCopy from './icons/Copy' -import SvgWallet from './icons/Wallet' - function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) return ( @@ -42,7 +40,7 @@ function CopyButton({ text }: { text: string }) { {copied ? ( ) : ( - + )} @@ -111,7 +109,7 @@ function WalletConnect() { return (
- +
{walletName}
@@ -134,7 +132,7 @@ function WalletConnect() { full onClick={handleConnect} > - +

Connect wallet

diff --git a/apps/dapp/components/ContractCard.tsx b/apps/dapp/components/ContractCard.tsx index a4ecc6220..aa1cabaa9 100644 --- a/apps/dapp/components/ContractCard.tsx +++ b/apps/dapp/components/ContractCard.tsx @@ -2,6 +2,7 @@ import { ReactNode } from 'react' import Link from 'next/link' +import { Dao, Pencil, Votes } from '@dao-dao/icons' import { CARD_IMAGES_ENABLED, NATIVE_DECIMALS, @@ -12,9 +13,6 @@ import { import { StarIcon as StarIconOutline } from '@heroicons/react/outline' import { StarIcon as StarIconSolid } from '@heroicons/react/solid' -import SvgDao from './icons/Dao' -import SvgPencil from './icons/Pencil' -import SvgVotes from './icons/Votes' import { Logo } from './Logo' function ContractCardBase({ @@ -65,7 +63,7 @@ function ContractCardBase({
{balance && (

- + {convertMicroDenomToDenomWithDecimals( balance, NATIVE_DECIMALS @@ -75,19 +73,13 @@ function ContractCardBase({ )} {proposals != undefined && (

- + {proposals} proposal{weight != 1 && 's'}

)} {weight != undefined && (

- + {weight} vote{weight != 1 && 's'}

)} diff --git a/apps/dapp/components/CopyToClipboard.tsx b/apps/dapp/components/CopyToClipboard.tsx index 97ef6ac84..f114be947 100644 --- a/apps/dapp/components/CopyToClipboard.tsx +++ b/apps/dapp/components/CopyToClipboard.tsx @@ -1,11 +1,10 @@ import { useState } from 'react' +import { Copy } from '@dao-dao/icons' import { useThemeContext } from '@dao-dao/ui' import { CheckCircleIcon } from '@heroicons/react/outline' import toast from 'react-hot-toast' -import SvgCopy from './icons/Copy' - function concatAddressImpl( address: string, takeStart: number, @@ -46,7 +45,7 @@ export function CopyToClipboard({ {copied ? ( ) : ( - + )} {concatAddress(value, takeN)} diff --git a/apps/dapp/components/DaoContractInfo.tsx b/apps/dapp/components/DaoContractInfo.tsx index 5a69078b4..fbab1e3eb 100644 --- a/apps/dapp/components/DaoContractInfo.tsx +++ b/apps/dapp/components/DaoContractInfo.tsx @@ -1,5 +1,6 @@ import { useRecoilValue } from 'recoil' +import { Votes } from '@dao-dao/icons' import { humanReadableDuration, convertMicroDenomToDenomWithDecimals, @@ -16,7 +17,6 @@ import { import { GovInfoListItem } from './ContractView' import { CopyToClipboardAccent } from './CopyToClipboard' import { DaoTreasury } from './DaoTreasury' -import SvgVotes from './icons/Votes' export function DaoContractInfo({ address }: { address: string }) { const daoInfo = useRecoilValue(daoSelector(address)) @@ -43,13 +43,13 @@ export function DaoContractInfo({ address }: { address: string }) { value={humanReadableDuration(unstakingDuration)} /> } + icon={} text="Passing threshold" value={threshold} /> {quorum && ( } + icon={} text="Quorum" value={quorum} /> @@ -61,7 +61,7 @@ export function DaoContractInfo({ address }: { address: string }) { />
  • - {' '} + {' '} {convertMicroDenomToDenomWithDecimals( daoInfo.config.proposal_deposit, govTokenInfo.decimals diff --git a/apps/dapp/components/MultisigContractInfo.tsx b/apps/dapp/components/MultisigContractInfo.tsx index cc99d4658..7ee54c496 100644 --- a/apps/dapp/components/MultisigContractInfo.tsx +++ b/apps/dapp/components/MultisigContractInfo.tsx @@ -1,5 +1,6 @@ import { useRecoilValue } from 'recoil' +import { Votes } from '@dao-dao/icons' import { humanReadableDuration, thresholdString } from '@dao-dao/utils' import { ClockIcon } from '@heroicons/react/outline' @@ -8,7 +9,6 @@ import { sigSelector } from 'selectors/multisigs' import { GovInfoListItem } from './ContractView' import { CopyToClipboardAccent } from './CopyToClipboard' import { DaoTreasury } from './DaoTreasury' -import SvgVotes from './icons/Votes' export function MultisigContractInfo({ address }: { address: string }) { const sigInfo = useRecoilValue(sigSelector(address)) @@ -19,7 +19,7 @@ export function MultisigContractInfo({ address }: { address: string }) {

    Governance Details

  • diff --git a/apps/dapp/pages/multisig/create.tsx b/apps/dapp/pages/multisig/create.tsx index 69abf20f8..4f5890441 100644 --- a/apps/dapp/pages/multisig/create.tsx +++ b/apps/dapp/pages/multisig/create.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/router' import { useSetRecoilState, useRecoilValue } from 'recoil' import { InstantiateResult } from '@cosmjs/cosmwasm-stargate' +import { Airplane } from '@dao-dao/icons' import { Button, Tooltip } from '@dao-dao/ui' import { ImageSelector, @@ -22,7 +23,6 @@ import { useFieldArray, useForm, Validate } from 'react-hook-form' import { GradientHero } from '@components/ContractView' import { FormCard } from '@components/FormCard' -import SvgAirplane from '@components/icons/Airplane' import TooltipsDisplay, { useTooltipsRegister, } from '@components/TooltipsDisplay' @@ -296,7 +296,7 @@ const CreateMultisig: NextPage = () => { >
    diff --git a/apps/dapp/components/icons/TriangleUp.tsx b/packages/icons/assets/TriangleUp.svg similarity index 58% rename from apps/dapp/components/icons/TriangleUp.tsx rename to packages/icons/assets/TriangleUp.svg index 0cc29e53b..b79e42af7 100644 --- a/apps/dapp/components/icons/TriangleUp.tsx +++ b/packages/icons/assets/TriangleUp.svg @@ -1,17 +1,11 @@ -import * as React from 'react' -import { SVGProps } from 'react' - -export const TriangleUp = (props: SVGProps) => ( -) From 0a2a22df984259974550615f168c3496324ce79f Mon Sep 17 00:00:00 2001 From: Zeke Medley Date: Sat, 16 Apr 2022 20:06:28 -0700 Subject: [PATCH 06/16] Stateless wallet connect button. --- apps/dapp/components/ConnectWalletButton.tsx | 75 ++--------------- packages/icons/assets/copy.svg | 6 +- packages/ui/components/WalletConnect.tsx | 89 ++++++++++++++++++++ packages/ui/index.tsx | 1 + 4 files changed, 102 insertions(+), 69 deletions(-) create mode 100644 packages/ui/components/WalletConnect.tsx diff --git a/apps/dapp/components/ConnectWalletButton.tsx b/apps/dapp/components/ConnectWalletButton.tsx index e4f0a282e..255687297 100644 --- a/apps/dapp/components/ConnectWalletButton.tsx +++ b/apps/dapp/components/ConnectWalletButton.tsx @@ -1,9 +1,8 @@ -import { useCallback, useState } from 'react' +import { useCallback } from 'react' import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil' -import { Copy, Wallet } from '@dao-dao/icons' -import { Button, Tooltip } from '@dao-dao/ui' +import { WalletConnect as StatelessWalletConnect } from '@dao-dao/ui' import { CHAIN_ID, NATIVE_DECIMALS, @@ -11,7 +10,6 @@ import { convertDenomToHumanReadableDenom, convertMicroDenomToDenomWithDecimals, } from '@dao-dao/utils' -import { CheckCircleIcon, LogoutIcon } from '@heroicons/react/outline' import { connectedWalletAtom, @@ -25,38 +23,6 @@ import { } from 'selectors/cosm' import { connectKeplrWithoutAlerts } from 'services/keplr' -function CopyButton({ text }: { text: string }) { - const [copied, setCopied] = useState(false) - return ( - - - - ) -} - -function DisconnectButton({ onClick }: { onClick: () => void }) { - return ( - - - - ) -} - function WalletConnect() { const [wallet, setWallet] = useRecoilState(connectedWalletAtom) const setInstallWarningVisible = useSetRecoilState(installWarningVisibleAtom) @@ -105,37 +71,14 @@ function WalletConnect() { setWallet, ]) - if (walletAddress) { - return ( -
    -
    - -
    - {walletName} -
    - - {walletBalanceHuman} {chainDenomHuman} - -
    -
    -
    - - -
    -
    - ) - } return ( -
    - -
    + ) } diff --git a/packages/icons/assets/copy.svg b/packages/icons/assets/copy.svg index 59e7dc3c1..fe675c746 100644 --- a/packages/icons/assets/copy.svg +++ b/packages/icons/assets/copy.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/packages/ui/components/WalletConnect.tsx b/packages/ui/components/WalletConnect.tsx new file mode 100644 index 000000000..dcab6cbfd --- /dev/null +++ b/packages/ui/components/WalletConnect.tsx @@ -0,0 +1,89 @@ +import { FC, useState } from 'react' + +import { Wallet, Copy } from '@dao-dao/icons' +import { CheckCircleIcon, LogoutIcon } from '@heroicons/react/outline' +import { Button } from './Button' +import { Tooltip } from './Tooltip' + +export interface WalletConnectProps { + walletAddress: string + walletName: string | undefined + walletBalance: number + walletBalanceDenom: string + handleConnect: () => void +} + +export const WalletConnect: FC = ({ + walletAddress, + walletName, + walletBalance, + walletBalanceDenom, + handleConnect, +}) => + walletAddress ? ( +
    +
    + +
    + {walletName} +
    + + {walletBalance} {walletBalanceDenom} + +
    +
    +
    + + +
    +
    + ) : ( +
    + +
    + ) + +interface CopyButtonProps { + text: string +} + +const CopyButton: FC = ({ text }) => { + const [copied, setCopied] = useState(false) + return ( + + + + ) +} + +interface DisconnectButtonProps { + onClick: () => void +} + +const DisconnectButton: FC = ({ onClick }) => ( + + + +) diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index cf6bf0694..b8cafcb1d 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -9,3 +9,4 @@ export * from './components/CosmosMessageDisplay' export * from './components/input/index' export * from './components/Execute' export * from './components/Vote' +export * from './components/WalletConnect' From 43657e1cfe0d1225d0842df165eefa795af55d91 Mon Sep 17 00:00:00 2001 From: Zeke Medley Date: Sun, 17 Apr 2022 14:51:49 -0700 Subject: [PATCH 07/16] Stateless proposal details sidebar. --- apps/dapp/components/ConnectWalletButton.tsx | 4 +- apps/dapp/components/DaoContractInfo.tsx | 2 +- apps/dapp/components/MultisigContractInfo.tsx | 2 +- .../components/ProposalDetailsSidebar.tsx | 612 +----------------- apps/dapp/components/ProposalList.tsx | 2 +- .../proposals/[proposalId].tsx | 2 +- .../[contractAddress]/proposals/create.tsx | 2 +- .../multisig/[contractAddress]/index.tsx | 2 +- .../ui}/components/CopyToClipboard.tsx | 0 .../ui}/components/Progress.tsx | 0 .../ProposalDetailsSidebar.tsx | 599 +++++++++++++++++ .../ui/components/ProposalDetails/index.tsx | 1 + .../ProposalStatus/ProposalStatus.stories.tsx | 0 .../ProposalStatus/ProposalStatus.tsx | 0 .../ui}/components/ProposalStatus/index.ts | 0 .../components/StatusIcons/StatusIcons.tsx | 0 .../ui}/components/StatusIcons/index.ts | 0 .../ui/components/input/ImageSelector.tsx | 3 +- packages/ui/index.tsx | 3 + packages/ui/package.json | 1 + packages/ui/tsconfig.json | 3 +- 21 files changed, 651 insertions(+), 587 deletions(-) rename {apps/dapp => packages/ui}/components/CopyToClipboard.tsx (100%) rename {apps/dapp => packages/ui}/components/Progress.tsx (100%) create mode 100644 packages/ui/components/ProposalDetails/ProposalDetailsSidebar.tsx rename {apps/dapp => packages/ui}/components/ProposalStatus/ProposalStatus.stories.tsx (100%) rename {apps/dapp => packages/ui}/components/ProposalStatus/ProposalStatus.tsx (100%) rename {apps/dapp => packages/ui}/components/ProposalStatus/index.ts (100%) rename {apps/dapp => packages/ui}/components/StatusIcons/StatusIcons.tsx (100%) rename {apps/dapp => packages/ui}/components/StatusIcons/index.ts (100%) diff --git a/apps/dapp/components/ConnectWalletButton.tsx b/apps/dapp/components/ConnectWalletButton.tsx index 255687297..037812a9d 100644 --- a/apps/dapp/components/ConnectWalletButton.tsx +++ b/apps/dapp/components/ConnectWalletButton.tsx @@ -73,11 +73,11 @@ function WalletConnect() { return ( ) } diff --git a/apps/dapp/components/DaoContractInfo.tsx b/apps/dapp/components/DaoContractInfo.tsx index fbab1e3eb..d2a37e91a 100644 --- a/apps/dapp/components/DaoContractInfo.tsx +++ b/apps/dapp/components/DaoContractInfo.tsx @@ -1,6 +1,7 @@ import { useRecoilValue } from 'recoil' import { Votes } from '@dao-dao/icons' +import { CopyToClipboardAccent } from '@dao-dao/ui' import { humanReadableDuration, convertMicroDenomToDenomWithDecimals, @@ -15,7 +16,6 @@ import { } from 'selectors/daos' import { GovInfoListItem } from './ContractView' -import { CopyToClipboardAccent } from './CopyToClipboard' import { DaoTreasury } from './DaoTreasury' export function DaoContractInfo({ address }: { address: string }) { diff --git a/apps/dapp/components/MultisigContractInfo.tsx b/apps/dapp/components/MultisigContractInfo.tsx index 7ee54c496..ff36637d1 100644 --- a/apps/dapp/components/MultisigContractInfo.tsx +++ b/apps/dapp/components/MultisigContractInfo.tsx @@ -1,13 +1,13 @@ import { useRecoilValue } from 'recoil' import { Votes } from '@dao-dao/icons' +import { CopyToClipboardAccent } from '@dao-dao/ui' import { humanReadableDuration, thresholdString } from '@dao-dao/utils' import { ClockIcon } from '@heroicons/react/outline' import { sigSelector } from 'selectors/multisigs' import { GovInfoListItem } from './ContractView' -import { CopyToClipboardAccent } from './CopyToClipboard' import { DaoTreasury } from './DaoTreasury' export function MultisigContractInfo({ address }: { address: string }) { diff --git a/apps/dapp/components/ProposalDetailsSidebar.tsx b/apps/dapp/components/ProposalDetailsSidebar.tsx index f9d98a338..5ae83427a 100644 --- a/apps/dapp/components/ProposalDetailsSidebar.tsx +++ b/apps/dapp/components/ProposalDetailsSidebar.tsx @@ -1,14 +1,10 @@ import { useRecoilValue, useRecoilValueLoadable } from 'recoil' -import { TriangleUp, Abstain } from '@dao-dao/icons' -import { Tooltip } from '@dao-dao/ui' import { - CHAIN_TXN_URL_PREFIX, - convertMicroDenomToDenomWithDecimals, - expirationAtTimeToSecondsFromNow, - secondsToWdhms, -} from '@dao-dao/utils' -import { ExternalLinkIcon, CheckIcon, XIcon } from '@heroicons/react/outline' + ProposalDetailsVoteStatus as StatelessProposalDetailsVoteStatus, + ProposalDetailsCard as StatelessProposalDetailsCard, + WalletVote as StatelessWalletVote, +} from '@dao-dao/ui' import { proposalSelector, @@ -17,7 +13,6 @@ import { walletVoteSelector, proposalStartBlockSelector, votingPowerAtHeightSelector, - WalletVote, } from 'selectors/proposals' import { contractConfigSelector, @@ -25,23 +20,6 @@ import { } from 'util/contractConfigWrapper' import { useThresholdQuorum } from 'util/proposal' -import { CopyToClipboard } from './CopyToClipboard' -import { Progress } from './Progress' -import { ProposalStatus } from './ProposalStatus' - -const YouTooltip = ({ label }: { label: string }) => ( - -

    - ? -

    -
    -) - -const PASSING_THRESHOLD_TOOLTIP = - "A proposal must attain this proportion of 'Yes' votes to pass." -const QUORUM_TOOLTIP = - 'This proportion of voting weight must vote for a proposal to pass.' - interface ProposalDetailsProps { contractAddress: string multisig: boolean @@ -50,18 +28,12 @@ interface ProposalDetailsProps { export const ProposalDetailsCard = ({ contractAddress, - multisig, proposalId, + multisig, }: ProposalDetailsProps) => { const proposal = useRecoilValue( proposalSelector({ contractAddress, proposalId }) ) - const { state: proposalExecutionTXHashState, contents: txHashContents } = - useRecoilValueLoadable( - proposalExecutionTXHashSelector({ contractAddress, proposalId }) - ) - const proposalExecutionTXHash: string | null = - proposalExecutionTXHashState === 'hasValue' ? txHashContents : null const walletVote = useRecoilValue( walletVoteSelector({ contractAddress, proposalId }) @@ -79,115 +51,35 @@ export const ProposalDetailsCard = ({ ) const memberWhenProposalCreated = votingPower > 0 + const { state: proposalExecutionTXHashState, contents: txHashContents } = + useRecoilValueLoadable( + proposalExecutionTXHashSelector({ contractAddress, proposalId }) + ) + const proposalExecutionTXHash: string | undefined = + proposalExecutionTXHashState === 'hasValue' ? txHashContents : undefined + if (!proposal) { - return null + return ( +
    +

    Could not find proposal.

    +
    + ) } return ( -
    -
    -
    -

    - Proposal -

    - -

    - # {proposal.id.toString().padStart(6, '0')} -

    -
    - -
    - -
    -

    - Status -

    - -

    - -

    -
    - -
    - -
    -

    - You -

    - - {!memberWhenProposalCreated ? ( - - ) : walletVote === WalletVote.Yes ? ( -

    - Yes -

    - ) : walletVote === WalletVote.No ? ( -

    - No -

    - ) : walletVote === WalletVote.Abstain ? ( -

    - Abstain -

    - ) : walletVote === WalletVote.Veto ? ( -

    - Veto -

    - ) : walletVote ? ( -

    - Unknown: {walletVote} -

    - ) : proposal.status === 'open' ? ( - - ) : ( - - )} -
    -
    - -
    -

    Proposer

    - - - - {proposal.status === 'executed' && - proposalExecutionTXHashState === 'loading' ? ( - <> -

    TX

    -

    Loading...

    - - ) : !!proposalExecutionTXHash ? ( - <> - {CHAIN_TXN_URL_PREFIX ? ( - - TX - - - ) : ( -

    TX

    - )} - - - - ) : null} -
    -
    + ) } export const ProposalDetailsVoteStatus = ({ contractAddress, - multisig, proposalId, + multisig, }: ProposalDetailsProps) => { const proposal = useRecoilValue( proposalSelector({ contractAddress, proposalId }) @@ -209,458 +101,24 @@ export const ProposalDetailsVoteStatus = ({ const configWrapper = new ContractConfigWrapper(config) const tokenDecimals = configWrapper.gov_token_decimals - const localeOptions = { maximumSignificantDigits: 3 } - - const yesVotes = Number( - multisig - ? proposalTally.votes.yes - : convertMicroDenomToDenomWithDecimals( - proposalTally.votes.yes, - tokenDecimals - ) - ) - const noVotes = Number( - multisig - ? proposalTally.votes.no - : convertMicroDenomToDenomWithDecimals( - proposalTally.votes.no, - tokenDecimals - ) - ) - const abstainVotes = Number( - multisig - ? proposalTally.votes.abstain - : convertMicroDenomToDenomWithDecimals( - proposalTally.votes.abstain, - tokenDecimals - ) - ) - - const totalWeight = Number( - multisig - ? proposalTally.total_weight - : convertMicroDenomToDenomWithDecimals( - proposalTally.total_weight, - tokenDecimals - ) - ) - - const turnoutTotal = yesVotes + noVotes + abstainVotes - const turnoutYesPercent = turnoutTotal ? (yesVotes / turnoutTotal) * 100 : 0 - const turnoutNoPercent = turnoutTotal ? (noVotes / turnoutTotal) * 100 : 0 - const turnoutAbstainPercent = turnoutTotal - ? (abstainVotes / turnoutTotal) * 100 - : 0 - - const turnoutPercent = (turnoutTotal / totalWeight) * 100 - const totalYesPercent = (yesVotes / totalWeight) * 100 - const totalNoPercent = (noVotes / totalWeight) * 100 - const totalAbstainPercent = (abstainVotes / totalWeight) * 100 - - if (!proposal) { - return null - } - const maxVotingSeconds = 'time' in config.config.max_voting_period ? config.config.max_voting_period.time : undefined - const expiresInSeconds = - proposal.expires && 'at_time' in proposal.expires - ? expirationAtTimeToSecondsFromNow(proposal.expires) - : undefined - - const thresholdReached = - !!threshold && - (quorum ? turnoutYesPercent : totalYesPercent) >= threshold.percent - const quorumMet = !!quorum && turnoutPercent >= quorum.percent - const helpfulStatusText = - proposal.status === 'open' && threshold && quorum - ? thresholdReached && quorumMet - ? 'If the current vote stands, this proposal will pass.' - : !thresholdReached && quorumMet - ? "If the current vote stands, this proposal will fail because insufficient 'Yes' votes have been cast." - : thresholdReached && !quorumMet - ? 'If the current vote stands, this proposal will fail due to a lack of voter participation.' - : undefined - : undefined + if (!proposal) { + return null + } return ( -
    - {helpfulStatusText && ( -

    - {helpfulStatusText} -

    - )} - - {threshold ? ( - quorum ? ( - <> -

    Ratio of votes

    - -
    - {[ -

    - Yes{' '} - {turnoutYesPercent.toLocaleString(undefined, localeOptions)}% -

    , -

    - No {turnoutNoPercent.toLocaleString(undefined, localeOptions)} - % -

    , - ] - .sort(() => yesVotes - noVotes) - .map((elem, idx) => ( -
    - {elem} -
    - ))} -

    - Abstain{' '} - {turnoutAbstainPercent.toLocaleString(undefined, localeOptions)} - % -

    -
    - -
    - b.value - a.value), - { - value: Number(turnoutAbstainPercent), - // Secondary is dark with 80% opacity. - color: 'rgba(var(--dark), 0.8)', - }, - ], - }, - ]} - verticalBars={ - threshold && [ - { - value: threshold.percent, - color: 'rgba(var(--dark), 0.5)', - }, - ] - } - /> -
    - -
    - 90 - ? 'calc(100% - 32px)' - : `calc(${threshold.percent}% - 17px)`, - }} - width="36px" - /> - - -
    -

    - Passing threshold:{' '} - {threshold.display} -

    - -

    - {thresholdReached ? ( - <> - Passing{' '} - - - ) : ( - <> - Failing{' '} - - - )} -

    -
    -
    -
    - -
    -

    - Turnout -

    - -

    - {turnoutPercent.toLocaleString(undefined, localeOptions)}% -

    -
    - -
    - -
    - -
    - 90 - ? 'calc(100% - 32px)' - : `calc(${quorum.percent}% - 17px)`, - }} - width="36px" - /> - - -
    -

    - Quorum: {quorum.display} -

    - -

    - {quorumMet ? ( - <> - Reached{' '} - - - ) : ( - <> - Not met{' '} - - - )} -

    -
    -
    -
    - - ) : ( - <> -

    - Turnout -

    - -
    - {[ -

    - Yes {totalYesPercent.toLocaleString(undefined, localeOptions)} - % -

    , -

    - No {totalNoPercent.toLocaleString(undefined, localeOptions)}% -

    , - ] - .sort(() => yesVotes - noVotes) - .map((elem, idx) => ( -
    - {elem} -
    - ))} -

    - Abstain{' '} - {totalAbstainPercent.toLocaleString(undefined, localeOptions)}% -

    -
    - -
    - b.value - a.value), - { - value: Number(totalAbstainPercent), - // Secondary is dark with 80% opacity. - color: 'rgba(var(--dark), 0.8)', - }, - ], - }, - ]} - verticalBars={[ - { - value: threshold.percent, - color: 'rgba(var(--dark), 0.5)', - }, - ]} - /> -
    - -
    - 90 - ? 'calc(100% - 32px)' - : `calc(${threshold.percent}% - 17px)`, - }} - width="36px" - /> - - -
    -

    - Passing threshold:{' '} - {threshold.display} -

    - -

    - {thresholdReached ? ( - <> - Reached{' '} - - - ) : ( - <> - Not met{' '} - - - )} -

    -
    -
    -
    - - ) - ) : null} - - {proposal.status === 'open' && - expiresInSeconds !== undefined && - expiresInSeconds > 0 && ( - <> -

    - Time left -

    - -

    - {secondsToWdhms(expiresInSeconds, 2)} -

    - - {maxVotingSeconds !== undefined && ( -
    - -
    - )} - - )} - - {threshold?.percent === 50 && yesVotes === noVotes && ( -
    -

    Tie clarification

    - -

    {"'Yes' will win a tie vote."}

    -
    - )} - - {abstainVotes === turnoutTotal && ( -
    -

    All abstain clarification

    - -

    - {/* TODO: Change this to fail wen v1 contracts. */} - When all abstain, a proposal will pass. -

    -
    - )} -
    + ) } diff --git a/apps/dapp/components/ProposalList.tsx b/apps/dapp/components/ProposalList.tsx index 6085c1d5b..59e4b08fc 100644 --- a/apps/dapp/components/ProposalList.tsx +++ b/apps/dapp/components/ProposalList.tsx @@ -9,6 +9,7 @@ import { ProposalResponse, Status, } from '@dao-dao/types/contracts/cw3-dao' +import { ProposalStatus } from '@dao-dao/ui' import { expirationAtTimeToSecondsFromNow, secondsToWdhms, @@ -30,7 +31,6 @@ import { import { ExtendedProposalResponse } from 'types/proposals' import { draftProposalsToExtendedResponses } from '../util/proposal' -import { ProposalStatus } from './ProposalStatus' const PROP_LOAD_LIMIT = 10 diff --git a/apps/dapp/pages/dao/[contractAddress]/proposals/[proposalId].tsx b/apps/dapp/pages/dao/[contractAddress]/proposals/[proposalId].tsx index 9105722a6..34fbb971b 100644 --- a/apps/dapp/pages/dao/[contractAddress]/proposals/[proposalId].tsx +++ b/apps/dapp/pages/dao/[contractAddress]/proposals/[proposalId].tsx @@ -7,8 +7,8 @@ import { Breadcrumbs } from 'components/Breadcrumbs' import { ProposalDetails } from 'components/ProposalDetails' import { ProposalDetailsSidebar, - ProposalDetailsCard, ProposalDetailsVoteStatus, + ProposalDetailsCard, } from 'components/ProposalDetailsSidebar' import { daoSelector } from 'selectors/daos' import { cw20TokenInfo } from 'selectors/treasury' diff --git a/apps/dapp/pages/dao/[contractAddress]/proposals/create.tsx b/apps/dapp/pages/dao/[contractAddress]/proposals/create.tsx index 93b7c6642..7fe17c7ad 100644 --- a/apps/dapp/pages/dao/[contractAddress]/proposals/create.tsx +++ b/apps/dapp/pages/dao/[contractAddress]/proposals/create.tsx @@ -7,10 +7,10 @@ import { useRecoilValue, useSetRecoilState } from 'recoil' import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' import { findAttribute } from '@cosmjs/stargate/build/logs' +import { CopyToClipboard } from '@dao-dao/ui' import toast from 'react-hot-toast' import { Breadcrumbs } from '@components/Breadcrumbs' -import { CopyToClipboard } from '@components/CopyToClipboard' import { ProposalData, ProposalForm } from '@components/ProposalForm' import { proposalsCreatedAtom } from 'atoms/proposals' import { diff --git a/apps/dapp/pages/multisig/[contractAddress]/index.tsx b/apps/dapp/pages/multisig/[contractAddress]/index.tsx index 2d2b5bd6d..04135221d 100644 --- a/apps/dapp/pages/multisig/[contractAddress]/index.tsx +++ b/apps/dapp/pages/multisig/[contractAddress]/index.tsx @@ -7,13 +7,13 @@ import { useRecoilState, useRecoilValue } from 'recoil' import { Threshold } from '@dao-dao/types/contracts/cw3-multisig' import { useThemeContext } from '@dao-dao/ui' +import { CopyToClipboard } from '@dao-dao/ui' import { ScaleIcon, UserGroupIcon, VariableIcon, } from '@heroicons/react/outline' -import { CopyToClipboard } from '@components/CopyToClipboard' import { MultisigContractInfo } from '@components/MultisigContractInfo' import { pinnedMultisigsAtom } from 'atoms/pinned' import { Breadcrumbs } from 'components/Breadcrumbs' diff --git a/apps/dapp/components/CopyToClipboard.tsx b/packages/ui/components/CopyToClipboard.tsx similarity index 100% rename from apps/dapp/components/CopyToClipboard.tsx rename to packages/ui/components/CopyToClipboard.tsx diff --git a/apps/dapp/components/Progress.tsx b/packages/ui/components/Progress.tsx similarity index 100% rename from apps/dapp/components/Progress.tsx rename to packages/ui/components/Progress.tsx diff --git a/packages/ui/components/ProposalDetails/ProposalDetailsSidebar.tsx b/packages/ui/components/ProposalDetails/ProposalDetailsSidebar.tsx new file mode 100644 index 000000000..46abc5dba --- /dev/null +++ b/packages/ui/components/ProposalDetails/ProposalDetailsSidebar.tsx @@ -0,0 +1,599 @@ +import { FC } from 'react' +import { + ProposalResponse, + ProposalTallyResponse, +} from '@dao-dao/types/contracts/cw3-dao' +import { ProposalStatus } from '../ProposalStatus' +import { Tooltip } from '../Tooltip' +import { Progress } from '../Progress' +import { CopyToClipboard } from '../CopyToClipboard' +import { TriangleUp, Abstain } from '@dao-dao/icons' +import { ExternalLinkIcon, CheckIcon, XIcon } from '@heroicons/react/outline' +import { + CHAIN_TXN_URL_PREFIX, + convertMicroDenomToDenomWithDecimals, + expirationAtTimeToSecondsFromNow, + secondsToWdhms, +} from '@dao-dao/utils' + +// Need this for now as the `WalletVote` enum is stored in dapp state. TODO: +// remove once we break state into a package. +export enum WalletVote { + Yes = 'yes', + No = 'no', + Abstain = 'abstain', + Veto = 'veto', +} + +export interface ProposalDetailsCardProps { + proposal: ProposalResponse + memberWhenProposalCreated: boolean + walletVote: WalletVote + proposalExecutionTXHash: string | undefined +} + +export interface ProposalDetailsVoteStatusProps { + proposal: ProposalResponse + tally: ProposalTallyResponse + tokenDecimals: number + // Undefined if max voting period is in blocks. + maxVotingSeconds?: number + threshold?: { + absolute?: number + percent: number + display: string + } + quorum?: { + percent: number + display: string + } +} + +export interface YouTooltipProps { + label: string +} + +export const YouTooltip: FC = ({ label }) => ( + +

    + ? +

    +
    +) + +export const ProposalDetailsCard: FC = ({ + proposal, + memberWhenProposalCreated, + walletVote, + proposalExecutionTXHash, +}) => ( +
    +
    +
    +

    + Proposal +

    + +

    + # {proposal.id.toString().padStart(6, '0')} +

    +
    + +
    + +
    +

    + Status +

    + +

    + +

    +
    + +
    + +
    +

    + You +

    + + {!memberWhenProposalCreated ? ( + + ) : walletVote === WalletVote.Yes ? ( +

    + Yes +

    + ) : walletVote === WalletVote.No ? ( +

    + No +

    + ) : walletVote === WalletVote.Abstain ? ( +

    + Abstain +

    + ) : walletVote === WalletVote.Veto ? ( +

    + Veto +

    + ) : walletVote ? ( +

    + Unknown: {walletVote} +

    + ) : proposal.status === 'open' ? ( + + ) : ( + + )} +
    +
    +
    +

    Proposer

    + + + + {proposal.status === 'executed' && !proposalExecutionTXHash ? ( + <> +

    TX

    +

    Loading...

    + + ) : !!proposalExecutionTXHash ? ( + <> + {CHAIN_TXN_URL_PREFIX ? ( + + TX + + + ) : ( +

    TX

    + )} + + + + ) : null} +
    +
    +) + +export const ProposalDetailsVoteStatus: FC = ({ + proposal, + tally, + tokenDecimals, + maxVotingSeconds, + threshold, + quorum, +}) => { + const localeOptions = { maximumSignificantDigits: 3 } + + const yesVotes = Number( + convertMicroDenomToDenomWithDecimals(tally.votes.yes, tokenDecimals) + ) + const noVotes = Number( + convertMicroDenomToDenomWithDecimals(tally.votes.no, tokenDecimals) + ) + const abstainVotes = Number( + convertMicroDenomToDenomWithDecimals(tally.votes.abstain, tokenDecimals) + ) + + const totalWeight = Number( + convertMicroDenomToDenomWithDecimals(tally.total_weight, tokenDecimals) + ) + + const turnoutTotal = yesVotes + noVotes + abstainVotes + const turnoutYesPercent = turnoutTotal ? (yesVotes / turnoutTotal) * 100 : 0 + const turnoutNoPercent = turnoutTotal ? (noVotes / turnoutTotal) * 100 : 0 + const turnoutAbstainPercent = turnoutTotal + ? (abstainVotes / turnoutTotal) * 100 + : 0 + + const turnoutPercent = (turnoutTotal / totalWeight) * 100 + const totalYesPercent = (yesVotes / totalWeight) * 100 + const totalNoPercent = (noVotes / totalWeight) * 100 + const totalAbstainPercent = (abstainVotes / totalWeight) * 100 + + const expiresInSeconds = + proposal.expires && 'at_time' in proposal.expires + ? expirationAtTimeToSecondsFromNow(proposal.expires) + : undefined + + const thresholdReached = + !!threshold && + (quorum ? turnoutYesPercent : totalYesPercent) >= threshold.percent + const quorumMet = !!quorum && turnoutPercent >= quorum.percent + + const helpfulStatusText = + proposal.status === 'open' && threshold && quorum + ? thresholdReached && quorumMet + ? 'If the current vote stands, this proposal will pass.' + : !thresholdReached && quorumMet + ? "If the current vote stands, this proposal will fail because insufficient 'Yes' votes have been cast." + : thresholdReached && !quorumMet + ? 'If the current vote stands, this proposal will fail due to a lack of voter participation.' + : undefined + : undefined + return ( +
    + {helpfulStatusText && ( +

    + {helpfulStatusText} +

    + )} + + {threshold ? ( + quorum ? ( + <> +

    Ratio of votes

    + +
    + {[ +

    + Yes{' '} + {turnoutYesPercent.toLocaleString(undefined, localeOptions)}% +

    , +

    + No {turnoutNoPercent.toLocaleString(undefined, localeOptions)} + % +

    , + ] + .sort(() => yesVotes - noVotes) + .map((elem, idx) => ( +
    + {elem} +
    + ))} +

    + Abstain{' '} + {turnoutAbstainPercent.toLocaleString(undefined, localeOptions)} + % +

    +
    + +
    + b.value - a.value), + { + value: Number(turnoutAbstainPercent), + // Secondary is dark with 80% opacity. + color: 'rgba(var(--dark), 0.8)', + }, + ], + }, + ]} + verticalBars={ + threshold && [ + { + value: threshold.percent, + color: 'rgba(var(--dark), 0.5)', + }, + ] + } + /> +
    + +
    + 90 + ? 'calc(100% - 32px)' + : `calc(${threshold.percent}% - 17px)`, + }} + width="36px" + /> + + +
    +

    + Passing threshold:{' '} + {threshold.display} +

    + +

    + {thresholdReached ? ( + <> + Passing{' '} + + + ) : ( + <> + Failing{' '} + + + )} +

    +
    +
    +
    + +
    +

    + Turnout +

    + +

    + {turnoutPercent.toLocaleString(undefined, localeOptions)}% +

    +
    + +
    + +
    + +
    + 90 + ? 'calc(100% - 32px)' + : `calc(${quorum.percent}% - 17px)`, + }} + width="36px" + /> + + +
    +

    + Quorum: {quorum.display} +

    + +

    + {quorumMet ? ( + <> + Reached{' '} + + + ) : ( + <> + Not met{' '} + + + )} +

    +
    +
    +
    + + ) : ( + <> +

    + Turnout +

    + +
    + {[ +

    + Yes {totalYesPercent.toLocaleString(undefined, localeOptions)} + % +

    , +

    + No {totalNoPercent.toLocaleString(undefined, localeOptions)}% +

    , + ] + .sort(() => yesVotes - noVotes) + .map((elem, idx) => ( +
    + {elem} +
    + ))} +

    + Abstain{' '} + {totalAbstainPercent.toLocaleString(undefined, localeOptions)}% +

    +
    + +
    + b.value - a.value), + { + value: Number(totalAbstainPercent), + // Secondary is dark with 80% opacity. + color: 'rgba(var(--dark), 0.8)', + }, + ], + }, + ]} + verticalBars={[ + { + value: threshold.percent, + color: 'rgba(var(--dark), 0.5)', + }, + ]} + /> +
    + +
    + 90 + ? 'calc(100% - 32px)' + : `calc(${threshold.percent}% - 17px)`, + }} + width="36px" + /> + + +
    +

    + Passing threshold:{' '} + {threshold.display} +

    + +

    + {thresholdReached ? ( + <> + Reached{' '} + + + ) : ( + <> + Not met{' '} + + + )} +

    +
    +
    +
    + + ) + ) : null} + + {proposal.status === 'open' && + expiresInSeconds !== undefined && + expiresInSeconds > 0 && ( + <> +

    + Time left +

    + +

    + {secondsToWdhms(expiresInSeconds, 2)} +

    + + {maxVotingSeconds !== undefined && ( +
    + +
    + )} + + )} + + {threshold?.percent === 50 && yesVotes === noVotes && ( +
    +

    Tie clarification

    + +

    {"'Yes' will win a tie vote."}

    +
    + )} + + {abstainVotes === turnoutTotal && ( +
    +

    All abstain clarification

    + +

    + {/* TODO: Change this to fail wen v1 contracts. */} + When all abstain, a proposal will pass. +

    +
    + )} +
    + ) +} diff --git a/packages/ui/components/ProposalDetails/index.tsx b/packages/ui/components/ProposalDetails/index.tsx index 3f1d28eb6..9bc49cf7a 100644 --- a/packages/ui/components/ProposalDetails/index.tsx +++ b/packages/ui/components/ProposalDetails/index.tsx @@ -1 +1,2 @@ export * from './ProposalDetails' +export * from './ProposalDetailsSidebar' diff --git a/apps/dapp/components/ProposalStatus/ProposalStatus.stories.tsx b/packages/ui/components/ProposalStatus/ProposalStatus.stories.tsx similarity index 100% rename from apps/dapp/components/ProposalStatus/ProposalStatus.stories.tsx rename to packages/ui/components/ProposalStatus/ProposalStatus.stories.tsx diff --git a/apps/dapp/components/ProposalStatus/ProposalStatus.tsx b/packages/ui/components/ProposalStatus/ProposalStatus.tsx similarity index 100% rename from apps/dapp/components/ProposalStatus/ProposalStatus.tsx rename to packages/ui/components/ProposalStatus/ProposalStatus.tsx diff --git a/apps/dapp/components/ProposalStatus/index.ts b/packages/ui/components/ProposalStatus/index.ts similarity index 100% rename from apps/dapp/components/ProposalStatus/index.ts rename to packages/ui/components/ProposalStatus/index.ts diff --git a/apps/dapp/components/StatusIcons/StatusIcons.tsx b/packages/ui/components/StatusIcons/StatusIcons.tsx similarity index 100% rename from apps/dapp/components/StatusIcons/StatusIcons.tsx rename to packages/ui/components/StatusIcons/StatusIcons.tsx diff --git a/apps/dapp/components/StatusIcons/index.ts b/packages/ui/components/StatusIcons/index.ts similarity index 100% rename from apps/dapp/components/StatusIcons/index.ts rename to packages/ui/components/StatusIcons/index.ts diff --git a/packages/ui/components/input/ImageSelector.tsx b/packages/ui/components/input/ImageSelector.tsx index 8798173bc..80c33d2c8 100644 --- a/packages/ui/components/input/ImageSelector.tsx +++ b/packages/ui/components/input/ImageSelector.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' -import { Button, Modal } from '@dao-dao/ui' +import { Button } from '../Button' +import { Modal } from '../Modal' import { PlusIcon, XIcon } from '@heroicons/react/outline' import clsx from 'clsx' import { diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index b8cafcb1d..28ab55bd1 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -10,3 +10,6 @@ export * from './components/input/index' export * from './components/Execute' export * from './components/Vote' export * from './components/WalletConnect' +export * from './components/Progress' +export * from './components/ProposalStatus' +export * from './components/CopyToClipboard' diff --git a/packages/ui/package.json b/packages/ui/package.json index 0f9c2599b..6d3063772 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -20,6 +20,7 @@ "@dao-dao/types": "^0.0.8", "react-markdown": "^8.0.0", "react-hook-form": "^7.20.4", + "clsx": "^1.1.1", "codemirror": "^5.65.0" }, "devDependencies": { diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 945987d41..b702b31a9 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "tsconfig/nextjs.json", "include": ["."], - "exclude": ["dist", "build", "node_modules"] + "exclude": ["dist", "build", "node_modules"], + "moduleResolution": "node" } From 83bfa0180e9830eb7d68ca739d81b5937e278505 Mon Sep 17 00:00:00 2001 From: Zeke Medley Date: Thu, 14 Apr 2022 18:55:01 -0700 Subject: [PATCH 08/16] Split StakingModal into stateless and stateful components. --- apps/dapp/components/BetaWarning.tsx | 9 +- apps/dapp/components/ChainEnableModal.tsx | 2 +- apps/dapp/components/ConnectWalletButton.tsx | 75 ++- apps/dapp/components/ContractCard.tsx | 46 +- apps/dapp/components/ContractView.tsx | 2 +- apps/dapp/components/DaoContractInfo.tsx | 4 +- apps/dapp/components/EmptyContractCard.tsx | 2 +- apps/dapp/components/FormCard.tsx | 2 +- apps/dapp/components/InstallKeplr.tsx | 2 +- apps/dapp/components/MultisigContractInfo.tsx | 4 +- apps/dapp/components/NoKeplrAccountModal.tsx | 2 +- apps/dapp/components/ProposalDetails.tsx | 44 +- .../components/ProposalDetailsSidebar.tsx | 501 +++++++++++++++++- apps/dapp/components/ProposalForm.tsx | 15 +- apps/dapp/components/ProposalList.tsx | 4 +- apps/dapp/components/StakingModal.tsx | 28 +- apps/dapp/components/TemplateSelector.tsx | 4 +- apps/dapp/components/Transfers.tsx | 4 +- .../pages/dao/[contractAddress]/index.tsx | 2 + apps/dapp/pages/dao/create.tsx | 7 + apps/dapp/pages/dao/list.tsx | 7 + apps/dapp/pages/index.tsx | 6 + .../multisig/[contractAddress]/index.tsx | 1 + apps/dapp/pages/multisig/create.tsx | 2 + apps/dapp/pages/multisig/list.tsx | 6 + apps/dapp/pages/starred.tsx | 4 + apps/dapp/selectors/cosm.ts | 1 + apps/dapp/selectors/daos.ts | 1 + apps/dapp/selectors/multisigs.ts | 1 + apps/dapp/services/keplr.tsx | 2 + apps/dapp/templates/changeMembers.tsx | 1 + apps/dapp/templates/configUpdate.tsx | 5 - apps/dapp/templates/mint.tsx | 4 + apps/dapp/templates/removeToken.tsx | 1 + apps/dapp/templates/spend.tsx | 9 + apps/dapp/templates/stake.tsx | 20 +- apps/dapp/util/messagehelpers.ts | 9 + apps/dapp/util/proposal.ts | 1 + packages/ui/components/CopyToClipboard.tsx | 5 +- .../ui/components/CosmosMessageDisplay.tsx | 1 + packages/ui/components/Execute.tsx | 6 +- packages/ui/components/Modal.tsx | 18 +- packages/ui/components/Vote.tsx | 1 + .../ui/components/input/CodeMirrorInput.tsx | 1 + .../ui/components/input/ImageSelector.tsx | 3 +- packages/ui/index.tsx | 14 +- packages/ui/package.json | 7 +- packages/utils/index.tsx | 1 - 48 files changed, 761 insertions(+), 136 deletions(-) diff --git a/apps/dapp/components/BetaWarning.tsx b/apps/dapp/components/BetaWarning.tsx index 87f7c971c..7e3d96d7a 100644 --- a/apps/dapp/components/BetaWarning.tsx +++ b/apps/dapp/components/BetaWarning.tsx @@ -1,7 +1,8 @@ -import { Message } from '@dao-dao/icons' import { Button } from '@dao-dao/ui' import { ChevronRightIcon, XIcon } from '@heroicons/react/outline' +import SvgMessage from 'components/icons/Message' + export function BetaWarningModal({ onAccept }: { onAccept: Function }) { return (
    @@ -46,14 +47,14 @@ export function BetaWarningModal({ onAccept }: { onAccept: Function }) { export function BetaNotice({ onClose }: { onClose: Function }) { return ( -
    +

    DAO DAO is in
    beta!

    + + ) +} + +function DisconnectButton({ onClick }: { onClick: () => void }) { + return ( + + + + ) +} + function WalletConnect() { const [wallet, setWallet] = useRecoilState(connectedWalletAtom) const setInstallWarningVisible = useSetRecoilState(installWarningVisibleAtom) @@ -71,14 +107,37 @@ function WalletConnect() { setWallet, ]) + if (walletAddress) { + return ( +
    +
    + +
    + {walletName} +
    + + {walletBalanceHuman} {chainDenomHuman} + +
    +
    +
    + + +
    +
    + ) + } return ( - +
    + +
    ) } diff --git a/apps/dapp/components/ContractCard.tsx b/apps/dapp/components/ContractCard.tsx index aa1cabaa9..78e818f01 100644 --- a/apps/dapp/components/ContractCard.tsx +++ b/apps/dapp/components/ContractCard.tsx @@ -2,7 +2,6 @@ import { ReactNode } from 'react' import Link from 'next/link' -import { Dao, Pencil, Votes } from '@dao-dao/icons' import { CARD_IMAGES_ENABLED, NATIVE_DECIMALS, @@ -10,9 +9,12 @@ import { convertDenomToHumanReadableDenom, convertMicroDenomToDenomWithDecimals, } from '@dao-dao/utils' -import { StarIcon as StarIconOutline } from '@heroicons/react/outline' +import { PlusIcon, StarIcon as StarIconOutline } from '@heroicons/react/outline' import { StarIcon as StarIconSolid } from '@heroicons/react/solid' +import SvgDao from './icons/Dao' +import SvgPencil from './icons/Pencil' +import SvgVotes from './icons/Votes' import { Logo } from './Logo' function ContractCardBase({ @@ -37,8 +39,8 @@ function ContractCardBase({ return (
    -
    -
    +
    +
    {children} @@ -56,7 +58,7 @@ function ContractCardBase({

    {title}

    -

    +

    {body}

    @@ -150,12 +152,42 @@ export function ContractCard({ export function LoadingContractCard() { return ( -
    -
    +
    +
    +
    +
    + ) +} + +const EmptyStateContractCard = ({ + title, + description, + backgroundUrl, + href, +}: { + title: string + description: string + backgroundUrl: string + href: string +}) => { + return ( + +
    +
    +
    +
    + + {title} +
    +
    {description}
    +
    ) diff --git a/apps/dapp/components/ContractView.tsx b/apps/dapp/components/ContractView.tsx index 222ce6cbd..7095c7da8 100644 --- a/apps/dapp/components/ContractView.tsx +++ b/apps/dapp/components/ContractView.tsx @@ -181,7 +181,7 @@ export function BalanceIcon({ iconURI }: { iconURI?: string }) { return (
    +

    Governance Details