-
Notifications
You must be signed in to change notification settings - Fork 407
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
456 additions
and
117 deletions.
There are no files selected for viewing
77 changes: 30 additions & 47 deletions
77
src/components/CCIP/TutorialBlockchainSelector/ContractAddress.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,58 @@ | ||
import { useState, useCallback } from "react" | ||
import { utils } from "ethers" | ||
import { useStore } from "@nanostores/react" | ||
import { laneStore, setSourceContract, setDestinationContract } from "@stores/lanes" | ||
import type { DeployedContracts } from "@stores/lanes" | ||
import { utils } from "ethers" | ||
import "./ContractAddress.css" | ||
import { useState, useCallback, useEffect, useMemo } from "react" | ||
|
||
interface ContractAddressProps { | ||
type: keyof DeployedContracts | ||
chain: "source" | "destination" | ||
placeholder?: string | ||
placeholder: string | ||
} | ||
|
||
export const ContractAddress = ({ type, chain, placeholder }: ContractAddressProps) => { | ||
const state = useStore(laneStore) | ||
const contracts = useMemo( | ||
() => (chain === "source" ? state.sourceContracts : state.destinationContracts), | ||
[chain, state] | ||
) | ||
const setValue = useMemo(() => (chain === "source" ? setSourceContract : setDestinationContract), [chain]) | ||
|
||
const [inputValue, setInputValue] = useState<string>(contracts[type]?.toString() || "") | ||
const [isDirty, setIsDirty] = useState(false) | ||
const [isUpdating, setIsUpdating] = useState(false) | ||
|
||
useEffect(() => { | ||
const newValue = contracts[type]?.toString() || "" | ||
if (newValue !== inputValue) { | ||
setInputValue(newValue) | ||
} | ||
}, [contracts, type, inputValue]) | ||
const [inputValue, setInputValue] = useState("") | ||
const [isValid, setIsValid] = useState(true) | ||
const setValue = chain === "source" ? setSourceContract : setDestinationContract | ||
|
||
const handleChange = useCallback( | ||
async (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const address = e.target.value | ||
if (isUpdating || inputValue === address) return | ||
(e: React.ChangeEvent<HTMLInputElement>) => { | ||
const value = e.target.value.trim() | ||
setInputValue(value) | ||
|
||
if (value === "") { | ||
// Reset validation and clear store when empty | ||
setIsValid(true) | ||
setValue(type, "") // Clear the store value | ||
return | ||
} | ||
|
||
setInputValue(address) | ||
setIsDirty(true) | ||
// Validate non-empty values | ||
const valid = utils.isAddress(value) | ||
setIsValid(valid) | ||
|
||
if (address === "" || utils.isAddress(address)) { | ||
setIsUpdating(true) | ||
try { | ||
await new Promise((resolve) => setTimeout(resolve, 0)) | ||
setValue(type, address) | ||
} finally { | ||
setIsUpdating(false) | ||
} | ||
// Only update store if it's a valid address | ||
if (valid) { | ||
setValue(type, value) | ||
} else { | ||
// Clear store if value becomes invalid | ||
setValue(type, "") | ||
} | ||
}, | ||
[inputValue, setValue, type, isUpdating] | ||
[type, setValue] | ||
) | ||
|
||
const isValidAddress = useCallback((address: string) => { | ||
return address === "" || utils.isAddress(address) | ||
}, []) | ||
|
||
const showError = isDirty && inputValue !== "" && !isValidAddress(inputValue) | ||
|
||
return ( | ||
<div className="contract-address-container"> | ||
<input | ||
type="string" | ||
spellCheck="false" | ||
autoComplete="off" | ||
type="text" | ||
value={inputValue} | ||
onChange={handleChange} | ||
onBlur={() => setIsDirty(true)} | ||
placeholder={placeholder || `Enter ${type} address`} | ||
className={`contract-address-input ${showError ? "invalid-address" : ""}`} | ||
placeholder={placeholder} | ||
className={`contract-address-input ${!isValid ? "invalid-address" : ""}`} | ||
/> | ||
{showError && <div className="validation-message">⚠️ Please enter a valid Ethereum address</div>} | ||
{!isValid && <span className="validation-message">Please enter a valid Ethereum address</span>} | ||
</div> | ||
) | ||
} |
30 changes: 30 additions & 0 deletions
30
src/components/CCIP/TutorialBlockchainSelector/DeployTokenStep.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
.parameters { | ||
background: var(--color-background-secondary); | ||
border: 1px solid var(--color-border); | ||
border-radius: var(--border-radius); | ||
padding: var(--space-4x); | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--space-3x); | ||
} | ||
|
||
.parametersIntro { | ||
margin-bottom: var(--space-3x); | ||
} | ||
|
||
.parameterHelp { | ||
margin-top: var(--space-4x); | ||
padding-top: var(--space-3x); | ||
border-top: 1px solid var(--color-border); | ||
} | ||
|
||
.helpItem { | ||
display: flex; | ||
gap: var(--space-2x); | ||
margin-bottom: var(--space-2x); | ||
} | ||
|
||
.helpLabel { | ||
font-weight: 600; | ||
color: var(--color-text-primary); | ||
} |
106 changes: 106 additions & 0 deletions
106
src/components/CCIP/TutorialBlockchainSelector/DeployTokenStep.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { useStore } from "@nanostores/react" | ||
import { laneStore } from "@stores/lanes" | ||
import { ContractAddress } from "./ContractAddress" | ||
import { TutorialCard } from "../TutorialSetup/TutorialCard" | ||
import { TutorialStep } from "../TutorialSetup/TutorialStep" | ||
import { NetworkCheck } from "../TutorialSetup/NetworkCheck" | ||
import { SolidityParam } from "../TutorialSetup/SolidityParam" | ||
import styles from "./DeployTokenStep.module.css" | ||
|
||
interface DeployTokenStepProps { | ||
chain: "source" | "destination" | ||
isEnabled: boolean | ||
} | ||
|
||
export const DeployTokenStep = ({ chain }: DeployTokenStepProps) => { | ||
const state = useStore(laneStore) | ||
const network = chain === "source" ? state.sourceNetwork : state.destinationNetwork | ||
const networkInfo = network | ||
? { | ||
name: network.name, | ||
logo: network.logo, | ||
} | ||
: { name: "loading..." } | ||
|
||
const content = ( | ||
<> | ||
<NetworkCheck network={networkInfo} /> | ||
|
||
<ol className={styles.steps}> | ||
<TutorialStep title="Configure Remix"> | ||
<ul> | ||
<li>Open the "Deploy & Run Transactions" tab</li> | ||
<li>Set Environment to "Injected Provider - MetaMask"</li> | ||
<li> | ||
Select <strong>BurnMintERC677</strong> contract | ||
</li> | ||
</ul> | ||
</TutorialStep> | ||
|
||
<TutorialStep title="Set Contract Parameters"> | ||
<div className={styles.parametersIntro}> | ||
<p>Configure your token by setting these required parameters in Remix:</p> | ||
</div> | ||
|
||
<div className={styles.parameters}> | ||
<SolidityParam | ||
name="name" | ||
type="string" | ||
description="The full name of your token that users will see" | ||
example='"My Cross Chain Token"' | ||
/> | ||
<SolidityParam | ||
name="symbol" | ||
type="string" | ||
description="A short ticker symbol for your token (usually 3-4 letters)" | ||
example='"MCCT"' | ||
/> | ||
<SolidityParam | ||
name="decimals" | ||
type="uint8" | ||
description="Number of decimal places your token will support (18 is standard)" | ||
example="18" | ||
/> | ||
<SolidityParam | ||
name="maxSupply" | ||
type="uint256" | ||
description="The maximum amount of tokens that can ever exist (0 means unlimited)" | ||
example="0" | ||
/> | ||
</div> | ||
|
||
<div className={styles.parameterHelp}> | ||
<div className={styles.helpItem}> | ||
<span className={styles.helpLabel}>Parameter Name:</span> | ||
<span>The exact name you'll type in Remix</span> | ||
</div> | ||
<div className={styles.helpItem}> | ||
<span className={styles.helpLabel}>Type:</span> | ||
<span>The Solidity data type required</span> | ||
</div> | ||
<div className={styles.helpItem}> | ||
<span className={styles.helpLabel}>Example:</span> | ||
<span>Click to copy a valid value</span> | ||
</div> | ||
</div> | ||
</TutorialStep> | ||
|
||
<TutorialStep title="Deploy Contract"> | ||
<ul> | ||
<li>Click "Deploy" and confirm in MetaMask</li> | ||
<li>Copy your token address from "Deployed Contracts"</li> | ||
</ul> | ||
<div className={styles.addressInput}> | ||
<ContractAddress type="token" chain={chain} placeholder="Enter deployed token address" /> | ||
</div> | ||
</TutorialStep> | ||
</ol> | ||
</> | ||
) | ||
|
||
return ( | ||
<TutorialCard title="Deploy Token Contract" description="Configure and deploy your token using Remix IDE"> | ||
{content} | ||
</TutorialCard> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.networkCheck { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--space-2x); | ||
padding: var(--space-4x); | ||
margin-bottom: var(--space-4x); | ||
background: var(--color-background-secondary); | ||
border: 1px solid var(--color-border); | ||
border-radius: var(--border-radius); | ||
} | ||
|
||
.networkCheck img { | ||
width: 24px; | ||
height: 24px; | ||
} | ||
|
||
.networkCheck strong { | ||
color: var(--color-text-primary); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import styles from "./NetworkCheck.module.css" | ||
|
||
interface NetworkCheckProps { | ||
network: { | ||
name: string | ||
logo?: string | ||
} | ||
} | ||
|
||
export const NetworkCheck = ({ network }: NetworkCheckProps) => ( | ||
<div className={styles.networkCheck}> | ||
{network?.logo && <img src={network.logo} alt={network.name} width={24} height={24} />} | ||
<span> | ||
Ensure MetaMask is connected to <strong>{network?.name || "loading..."}</strong> | ||
</span> | ||
</div> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.