Skip to content

Commit

Permalink
Create links and make them beautiful (#146)
Browse files Browse the repository at this point in the history
Co-authored-by: Thibaut Sardan <[email protected]>
Co-authored-by: Thibaut Sardan <[email protected]>
  • Loading branch information
3 people authored Sep 19, 2024
1 parent 289b88b commit 228fc48
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 52 deletions.
18 changes: 11 additions & 7 deletions src/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Delegate } from '@/pages/Delegate'
import { toast } from 'sonner'
import { useEffect } from 'react'
import { useNetwork } from './contexts/NetworkContext'
import { RedirectByName } from './components/RedirectByName'

const pages = [
{
path: '',
Expand All @@ -18,6 +20,10 @@ const pages = [
path: '/delegate/:address',
element: <Delegate />,
},
{
path: '/:network/:name',
element: <RedirectByName />,
},
]

export const Content = () => {
Expand All @@ -30,12 +36,10 @@ export const Content = () => {
}, [isLight, lightClientLoaded])

return (
<>
<Routes>
{pages.map(({ path, element }, i) => {
return <Route key={`page_${i}`} path={path} element={element} />
})}
</Routes>
</>
<Routes>
{pages.map(({ path, element }, i) => {
return <Route key={`page_${i}`} path={path} element={element} />
})}
</Routes>
)
}
21 changes: 14 additions & 7 deletions src/components/DelegateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { Card } from '@/components/ui/card'
import { useLocation, useNavigate } from 'react-router-dom'
import { Delegate } from '@/contexts/DelegatesContext'
import { ContentReveal } from './ui/content-reveal'
import { sanitizeString } from '@/lib/utils'
import { useNetwork } from '@/contexts/NetworkContext'
import { cn } from '@/lib/utils'
import { toast } from 'sonner'
import { LinkIcon } from 'lucide-react'
import Markdown from 'react-markdown'
import { H, H2, H3, Hr, P } from './ui/md'
import { AnchorLink } from './ui/anchorLink'
import { useCallback, useMemo } from 'react'

