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

UI Revamp #18

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
467 changes: 458 additions & 9 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.17",
"@heroicons/react": "^2.0.18",
"@solana/wallet-adapter-base": "^0.9.20",
"@solana/wallet-adapter-react": "^0.15.28",
"@solana/wallet-adapter-react-ui": "^0.9.27",
Expand All @@ -21,6 +21,7 @@
"ethers": "5.5.4",
"next": "^13.3.1",
"react": "^18.2.0",
"react-content-loader": "^6.2.1",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^2.1.0"
},
Expand Down
247 changes: 247 additions & 0 deletions pages/_components/AddNetworkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import { CheckCircleIcon, ExclamationCircleIcon } from '@heroicons/react/20/solid'
import React, { useEffect, useState } from 'react'
import Skeleton from 'react-content-loader'
import Image from 'next/image'

interface Chain {
icon_urls: any
chain_id: string
chain_name: string
rpc_urls: string[]
block_explorer_urls: string[]
native_currency_name: string
native_currency_decimals: number
native_currency_symbol: string
}

interface AddNetworkButtonProps {
children: React.ReactNode
setIsConnected: (connected: boolean) => void
onRpcUrlChanged: (rpcUrl: string | null) => void
}

const fetchChains = async (): Promise<Chain[]> => {
const response = await fetch('https://api.chains.eclipse.builders/evm_chains')
const data = await response.json()
return data
}

const isValidUrl = (url: string) => {
try {
new URL(url)
return true
} catch {
return false
}
}

const sanitizeUrl = (url: string) => {
const cleanedUrl = url.replace(/\s+/g, '')
return isValidUrl(cleanedUrl) ? cleanedUrl : null
}

export const AddNetworkButton: React.FC<AddNetworkButtonProps> = ({ children, setIsConnected, onRpcUrlChanged }) => {
const [chains, setChains] = useState<Chain[]>([])
const [selectedChain, setSelectedChain] = useState<Chain | null>(null)
const [isConnected, setConnected] = useState(false)
const [userSelectedChain, setUserSelectedChain] = useState<Chain | null>(null)
const [isLoading, setIsLoading] = useState(false)

useEffect(() => {
if (typeof window !== 'undefined' && window.ethereum) {
const handleChainChanged = async () => {
const currentChainId = await window.ethereum.request({
method: 'eth_chainId',
})
const matchedChain = chains.find((chain) => chain.chain_id === currentChainId)
if (matchedChain) {
setSelectedChain(matchedChain)
setConnected(true)
} else {
setSelectedChain(null)
setConnected(false)
}
}

handleChainChanged()

window.ethereum.on('chainChanged', handleChainChanged)

return () => {
window.ethereum.removeListener('chainChanged', handleChainChanged)
}
}
}, [chains, selectedChain, isConnected])

useEffect(() => {
if (typeof window !== 'undefined') {
const checkConnectionStatus = async () => {
if (window.ethereum && selectedChain) {
const currentChainId = await window.ethereum.request({
method: 'eth_chainId',
})
setConnected(currentChainId === selectedChain.chain_id)
}
}

checkConnectionStatus()
}
}, [selectedChain])

useEffect(() => {
const fetchChainsData = async () => {
setIsLoading(true)
const fetchedChains = await fetchChains()
setChains(fetchedChains)
setIsLoading(false)
}

fetchChainsData()
}, [])

useEffect(() => {
if (selectedChain) {
onRpcUrlChanged(selectedChain.rpc_urls[0])
} else {
onRpcUrlChanged(null)
}
}, [selectedChain, onRpcUrlChanged])

const loadingSkeleton = (
<div className="w-full h-full sm:w-auto sm:h-auto md:w-96 md:h-96 lg:w-[600px] lg:h-[900px]">
<Skeleton
speed={3}
width="100%"
height="100%"
viewBox="0 0 300 160"
backgroundColor="#f0f0f0"
foregroundColor="#e0e0e0"
uniqueKey="custom-loader"
>
<rect x="0" y="0" rx="3" ry="3" width="300" height="20" />
<rect x="0" y="30" rx="3" ry="3" width="300" height="20" />
<rect x="0" y="60" rx="3" ry="3" width="300" height="20" />
<rect x="0" y="90" rx="3" ry="3" width="300" height="20" />
<rect x="0" y="120" rx="3" ry="3" width="300" height="20" />
</Skeleton>
</div>
)

const addNetwork = async (chain: Chain) => {
const sanitizedBlockExplorerUrls = selectedChain?.block_explorer_urls.map(sanitizeUrl).filter(Boolean)
if (typeof window !== 'undefined' && window.ethereum) {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: chain.chain_id,
chainName: chain.chain_name,
rpcUrls: chain.rpc_urls,
blockExplorerUrls: sanitizedBlockExplorerUrls,
nativeCurrency: {
name: chain.native_currency_name,
decimals: chain.native_currency_decimals,
symbol: chain.native_currency_symbol,
},
},
],
})

const currentChainId = await window.ethereum.request({
method: 'eth_chainId',
})

