diff --git a/public/logos/superbridge.jpg b/public/logos/superbridge.jpg new file mode 100644 index 0000000..c1775bb Binary files /dev/null and b/public/logos/superbridge.jpg differ diff --git a/src/app/bridge/page.tsx b/src/app/bridge/page.tsx index 21aebe3..7b0869e 100644 --- a/src/app/bridge/page.tsx +++ b/src/app/bridge/page.tsx @@ -6,6 +6,8 @@ import { SolidButton } from 'src/components/buttons/SolidButton'; import { ChevronIcon } from 'src/components/icons/Chevron'; import { Section } from 'src/components/layout/Section'; import { H1 } from 'src/components/text/headers'; +import { config } from 'src/config/config'; +import { useIsCel2 } from 'src/features/account/hooks'; import PortalLogo from 'src/images/logos/portal-bridge.jpg'; import SquidLogo from 'src/images/logos/squid-router.jpg'; @@ -14,9 +16,17 @@ interface Bridge { operator: string; href: string; logo: any; + cel2Only?: boolean; } const BRIDGES: Bridge[] = [ + { + name: 'Superbridge', + operator: 'Superbridge', + href: `https://superbridge.app/celo${config.isAlfajores ? '-testnet' : ''}`, + logo: '/logos/superbridge.jpg', + cel2Only: true, + }, { name: 'Squid Router', operator: 'Squid', @@ -32,17 +42,18 @@ const BRIDGES: Bridge[] = [ ]; export default function Page() { + const isCel2 = useIsCel2(); return (

Bridge to Celo

- {BRIDGES.map((bridge) => ( + {BRIDGES.filter((x) => (x.cel2Only ? isCel2 : true)).map((bridge) => ( ))} - {/*

+

These bridges are independent, third-party service providers.
Celo assumes no responsibility for their operation. -

*/} +

); } diff --git a/src/app/delegate/[address]/page.tsx b/src/app/delegate/[address]/page.tsx index ffffb12..b0841ac 100644 --- a/src/app/delegate/[address]/page.tsx +++ b/src/app/delegate/[address]/page.tsx @@ -53,7 +53,10 @@ function DelegateeDescription({ delegatee }: { delegatee: Delegatee }) {

{delegatee.name}

- + {`Since ${dateString}`}
diff --git a/src/components/logos/Bug.tsx b/src/components/logos/Bug.tsx new file mode 100644 index 0000000..cd56071 --- /dev/null +++ b/src/components/logos/Bug.tsx @@ -0,0 +1,16 @@ +import { SVGProps, memo } from 'react'; + +function _Bug(props: SVGProps) { + const { fill, ...rest } = props; + return ( + // Font Awesome Free 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. + + + + ); +} + +export const Bug = memo(_Bug); diff --git a/src/components/logos/SocialLogo.tsx b/src/components/logos/SocialLogo.tsx index f01cb75..5c504c5 100644 --- a/src/components/logos/SocialLogo.tsx +++ b/src/components/logos/SocialLogo.tsx @@ -1,5 +1,6 @@ import { SVGProps } from 'react'; import { A_Blank } from 'src/components/buttons/A_Blank'; +import { Bug } from 'src/components/logos/Bug'; import { Discord } from 'src/components/logos/Discord'; import { Github } from 'src/components/logos/Github'; import { Twitter } from 'src/components/logos/Twitter'; @@ -18,6 +19,7 @@ const LOGOS: Record> = { [SocialLinkType.Twitter]: Twitter, [SocialLinkType.Github]: Github, [SocialLinkType.Discord]: Discord, + [SocialLinkType.Bug]: Bug, }; export function SocialLogo({ svgProps, type, className, size = 18 }: Props) { diff --git a/src/components/nav/Footer.tsx b/src/components/nav/Footer.tsx index dc2a8a5..6e8377e 100644 --- a/src/components/nav/Footer.tsx +++ b/src/components/nav/Footer.tsx @@ -12,6 +12,7 @@ export function Footer() { +
diff --git a/src/components/text/CopyInline.tsx b/src/components/text/CopyInline.tsx new file mode 100644 index 0000000..74e03aa --- /dev/null +++ b/src/components/text/CopyInline.tsx @@ -0,0 +1,15 @@ +import { ButtonHTMLAttributes } from 'react'; +import { useCopyHandler } from 'src/utils/clipboard'; + +type Props = ButtonHTMLAttributes & { + text: string; +}; + +export function CopyInline({ text, ...props }: Props) { + const onClick = useCopyHandler(text); + return ( + + ); +} diff --git a/src/components/text/ShortAddress.tsx b/src/components/text/ShortAddress.tsx index 381f17a..50e2245 100644 --- a/src/components/text/ShortAddress.tsx +++ b/src/components/text/ShortAddress.tsx @@ -1,16 +1,11 @@ import { ButtonHTMLAttributes } from 'react'; +import { CopyInline } from 'src/components/text/CopyInline'; import { shortenAddress } from 'src/utils/addresses'; -import { useCopyHandler } from 'src/utils/clipboard'; type Props = ButtonHTMLAttributes & { address: Address; }; export function ShortAddress({ address, ...props }: Props) { - const onClick = useCopyHandler(address); - return ( - - ); + return ; } diff --git a/src/config/links.ts b/src/config/links.ts index d9a5ec9..32a2ebf 100644 --- a/src/config/links.ts +++ b/src/config/links.ts @@ -1,9 +1,11 @@ import { config } from 'src/config/config'; +const github = 'https://github.com/celo-org/celo-mondo'; export const links = { home: 'https://mondo.celo.org', discord: 'https://discord.gg/celo', - github: 'https://github.com/celo-org/celo-mondo', + github, + bug: github + '/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=', twitter: 'https://twitter.com/celo', docs: 'https://docs.celo.org', forum: 'https://forum.celo.org', diff --git a/src/config/types.ts b/src/config/types.ts index 36f8ba3..0441253 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -5,6 +5,7 @@ export enum SocialLinkType { Twitter = 'twitter', Github = 'github', Discord = 'discord', + Bug = 'bug', } export const SocialLinksSchema = z.record(z.nativeEnum(SocialLinkType), z.string().url()); diff --git a/src/features/account/hooks.ts b/src/features/account/hooks.ts index e17450f..75539b0 100644 --- a/src/features/account/hooks.ts +++ b/src/features/account/hooks.ts @@ -1,9 +1,11 @@ import { accountsABI, lockedGoldABI } from '@celo/abis'; +import { useEffect, useState } from 'react'; import { useToastError } from 'src/components/notifications/useToastError'; import { BALANCE_REFRESH_INTERVAL, ZERO_ADDRESS } from 'src/config/consts'; import { Addresses } from 'src/config/contracts'; +import { isCel2 } from 'src/utils/is-cel2'; import { isNullish } from 'src/utils/typeof'; -import { useBalance as _useBalance, useReadContract } from 'wagmi'; +import { useBalance as _useBalance, usePublicClient, useReadContract } from 'wagmi'; export function useBalance(address?: Address) { const { data, isError, isLoading, error, refetch } = _useBalance({ @@ -72,3 +74,15 @@ export function useAccountDetails(address?: Address) { refetch, }; } + +export function useIsCel2() { + const publicClient = usePublicClient(); + const [_isCel2, setIsCel2] = useState(undefined); + useEffect(() => { + if (!publicClient) return; + + void isCel2(publicClient).then((value) => setIsCel2(value)); + }, [publicClient]); + + return _isCel2; +} diff --git a/src/features/delegation/components/DelegatorsTable.tsx b/src/features/delegation/components/DelegatorsTable.tsx index 4f86a6a..47d0737 100644 --- a/src/features/delegation/components/DelegatorsTable.tsx +++ b/src/features/delegation/components/DelegatorsTable.tsx @@ -3,9 +3,9 @@ import { SpinnerWithLabel } from 'src/components/animation/Spinner'; import { sortAndCombineChartData } from 'src/components/charts/chartData'; import { Collapse } from 'src/components/menus/Collapse'; import { formatNumberString } from 'src/components/numbers/Amount'; +import { ShortAddress } from 'src/components/text/ShortAddress'; import { useDelegators } from 'src/features/delegation/hooks/useDelegators'; import { Delegatee } from 'src/features/delegation/types'; -import { shortenAddress } from 'src/utils/addresses'; import { fromWei } from 'src/utils/amount'; import { objKeys } from 'src/utils/objects'; @@ -28,7 +28,7 @@ function DelegatorsTableContent({ delegatee }: { delegatee: Delegatee }) { const tableData = useMemo(() => { if (!delegatorToAmount) return []; const data = objKeys(delegatorToAmount).map((address) => ({ - label: shortenAddress(address), + label: address, value: fromWei(delegatorToAmount[address]), })); return sortAndCombineChartData(data, NUM_TO_SHOW); @@ -51,7 +51,9 @@ function DelegatorsTableContent({ delegatee }: { delegatee: Delegatee }) { {tableData.map((row) => ( - {row.label} + + + {`${formatNumberString(row.value)} CELO`} ))} diff --git a/src/features/governance/components/ProposalCard.tsx b/src/features/governance/components/ProposalCard.tsx index 7e02ad2..3c68f68 100644 --- a/src/features/governance/components/ProposalCard.tsx +++ b/src/features/governance/components/ProposalCard.tsx @@ -112,7 +112,10 @@ export function ProposalBadgeRow({ {showProposer && proposer && ( <>
- + )} {/* Show one of proposer or executedTimeValue but not both, too crowded */} diff --git a/src/features/governance/components/ProposalVotersTable.tsx b/src/features/governance/components/ProposalVotersTable.tsx index 2751ca7..b551e97 100644 --- a/src/features/governance/components/ProposalVotersTable.tsx +++ b/src/features/governance/components/ProposalVotersTable.tsx @@ -3,6 +3,7 @@ import { SpinnerWithLabel } from 'src/components/animation/Spinner'; import { ChartDataItem, sortAndCombineChartData } from 'src/components/charts/chartData'; import { Collapse } from 'src/components/menus/Collapse'; import { formatNumberString } from 'src/components/numbers/Amount'; +import { CopyInline } from 'src/components/text/CopyInline'; import { MergedProposalData } from 'src/features/governance/hooks/useGovernanceProposals'; import { useProposalVoters } from 'src/features/governance/hooks/useProposalVoters'; import { ProposalStage, VoteType } from 'src/features/governance/types'; @@ -17,19 +18,28 @@ import { toTitleCase } from 'src/utils/strings'; const NUM_TO_SHOW = 20; export function ProposalVotersTable({ propData }: { propData: MergedProposalData }) { + const votersData = useProposalVoters(propData.id); return ( Voters} + button={ +

+ Voters +

+ } buttonClasses="w-full" defaultOpen={propData.stage >= ProposalStage.Execution} > - +
); } -function VoterTableContent({ propData }: { propData: MergedProposalData }) { - const { isLoading, voters, totals } = useProposalVoters(propData.id); +function VoterTableContent({ + propData, +}: { + propData: MergedProposalData & { votersData: ReturnType }; +}) { + const { isLoading, voters, totals } = propData.votersData; const { addressToGroup } = useValidatorGroups(); const tableData = useMemo(() => { @@ -82,7 +92,11 @@ function VoterTableContent({ propData }: { propData: MergedProposalData }) { {tableData.map((row) => ( - {row.label} + + + {toTitleCase(row.type)}
@@ -98,3 +112,9 @@ function VoterTableContent({ propData }: { propData: MergedProposalData }) { ); } + +function VotersCount({ isLoading, voters }: ReturnType) { + if (isLoading) return null; + + return {Object.keys(voters!).length}; +} diff --git a/src/features/locking/LockForm.tsx b/src/features/locking/LockForm.tsx index cf130cd..461a497 100644 --- a/src/features/locking/LockForm.tsx +++ b/src/features/locking/LockForm.tsx @@ -82,6 +82,9 @@ export function LockForm({ return validateForm(values, lockedBalances, walletBalance, stakeBalances, isVoting); }; + const shouldShowWithdrawalTip = lockedBalances?.pendingFree === 0n && lockedBalances?.locked > 0n; + const isUnstaking = lockedBalances && lockedBalances.pendingBlocked > 0n; + return ( initialValues={{ @@ -102,6 +105,20 @@ export function LockForm({ Lock CELO to begin. )} + {values.action === LockActionType.Withdraw && shouldShowWithdrawalTip && ( + + You currently have no available unlocked CELO. Unlocking takes 3 days.{' '} + {isUnstaking && ( + <> +
+ + You are currently unlocking {fromWeiRounded(lockedBalances.pendingBlocked)}{' '} + CELO. + + + )} +
+ )} (
{label}
-
{value}
+
{value}
))}
diff --git a/tailwind.config.js b/tailwind.config.js index e4a5da9..8ac90a6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -39,7 +39,7 @@ module.exports = { fontFamily: { sans: ['var(--font-inter)', 'sans-serif'], serif: ['var(--font-alpina)', 'serif'], - mono: ['Courier New', 'monospace'], + mono: ['monospace'], }, fontSize: { xs: '0.725rem',