interface Props {
delegate: Delegate
Expand All @@ -23,22 +26,26 @@ export const DelegateCard = ({
hasShareButton,
hasDelegateButton = true,
}: Props) => {
const { network } = useNetwork()
const navigate = useNavigate()
const { search } = useLocation()
const copyLink = useMemo(
() => `${window.location.host}/${network}/${sanitizeString(name)}`,
[name, network],
)
const shouldHideLongDescription =
!longDescription || longDescription === shortDescription

const onDelegate = () => {
const onDelegate = useCallback(() => {
navigate(`/delegate/${address}${search}`)
}
const onCopy = () => {
navigator.clipboard.writeText(
window.location.origin + `/delegate/${address}${search}`,
)
}, [address, navigate, search])

const onCopy = useCallback(() => {
navigator.clipboard.writeText(copyLink)
toast.success('Copied to clipboard', {
duration: 1000,
})
}
}, [copyLink])

const DelegateAvatar: React.FC = () => {
const divStyle: React.CSSProperties = {
Expand Down
56 changes: 56 additions & 0 deletions src/components/RedirectByName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useDelegates } from '@/contexts/DelegatesContext'
import { isSupportedNetwork, useNetwork } from '@/contexts/NetworkContext'
import { Loader2 } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Navigate, useParams } from 'react-router-dom'

export const RedirectByName = () => {
const { name, network } = useParams()
const { getDelegateByName, isLoading: isDelegateLoading } = useDelegates()
const [isDelegateMissing, setIsDelegateMissing] = useState(false)
const [address, setAddress] = useState('')
const { selectNetwork } = useNetwork()

useEffect(() => {
if (!network || !isSupportedNetwork(network)) {
setIsDelegateMissing(true)

return
}

selectNetwork(network)
}, [network, selectNetwork])

useEffect(() => {
if (!name) {
setIsDelegateMissing(true)
return
}

if (isDelegateLoading) return

const delegate = getDelegateByName(name)

if (!delegate) {
setIsDelegateMissing(true)
} else {
setAddress(delegate.address)
}
}, [getDelegateByName, isDelegateLoading, name, network, selectNetwork])

if (address && !!network && isSupportedNetwork(network)) {
return <Navigate to={`/delegate/${address}?network=${network}`} />
}

if (!isDelegateMissing) {
return <Loader2 className="mx-auto h-8 w-8 animate-spin" />
}

return (
<div className="flex h-full w-full items-center justify-center">
Delegate not found for name:{' '}
<span className="mx-2 font-bold">{name}</span> and network:{' '}
<span className="mx-2 font-bold">{network}</span>
</div>
)
}
38 changes: 24 additions & 14 deletions src/contexts/DelegatesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { createContext, useContext, useEffect, useState } from 'react'
import { useNetwork } from './NetworkContext'
import { DelegateListKusama, DelegateListPolkadot } from '@/lib/constants'
import { shuffleArray } from '@/lib/utils'
import { sanitizeString, shuffleArray } from '@/lib/utils'

type DelegatesContextProps = {
children: React.ReactNode | React.ReactNode[]
Expand All @@ -18,35 +18,43 @@ export type Delegate = {
}

export interface IDelegatesContext {
isLoading: boolean
delegates: Delegate[]
getDelegateByAddress: (address: string) => Delegate | undefined
getDelegateByName: (name: string) => Delegate | undefined
}

const DelegatesContext = createContext<IDelegatesContext | undefined>(undefined)

const DelegateContextProvider = ({ children }: DelegatesContextProps) => {
const { network } = useNetwork()
const [delegates, setDelegates] = useState<Delegate[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)

useEffect(() => {
const fetchOpenPRs = async () => {
const response = await (
await fetch(
network === 'polkadot' || network === 'polkadot-lc'
? DelegateListPolkadot
: DelegateListKusama,
)!
).json()
if (!network) return
setIsLoading(true)
const networkToFetch =
network === 'polkadot' || network === 'polkadot-lc'
? DelegateListPolkadot
: DelegateListKusama

const randomized = shuffleArray(response) as Delegate[]
setDelegates(randomized)
}
fetchOpenPRs()
fetch(networkToFetch)
.then(async (response) => {
const result = await response.json()
const randomized = shuffleArray(result) as Delegate[]
setDelegates(randomized)
setIsLoading(false)
})
.catch(console.error)
}, [network])

const getDelegateByAddress = (address: string) =>
delegates.find((d) => d.address === address)

const getDelegateByName = (name: string) =>
delegates.find((d) => sanitizeString(d.name) == name.toLowerCase())

// Votes thingy - pause for now
// useEffect(() => {
// const a = async (delegates: any[]) => {
Expand All @@ -61,7 +69,9 @@ const DelegateContextProvider = ({ children }: DelegatesContextProps) => {
// }, [delegates])

return (
<DelegatesContext.Provider value={{ delegates, getDelegateByAddress }}>
<DelegatesContext.Provider
value={{ delegates, getDelegateByAddress, getDelegateByName, isLoading }}
>
{children}
</DelegatesContext.Provider>
)
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/NetworkContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface INetworkContext {
trackList: TrackList
}

const isSupportedNetwork = (
export const isSupportedNetwork = (
network: string,
): network is SupportedNetworkNames =>
!!descriptorName[network as SupportedNetworkNames]
Expand Down
11 changes: 11 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ export const getLockTimes = async (api: ApiType) => {
)
}

export const sanitizeString = (value: string) =>
value
// remove all emojis
.replace(
/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
'',
)
// replace all strange characters with underscores
.replace(/[\W_]+/g, '_')
.toLowerCase()

// thanks to https://stackoverflow.com/a/2450976/3086912
export const shuffleArray = (arrayToShuffle: unknown[]) => {
const array = [...arrayToShuffle]
Expand Down
65 changes: 42 additions & 23 deletions src/pages/Delegate/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useDelegates } from '@/contexts/DelegatesContext'
import {
type Delegate as DelegateType,
useDelegates,
} from '@/contexts/DelegatesContext'
import { useNetwork } from '@/contexts/NetworkContext'
import { VotingConviction } from '@polkadot-api/descriptors'
import { SetStateAction, useEffect, useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { useAccounts } from '@/contexts/AccountsContext'
import { Slider } from '@/components/ui/slider'
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
import { ArrowLeft } from 'lucide-react'
import { ArrowLeft, Loader2 } from 'lucide-react'
import { msgs } from '@/lib/constants'
import { evalUnits, planckToUnit } from '@polkadot-ui/utils'
import { useLocks } from '@/contexts/LocksContext'
Expand All @@ -23,12 +26,15 @@ import { DelegateCard } from '@/components/DelegateCard'
export const Delegate = () => {
const { api, assetInfo } = useNetwork()
const { address } = useParams()
const { getConvictionLockTimeDisplay } = useLocks()
const { selectedAccount } = useAccounts()
const getDelegateTx = useGetDelegateTx()
const { getConvictionLockTimeDisplay, refreshLocks } = useLocks()
const getSubscriptionCallBack = useGetSigningCallback()
const navigate = useNavigate()
const { search } = useLocation()
const { getDelegateByAddress, isLoading: isLoadingDelegates } = useDelegates()
const [delegate, setDelegate] = useState<DelegateType | undefined>()

const { getDelegateByAddress } = useDelegates()
const [delegate, setDelegate] = useState(
address && getDelegateByAddress(address),
)
const [isAmountDirty, setIsAmountDirty] = useState(false)
const [amount, setAmount] = useState<bigint>(0n)
const [amountVisible, setAmountVisible] = useState<string>('0')
Expand All @@ -37,16 +43,27 @@ export const Delegate = () => {
VotingConviction.Locked1x(),
)
const [convictionNo, setConvictionNo] = useState(1)
const { selectedAccount } = useAccounts()
const [isTxInitiated, setIsTxInitiated] = useState(false)
const getDelegateTx = useGetDelegateTx()
const navigate = useNavigate()
const { search } = useLocation()
const { isExhaustsResources } = useTestTx()
const [isMultiTxDialogOpen, setIsMultiTxDialogOpen] = useState(false)
const [delegateTxs, setDelegateTxs] = useState<DelegateTxs>({} as DelegateTxs)
const { refreshLocks } = useLocks()
const getSubscriptionCallBack = useGetSigningCallback()
const [noDelegateFound, setNoDelegateFound] = useState(false)

useEffect(() => {
// the delegate list may still be loading
if (isLoadingDelegates || delegate) return

const foundDelegate = address && getDelegateByAddress(address)

// if no delegate is found based on the address
// or there's no address passed in the url
if (!foundDelegate || !address) {
setNoDelegateFound(true)
return
}

setDelegate(foundDelegate)
}, [address, delegate, getDelegateByAddress, isLoadingDelegates])

const { display: convictionTimeDisplay, multiplier: convictionMultiplier } =
getConvictionLockTimeDisplay(convictionNo)
Expand Down Expand Up @@ -84,15 +101,6 @@ export const Delegate = () => {
setAmountVisible('0')
}, [api])

useEffect(() => {
if (!address || delegate) return

const res = getDelegateByAddress(address)
setDelegate(res)
}, [address, delegate, getDelegateByAddress])

if (!delegate || !api) return <div>No delegate found</div>

const onChangeAmount = (
e: React.ChangeEvent<HTMLInputElement>,
decimals: number,
Expand All @@ -118,7 +126,8 @@ export const Delegate = () => {
}

const onSign = async () => {
if (!selectedAccount || !amount) return
if (!delegate || !selectedAccount || !amount || !api) return

setIsTxInitiated(true)

const allTracks = await api.constants.Referenda.Tracks()
Expand Down Expand Up @@ -177,6 +186,16 @@ export const Delegate = () => {
.subscribe(subscriptionCallBack)
}

if (noDelegateFound)
return (
<div className="mx-auto">
No delegate found for this address: {address}
</div>
)

if (!delegate || !api)
return <Loader2 className="mx-auto h-8 w-8 animate-spin" />

return (
<main className="m-auto grid w-full max-w-4xl gap-4 p-4 sm:px-6 sm:py-0">
{!api && (
Expand Down

0 comments on commit 228fc48

Please sign in to comment.