if (currentChainId === chain.chain_id) {
setIsConnected(true)
} else {
setIsConnected(false)
}
} catch (error: any) {
console.error('Error adding network:', error)
console.log('Parameters passed to wallet_addEthereumChain:', {
chainId: chain.chain_id,
chainName: chain.chain_name,
rpcUrls: chain.rpc_urls,
nativeCurrency: {
name: chain.native_currency_name,
decimals: chain.native_currency_decimals,
symbol: chain.native_currency_symbol,
},
})
}
} else {
alert('Ethereum provider not found')
}
}


const getStatusTextAndColor = () => {
if (typeof window !== 'undefined' && window.ethereum) {
const currentChainId = window.ethereum.chainId
if (
isConnected &&
selectedChain &&
userSelectedChain &&
selectedChain.chain_id === currentChainId &&
userSelectedChain.chain_id === currentChainId
) {
return {
text: `Connected: ${selectedChain.chain_name}`,
color: 'text-green-500',
icon: <CheckCircleIcon className="w-4 h-4 mr-2" />,
}
}
}
return {
text: 'Not connected',
color: 'text-red-500',
icon: <ExclamationCircleIcon className="w-4 h-4 mr-2" />,
}
}

const { text, color, icon } = getStatusTextAndColor()

return (
<div className="flex flex-col justify-center items-center space-y-4">
{isLoading || chains.length === 0 ? (
loadingSkeleton
) : (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 w-full max-w-screen-xl">
{chains.map((chain) => (
<div
key={chain.chain_id}
className="border-2 border-gray-200 rounded-lg p-4 cursor-pointer hover:border-custom-yellow hover:shadow-lg transition-colors duration-200"
onClick={() => {
setUserSelectedChain(chain)
addNetwork(chain)
}}
>
<div className="flex items-center space-x-5">
{chain.icon_urls && chain.icon_urls[0] && (
<img
src={chain.icon_urls[0]}
alt={chain.chain_name}
className="w-14 h-14 object-cover rounded-full"
/>
)}
<div className="flex flex-col items-start">
<h2 className="font-bold text-lg mb-2 text-gray-700">{chain.chain_name}</h2>
<p className="text-gray-500">{chain.native_currency_symbol}</p>
</div>
</div>
</div>
))}
</div>
<div className={`flex items-center ${color}`}>
{icon}
{text}
</div>
</>
)}
</div>
)
}

export default AddNetworkButton
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ const ConnectWalletButton: React.FC<Props> = ({ onConnected, onConnecting, isCon
}
}

const buttonClass = `inline-flex items-center px-4 py-2 border-2 border-white focus:outline-none focus:ring-2 focus:ring-offset-2 mt-4 mb-2 transition-all duration-300 ease-in ${
const buttonClass = `inline-flex items-center px-6 py-3 border border-transparent rounded-full text-base font-medium shadow-sm transition-all duration-300 ease-in ${
isConnected
? 'bg-white text-gray-700 cursor-not-allowed'
? 'bg-gray-600 text-white cursor-not-allowed'
: walletNotFound
? 'bg-red-500 text-white hover:bg-red-600 focus:ring-red-500'
: 'bg-transparent text-white hover:bg-white hover:text-gray-700 focus:ring-blue-500'
: 'bg-custom-blue text-white hover:bg-custom-blue/70 focus:ring-custom-blue/20'
}`

return (
Expand Down
58 changes: 58 additions & 0 deletions pages/_components/Notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Fragment, useState } from 'react'
import { Transition } from '@headlessui/react'
import { CheckCircleIcon } from '@heroicons/react/24/outline'
import { XMarkIcon } from '@heroicons/react/20/solid'

export default function Notification() {
const [show, setShow] = useState(true)

return (
<>
{/* Global notification live region, render this permanently at the end of the document */}
<div
aria-live="assertive"
className="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6"
>
<div className="flex w-full flex-col items-center space-y-4 sm:items-end">
{/* Notification panel, dynamically insert this into the live region when it needs to be displayed */}
<Transition
show={show}
as={Fragment}
enter="transform ease-out duration-300 transition"
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
<div className="p-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<CheckCircleIcon className="h-6 w-6 text-green-400" aria-hidden="true" />
</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
<p className="text-sm font-medium text-gray-900">Successfully saved!</p>
<p className="mt-1 text-sm text-gray-500">Anyone with a link can now view this file.</p>
</div>
<div className="ml-4 flex flex-shrink-0">
<button
type="button"
className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
setShow(false)
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</Transition>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ const modifyRpcUrl = (url: string) => {

return (
<button
className={`send inline-flex items-center px-4 py-2 border-2 border-white text-sm font-medium ${
className={`send inline-flex items-center px-6 py-3 border border-transparent rounded-full text-base font-medium transition-all duration-300 ease-in ${
buttonStatus === 'idle'
? 'bg-transparent text-white hover:bg-white hover:text-gray-700'
? 'bg-custom-orange text-white hover:bg-white hover:text-gray-700'
: buttonStatus === 'sending'
? 'bg-yellow-500 text-white'
? 'bg-custom-orange/80 text-white'
: buttonStatus === 'success'
? 'bg-white text-gray-700 cursor-not-allowed'
: 'bg-red-500 text-white'
} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 mt-4 mb-2 transition-all duration-300 ease-in`}
} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-custom-orange/80 mt-4 mb-2 transition-all duration-300 ease-in`}
type="submit"
onClick={handleClick}
disabled={buttonStatus !== 'idle'}
Expand Down
Binary file added pages/assets/card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading