Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🍫 Add top bar #25

Merged
merged 14 commits into from
Apr 23, 2024
3 changes: 3 additions & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
},
"dependencies": {
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.28.9",
"@web3modal/wagmi": "^4.1.9",
"copy-to-clipboard": "^3.3.3",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"next": "14.1.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Hex } from 'viem'
import { useChainId, useChains } from 'wagmi'

export const useExplorerAddressLink = (address: Hex): string | undefined => {
export const useExplorerAddressLink = (address: Hex | undefined): string | undefined => {
const chains = useChains()
const chainId = useChainId()
const currentChain = chains.find((chain) => chain.id === chainId)

if (!currentChain || !currentChain.blockExplorers?.default) {
if (!currentChain || !currentChain.blockExplorers?.default || !address) {
return undefined
}
return `${currentChain.blockExplorers.default.url}/address/${address}`
Expand Down
5 changes: 2 additions & 3 deletions packages/frontend/src/components/bids/BidsListEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { AddressColumn, BidColumn, PlaceColumn } from '@/components/bids/BidsCol
import styled, { css } from 'styled-components'
import { Bid } from '@/types/bid'
import { Colors } from '@/styles/colors'
import { formatEther, Hex } from 'viem'
import { formatEther } from 'viem'
import { useExplorerAddressLink } from '@/blockchain/hooks/useExplorerAddressLink'
import { shortenEthAddress } from '@/utils/formatters/shortenEthAddress'

interface Props {
bid: Bid
Expand All @@ -29,8 +30,6 @@ export const BidsListEntry = ({ bid, isUser, view = 'full' }: Props) => {
)
}

const shortenEthAddress = (address: Hex) => `${address.substring(0, 6)}......${address.substring(address.length - 4)}`

const BidsEntryRow = styled.div<{ isUser?: boolean }>`
display: grid;
grid-template-columns: 1fr 1fr 2fr;
Expand Down
42 changes: 42 additions & 0 deletions packages/frontend/src/components/buttons/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ReactNode, useState } from 'react'
import copyToClipboard from 'copy-to-clipboard'
import styled from 'styled-components'
import { CopyIcon } from '@/components/icons'
import { Tooltip, TooltipButton } from '@/components/tooltip'

interface CopyButtonProps {
value: string
text?: ReactNode
side?: 'top' | 'right' | 'bottom' | 'left'
size?: number
color?: string
label?: string
}

export const CopyButton = ({ value, text, side, size, color, label }: CopyButtonProps) => {
const [tooltipText, setTooltipText] = useState(text)
const copy = () => {
copyToClipboard(value)
setTooltipText('Copied!')
}

return (
<Tooltip side={side} tooltip={tooltipText} onOpenChange={(value) => !value && setTooltipText(text)}>
<TooltipButton color={color} label={label} onClick={copy}>
<CopyIcon size={size} color={color} />
{label && (
<CopyButtonLabel color={color} onClick={copy}>
{label}
</CopyButtonLabel>
)}
</TooltipButton>
</Tooltip>
)
}

const CopyButtonLabel = styled.span<Pick<CopyButtonProps, 'color' | 'size'>>`
font-weight: 400;
font-size: 14px;
line-height: ${({ size }) => size};
color: ${({ color }) => color};
`
31 changes: 31 additions & 0 deletions packages/frontend/src/components/buttons/RedirectButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ReactNode } from 'react'
import styled from 'styled-components'
import { Tooltip, TooltipLink } from '@/components/tooltip'
import { RedirectIcon } from '@/components/icons'

interface RedirectTooltipProps {
link: string
tooltip: string | ReactNode
side?: 'top' | 'right' | 'bottom' | 'left'
size?: number
color?: string
label?: string
}

export const RedirectButton = ({ link, tooltip, side, size, color, label }: RedirectTooltipProps) => {
return (
<Tooltip side={side} tooltip={tooltip}>
<TooltipLink href={link} label={label}>
<RedirectIcon size={size} color={color} />
{label && <RedirectButtonLabel color={color}>{label}</RedirectButtonLabel>}
</TooltipLink>
</Tooltip>
)
}

const RedirectButtonLabel = styled.span<Pick<RedirectTooltipProps, 'color' | 'size'>>`
font-weight: 400;
font-size: 14px;
line-height: ${({ size }) => size};
color: ${({ color }) => color};
`
50 changes: 50 additions & 0 deletions packages/frontend/src/components/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
import { ReactNode } from 'react'
import styled from 'styled-components'
import { Colors } from '@/styles/colors'

interface TooltipProps {
side?: 'top' | 'right' | 'bottom' | 'left'
tooltip: ReactNode
children: ReactNode
onOpenChange?: (open: boolean) => void
}

export const Tooltip = ({ side = 'bottom', tooltip, children, onOpenChange }: TooltipProps) => {
return (
<TooltipPrimitive.Provider>
<TooltipPrimitive.Root delayDuration={250} onOpenChange={onOpenChange}>
<TooltipPrimitive.Trigger asChild role="tooltip" onMouseDown={(e) => e.preventDefault()}>
<TooltipTriggerContainer>{children}</TooltipTriggerContainer>
</TooltipPrimitive.Trigger>
<TooltipContent side={side} sideOffset={side === 'top' || side === 'bottom' ? 4 : 8}>
{tooltip}
<Arrow width={21} height={10} />
</TooltipContent>
</TooltipPrimitive.Root>
</TooltipPrimitive.Provider>
)
}

const TooltipTriggerContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: fit-content;
height: fit-content;
cursor: help;
`

const TooltipContent = styled(TooltipPrimitive.Content)`
display: flex;
flex-direction: column;
width: fit-content;
padding: 8px 42px;
background-color: ${Colors.Black};
font-size: 12px;
line-height: 14px;
color: ${Colors.White};
`
const Arrow = styled(TooltipPrimitive.Arrow)`
fill: ${Colors.Black};
`
35 changes: 35 additions & 0 deletions packages/frontend/src/components/tooltip/TooltipButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ReactNode, MouseEvent } from 'react'
import styled from 'styled-components'
import { Colors } from '@/styles/colors'
import { Button } from '@/components/buttons'

interface TooltipButtonProps {
color?: string
label?: string
children?: ReactNode
onClick?: (event?: MouseEvent<HTMLButtonElement>) => void
onMouseDown?: (event?: MouseEvent<HTMLButtonElement>) => void
}

export const TooltipButton = ({ color = Colors.Grey, label, children, ...props }: TooltipButtonProps) => {
return (
<TooltipButtonComponent color={color} label={label} {...props}>
{children}
</TooltipButtonComponent>
)
}

const TooltipButtonComponent = styled(Button)<Pick<TooltipButtonProps, 'color' | 'label'>>`
color: ${({ color }) => color};
background-color: ${Colors.White};
width: max-content;
height: 100%;
padding: ${({ label }) => (label ? '4px 12px 4px 4px' : '0')};
transition: all 0.25s ease;

&:hover,
&:focus-visible {
color: ${Colors.BlueDark};
background-color: ${Colors.GreenLight};
}
`
37 changes: 37 additions & 0 deletions packages/frontend/src/components/tooltip/TooltipLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ReactNode } from 'react'
import styled from 'styled-components'
import { Colors } from '@/styles/colors'

interface TooltipLinkProps {
color?: string
children?: ReactNode
href: string
label?: string
}

export const TooltipLink = ({ color = Colors.Grey, href, children, label }: TooltipLinkProps) => {
return (
<TooltipLinkComponent color={color} href={href} target="_blank" rel="noopener noreferrer" label={label}>
{children}
</TooltipLinkComponent>
)
}

const TooltipLinkComponent = styled.a<Pick<TooltipLinkProps, 'color' | 'label'>>`
display: flex;
align-items: center;
width: max-content;
padding: ${({ label }) => (label ? '4px 12px 4px 4px' : '0')};
background-color: transparent;
color: ${({ color }) => color};
outline: none;
user-select: none;
text-decoration: none;
transition: all 0.25s ease;

&:hover,
&:focus-visible {
color: ${Colors.BlueDark};
background-color: ${Colors.GreenLight};
}
`
3 changes: 3 additions & 0 deletions packages/frontend/src/components/tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Tooltip'
export * from './TooltipButton'
export * from './TooltipLink'
26 changes: 26 additions & 0 deletions packages/frontend/src/components/topBar/AccountButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useAccount } from 'wagmi'
import { useState } from 'react'
import { Button } from '@/components/buttons/Button'
import { shortenEthAddress } from '@/utils/formatters/shortenEthAddress'
import { ConnectWalletButton } from '@/components/buttons/ConnectWalletButton'
import { AccountDetailModal } from '@/components/topBar/AccountDetailModal'

export const AccountButton = () => {
const { address } = useAccount()
const [isModalOpen, setIsModalOpen] = useState(false)

return (
<>
{address ? (
<>
<Button view="secondary" onClick={() => setIsModalOpen(!isModalOpen)}>
{shortenEthAddress(address)}
</Button>
{isModalOpen && <AccountDetailModal isShown={isModalOpen} onRequestClose={() => setIsModalOpen(false)} />}
</>
) : (
<ConnectWalletButton view="secondary" />
)}
</>
)
}
96 changes: 96 additions & 0 deletions packages/frontend/src/components/topBar/AccountDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import styled from 'styled-components'
import { useAccount, useDisconnect } from 'wagmi'
import { Colors } from '@/styles/colors'
import { Button } from '@/components/buttons'
import { Modal } from '@/components/topBar/Modal'
import { shortenEthAddress } from '@/utils/formatters/shortenEthAddress'
import { CopyButton } from '@/components/buttons/CopyButton'
import { RedirectButton } from '@/components/buttons/RedirectButton'
import { useExplorerAddressLink } from '@/blockchain/hooks/useExplorerAddressLink'

export interface ModalProps {
isShown: boolean | undefined
onRequestClose: () => void
}

export const AccountDetailModal = ({ isShown, onRequestClose }: ModalProps) => {
const { connector, address } = useAccount()
const explorerLink = useExplorerAddressLink(address)
const { disconnectAsync } = useDisconnect()

const onDisconnect = async () => {
await disconnectAsync()
onRequestClose()
}

return (
<Modal isShown={isShown} onRequestClose={onRequestClose} title="Your account">
<ContentWrapper>
<ContentRow>
<ConnectedWallet>Connected with {connector?.name}</ConnectedWallet>
</ContentRow>
{address && (
<>
<CenteredContentRow>
<AccountAddress>{shortenEthAddress(address)}</AccountAddress>
</CenteredContentRow>
<ContentRow>
<RedirectButton
link={explorerLink ?? ''}
tooltip="View on Arbiscan"
color={Colors.Blue}
label=" View in block explorer"
side="top"
/>
<CopyButton
value={address}
text="Copy account address"
color={Colors.Blue}
label="Copy address"
side="top"
/>
</ContentRow>
</>
)}
</ContentWrapper>
<Button view="secondary" onClick={onDisconnect}>
Disconnect
</Button>
</Modal>
)
}

const AccountAddress = styled.p`
font-family: 'Space Mono', 'Roboto Mono', monospace;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: ${Colors.Black};
`

const ConnectedWallet = styled.p`
font-weight: 400;
font-size: 14px;
line-height: 32px;
color: ${Colors.Grey};
`

const ContentRow = styled.div`
display: flex;
justify-content: center;
align-items: center;
column-gap: 16px;
`

const CenteredContentRow = styled(ContentRow)`
width: 100%;
`

const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
row-gap: 20px;
padding: 20px;
border: 1px solid #e7eaf3;
`
Loading