Show tables and status
This commit is contained in:
152
src/App.css
152
src/App.css
@@ -36,3 +36,155 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Status display styles */
|
||||||
|
.status-container {
|
||||||
|
margin-top: 30px;
|
||||||
|
width: 80%;
|
||||||
|
margin: 30px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-address {
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details {
|
||||||
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-amount {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
font-size: 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
color: #721c24;
|
||||||
|
max-width: 200px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status colors */
|
||||||
|
.status-pending {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending .status-amount,
|
||||||
|
.status-pending .status-text {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background-color: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success .status-amount,
|
||||||
|
.status-success .status-text {
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed .status-amount,
|
||||||
|
.status-failed .status-text {
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error message styles */
|
||||||
|
.error-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 80%;
|
||||||
|
margin: 20px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload area improvements */
|
||||||
|
.upload-area {
|
||||||
|
border: 2px dashed gray;
|
||||||
|
padding: 50px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px auto 20px;
|
||||||
|
width: 200px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover {
|
||||||
|
border-color: #007bff;
|
||||||
|
background-color: #f8f9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button improvements */
|
||||||
|
.action-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 100px;
|
||||||
|
font-size: 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
background-color: #0069d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|||||||
354
src/App.jsx
354
src/App.jsx
@@ -1,20 +1,35 @@
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import { read, utils } from "xlsx";
|
import { read, utils } from 'xlsx';
|
||||||
import { ethers, formatEther, parseEther } from "ethers";
|
import { ethers, formatEther, parseEther } from 'ethers';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { tokenAbi } from "./tokenAbi";
|
import { tokenAbi } from './tokenAbi';
|
||||||
|
|
||||||
const contractAddress = "0x1ebA64fDe3BF54545c86B9e3bB40c72f50f8D012";
|
const contractAddress = '0x1ebA64fDe3BF54545c86B9e3bB40c72f50f8D012';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const abi = [{"inputs": [{"internalType": "address","name": "token","type": "address"},{"internalType": "address[]","name": "addresses","type": "address[]"},{"internalType": "uint256[]","name": "amounts","type": "uint256[]"},{"internalType": "bool","name": "isToken","type": "bool"}],"name": "batchTransferFrom","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "payable","type": "function"}]
|
const abi = [
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: 'address', name: 'token', type: 'address' },
|
||||||
|
{ internalType: 'address[]', name: 'addresses', type: 'address[]' },
|
||||||
|
{ internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' },
|
||||||
|
{ internalType: 'bool', name: 'isToken', type: 'bool' },
|
||||||
|
],
|
||||||
|
name: 'batchTransferFrom',
|
||||||
|
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
||||||
|
stateMutability: 'payable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const [from, setFrom] = useState("");
|
const [from, setFrom] = useState('');
|
||||||
const [toArray, setToArray] = useState([]);
|
const [toArray, setToArray] = useState([]);
|
||||||
const [amountArray, setAmountArray] = useState([]);
|
const [amountArray, setAmountArray] = useState([]);
|
||||||
|
const [transferStatuses, setTransferStatuses] = useState([]);
|
||||||
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const isToken = true;
|
const isToken = true;
|
||||||
const [provider, setProvider] = useState();
|
const [provider, setProvider] = useState();
|
||||||
const [text, setText] = useState("파일 업로드")
|
const [text, setText] = useState('파일 업로드');
|
||||||
const tokenRef = useRef(null);
|
const tokenRef = useRef(null);
|
||||||
|
|
||||||
const handleFile = (file) => {
|
const handleFile = (file) => {
|
||||||
@@ -28,28 +43,36 @@ function App() {
|
|||||||
const workbook = read(data, { type: 'array' });
|
const workbook = read(data, { type: 'array' });
|
||||||
const sheetName = workbook.SheetNames[0];
|
const sheetName = workbook.SheetNames[0];
|
||||||
const worksheet = workbook.Sheets[sheetName];
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
const jsonData = utils.sheet_to_json(worksheet, {raw:true});
|
const jsonData = utils.sheet_to_json(worksheet, { raw: true });
|
||||||
|
|
||||||
const filteredData = jsonData.filter(row => {
|
const filteredData = jsonData.filter((row) => {
|
||||||
return (
|
return (
|
||||||
typeof row.address === "string" &&
|
typeof row.address === 'string' &&
|
||||||
!row.address.toUpperCase().includes("EX") &&
|
!row.address.toUpperCase().includes('EX') &&
|
||||||
!String(row.amount).toUpperCase().includes("EX")
|
!String(row.amount).toUpperCase().includes('EX')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const addresses = filteredData.map((row) => row.address);
|
const addresses = filteredData.map((row) => row.address.trim());
|
||||||
const amount = filteredData.map((row) => {
|
const amount = filteredData.map((row) => {
|
||||||
// 소수점 2자리까지 반올림 처리
|
// 소수점 2자리까지 반올림 처리
|
||||||
const rounded = Math.round(Number(row.amount) * 100) / 100;
|
const rounded = Math.round(Number(row.amount) * 100) / 100;
|
||||||
return parseEther(rounded.toFixed(2));
|
return parseEther(rounded.toFixed(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize transfer statuses for each address
|
||||||
|
const statuses = addresses.map(() => ({
|
||||||
|
status: 'pending',
|
||||||
|
message: '',
|
||||||
|
}));
|
||||||
|
|
||||||
setToArray(addresses);
|
setToArray(addresses);
|
||||||
setAmountArray(amount);
|
setAmountArray(amount);
|
||||||
|
setTransferStatuses(statuses);
|
||||||
|
setErrorMessage(''); // Clear any previous errors
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error)
|
setErrorMessage(`파일 처리 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
};
|
};
|
||||||
@@ -73,114 +96,134 @@ function App() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if (!window.ethereum) return alert("MetaMask not installed");
|
if (!window.ethereum)
|
||||||
setProvider(new ethers.BrowserProvider(window.ethereum))
|
return setErrorMessage('MetaMask가 설치되지 않았습니다.');
|
||||||
},[])
|
setProvider(new ethers.BrowserProvider(window.ethereum));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const walletProvider = useCallback(async () => {
|
||||||
const walletProvider = useCallback(async ()=>{
|
|
||||||
try {
|
try {
|
||||||
await window.ethereum.request({
|
await window.ethereum.request({
|
||||||
method: 'wallet_switchEthereumChain',
|
method: 'wallet_switchEthereumChain',
|
||||||
params: [{ chainId: '0x38' }] //0x38
|
params: [{ chainId: '0x38' }], //0x38 56
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await window.ethereum.request({
|
await window.ethereum.request({
|
||||||
method: 'wallet_addEthereumChain',
|
method: 'wallet_addEthereumChain',
|
||||||
params: [{
|
params: [
|
||||||
chainId: '0x38', //0x38
|
{
|
||||||
chainName: 'BNB Smart Chain Mainnet',
|
chainId: '0x38', //0x38
|
||||||
rpcUrls: ['https://binance.llamarpc.com'],
|
chainName: 'BNB Smart Chain Mainnet',
|
||||||
nativeCurrency: {
|
rpcUrls: ['https://binance.llamarpc.com'],
|
||||||
name: 'BNB',
|
nativeCurrency: {
|
||||||
symbol: 'BNB',
|
name: 'BNB',
|
||||||
decimals: 18
|
symbol: 'BNB',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
blockExplorerUrls: ['https://bscscan.com/'],
|
||||||
},
|
},
|
||||||
blockExplorerUrls: ['https://bscscan.com/']
|
],
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await provider.send("eth_requestAccounts", []);
|
await provider.send('eth_requestAccounts', []);
|
||||||
|
|
||||||
window.ethereum.on("accountsChanged", (accounts) => {
|
window.ethereum.on('accountsChanged', (accounts) => {
|
||||||
if (accounts.length > 0) {
|
if (accounts.length > 0) {
|
||||||
setFrom(accounts[0]);
|
setFrom(accounts[0]);
|
||||||
} else {
|
} else {
|
||||||
setFrom(null);
|
setFrom(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const signer = provider.getSigner();
|
const signer = provider.getSigner();
|
||||||
const senderAddress = await signer;
|
const senderAddress = await signer;
|
||||||
setFrom(senderAddress.address)
|
setFrom(senderAddress.address);
|
||||||
},[provider])
|
}, [provider]);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if(provider){
|
if (provider) {
|
||||||
walletProvider()
|
walletProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (window.ethereum?.removeListener) {
|
if (window.ethereum?.removeListener) {
|
||||||
window.ethereum.removeListener("accountsChanged", () => {});
|
window.ethereum.removeListener('accountsChanged', () => {});
|
||||||
window.ethereum.removeListener("chainChanged", () => {});
|
window.ethereum.removeListener('chainChanged', () => {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}, [provider, walletProvider]);
|
||||||
},[provider,walletProvider])
|
|
||||||
|
|
||||||
const sendToken = async () => {
|
const sendToken = async () => {
|
||||||
const tokenAddress = tokenRef.current?.value;
|
const tokenAddress = tokenRef.current?.value;
|
||||||
|
|
||||||
let totalAmount = 0;
|
let totalAmount = 0;
|
||||||
amountArray.map((row)=> totalAmount += parseFloat(formatEther(row)));
|
amountArray.map((row) => (totalAmount += parseFloat(formatEther(row))));
|
||||||
|
|
||||||
const signer = await provider.getSigner();
|
const signer = await provider.getSigner();
|
||||||
const contract = new ethers.Contract(contractAddress, abi, signer);
|
const contract = new ethers.Contract(contractAddress, abi, signer);
|
||||||
|
|
||||||
const batchSize = 200;
|
const batchSize = 100;
|
||||||
const loopCount = Math.ceil(toArray.length / batchSize);
|
const loopCount = Math.ceil(toArray.length / batchSize);
|
||||||
|
|
||||||
let status;
|
let status;
|
||||||
|
|
||||||
if(toArray.length !== 0 && amountArray.length !== 0 && toArray.length === amountArray.length)
|
if (
|
||||||
{
|
toArray.length !== 0 &&
|
||||||
|
amountArray.length !== 0 &&
|
||||||
|
toArray.length === amountArray.length
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, signer);
|
const tokenContract = new ethers.Contract(
|
||||||
|
tokenAddress,
|
||||||
|
tokenAbi,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
let balance = isToken ? await tokenContract.balanceOf(from) : await provider.getBalance(from);
|
let balance = isToken
|
||||||
console.log(balance, parseEther(totalAmount.toString()))
|
? await tokenContract.balanceOf(from)
|
||||||
if(balance >= parseEther(totalAmount.toString())){
|
: await provider.getBalance(from);
|
||||||
if(isToken)
|
console.log(balance, parseEther(totalAmount.toString()));
|
||||||
{
|
if (balance >= parseEther(totalAmount.toString())) {
|
||||||
|
if (isToken) {
|
||||||
//token approve
|
//token approve
|
||||||
const tx = await tokenContract.approve(
|
const tx = await tokenContract.approve(
|
||||||
contractAddress,
|
contractAddress,
|
||||||
parseEther(totalAmount.toString())
|
parseEther(totalAmount.toString())
|
||||||
)
|
);
|
||||||
await provider.waitForTransaction(tx.hash);
|
await provider.waitForTransaction(tx.hash);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("잔액부족")
|
throw new Error('잔액부족');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error)
|
console.error('failed to send');
|
||||||
|
setErrorMessage(`전송 준비 중 오류: ${error.message}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("양식이 맞지 않습니다.")
|
setErrorMessage('파일 양식이 맞지 않습니다. 주소와 금액을 확인해주세요.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < loopCount; i++)
|
for (let i = 0; i < loopCount; i++) {
|
||||||
{
|
|
||||||
const start = i * batchSize;
|
const start = i * batchSize;
|
||||||
const end = start + batchSize;
|
const end = start + batchSize;
|
||||||
|
|
||||||
const chunkAddresses = toArray.slice(start, end);
|
const chunkAddresses = toArray.slice(start, end);
|
||||||
const chunkAmounts = amountArray.slice(start, end);
|
const chunkAmounts = amountArray.slice(start, end);
|
||||||
const chunkTotal = chunkAmounts.reduce((acc, amt) => acc + parseFloat(formatEther(amt)), 0);
|
const chunkTotal = chunkAmounts.reduce(
|
||||||
console.log(tokenAddress, chunkAddresses, chunkAmounts, isToken,chunkTotal)
|
(acc, amt) => acc + parseFloat(formatEther(amt)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
tokenAddress,
|
||||||
|
chunkAddresses,
|
||||||
|
chunkAmounts,
|
||||||
|
isToken,
|
||||||
|
chunkTotal
|
||||||
|
);
|
||||||
|
|
||||||
//multisender
|
//multisender
|
||||||
try {
|
try {
|
||||||
const tx = await contract.batchTransferFrom(
|
const tx = await contract.batchTransferFrom(
|
||||||
@@ -188,72 +231,173 @@ function App() {
|
|||||||
chunkAddresses,
|
chunkAddresses,
|
||||||
chunkAmounts,
|
chunkAmounts,
|
||||||
isToken,
|
isToken,
|
||||||
{value : isToken ? 0 : parseEther(chunkTotal.toString())}
|
{ value: isToken ? 0 : parseEther(chunkTotal.toString()) }
|
||||||
);
|
);
|
||||||
|
|
||||||
status = await provider.waitForTransaction(tx.hash);
|
const result = await provider.waitForTransaction(tx.hash);
|
||||||
|
|
||||||
|
// Update statuses for this batch
|
||||||
|
if (result && result.status === 1) {
|
||||||
|
// Success - update all addresses in this batch to success
|
||||||
|
setTransferStatuses((prev) => {
|
||||||
|
const newStatuses = [...prev];
|
||||||
|
for (let j = start; j < end && j < newStatuses.length; j++) {
|
||||||
|
newStatuses[j] = { status: 'success', message: '전송 성공' };
|
||||||
|
}
|
||||||
|
return newStatuses;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Failed - update all addresses in this batch to failed
|
||||||
|
setTransferStatuses((prev) => {
|
||||||
|
const newStatuses = [...prev];
|
||||||
|
for (let j = start; j < end && j < newStatuses.length; j++) {
|
||||||
|
newStatuses[j] = { status: 'failed', message: '트랜잭션 실패' };
|
||||||
|
}
|
||||||
|
return newStatuses;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
status = result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error)
|
// Update all addresses in this batch to failed with error message
|
||||||
|
setTransferStatuses((prev) => {
|
||||||
|
const newStatuses = [...prev];
|
||||||
|
for (let j = start; j < end && j < newStatuses.length; j++) {
|
||||||
|
newStatuses[j] = {
|
||||||
|
status: 'failed',
|
||||||
|
message: `전송 실패: ${error.message || '알 수 없는 오류'}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return newStatuses;
|
||||||
|
});
|
||||||
|
|
||||||
|
setErrorMessage(
|
||||||
|
`배치 전송 실패: ${error.message || '알 수 없는 오류'}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(status){
|
if (status) {
|
||||||
setText("파일 업로드")
|
setText('파일 업로드');
|
||||||
setToArray([]);
|
setToArray([]);
|
||||||
setAmountArray([]);
|
setAmountArray([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<h1>Admin MultiSender</h1>
|
<h1>Admin MultiSender</h1>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width : "600px",
|
width: '600px',
|
||||||
height : "100px",
|
height: '100px',
|
||||||
margin : "0 Auto",
|
margin: '0 Auto',
|
||||||
display : "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <div>
|
{/* <div>
|
||||||
<span style={{fontSize: "18px"}}>토큰 전송 </span>
|
<span style={{fontSize: "18px"}}>토큰 전송 </span>
|
||||||
<input type="checkbox" onChange={handleCheckboxChange} />
|
<input type="checkbox" onChange={handleCheckboxChange} />
|
||||||
</div> */}
|
</div> */}
|
||||||
<div>
|
<div>
|
||||||
<span style={{fontSize: "18px"}}>토큰 컨트랙트 : </span>
|
<span style={{ fontSize: '18px' }}>토큰 컨트랙트 : </span>
|
||||||
<input type="text" ref={tokenRef} placeholder='' style={{fontSize: "18px", marginTop : "10px", width : "450px"}} defaultValue={"0x36b8dE7c6B06B3f170003452114f0B8E6BcFEE18"}/>
|
<input
|
||||||
</div>
|
type="text"
|
||||||
<div>
|
ref={tokenRef}
|
||||||
<span style={{fontSize: "18px"}}>보내는 계좌 : </span>
|
placeholder=""
|
||||||
<input type='text' placeholder={from} style={{fontSize: "18px", marginTop : "10px", width: "450px", border: "none"}} disabled />
|
style={{ fontSize: '18px', marginTop: '10px', width: '450px' }}
|
||||||
</div>
|
defaultValue={'0x36b8dE7c6B06B3f170003452114f0B8E6BcFEE18'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style={{ fontSize: '18px' }}>보내는 계좌 : </span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={from}
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
marginTop: '10px',
|
||||||
|
width: '450px',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="fileInput" style={{ display: "none" }} onChange={handleFileChange} />
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
<label htmlFor="fileInput">
|
<label htmlFor="fileInput">
|
||||||
<div
|
<div
|
||||||
id="upload"
|
id="upload"
|
||||||
|
className="upload-area"
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
style={{
|
|
||||||
// display : toArray.length == 0 ? "block" : "none",
|
|
||||||
border: '2px dashed gray',
|
|
||||||
padding: '50px',
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: '50px',
|
|
||||||
marginBottom: '20px',
|
|
||||||
margin: "0 Auto",
|
|
||||||
width: "200px"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button type='button' onClick={sendToken} style={{marginTop : "20px", padding: "8px", width: "100px", fontSize: "20px"}}>전송</button>
|
<button type="button" className="action-button" onClick={sendToken}>
|
||||||
|
전송
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Display addresses with transfer status */}
|
||||||
|
{toArray.length > 0 && (
|
||||||
|
<div className="status-container">
|
||||||
|
<h3 className="status-header">전송 상태</h3>
|
||||||
|
<div className="status-list">
|
||||||
|
{toArray.map((address, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`status-item status-${
|
||||||
|
transferStatuses[index]?.status || 'pending'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="status-address">{address}</span>
|
||||||
|
<div className="status-details">
|
||||||
|
<div className="status-amount">
|
||||||
|
{formatEther(amountArray[index])} 토큰
|
||||||
|
</div>
|
||||||
|
<div className="status-text">
|
||||||
|
{transferStatuses[index]?.status === 'success'
|
||||||
|
? '✓ 성공'
|
||||||
|
: transferStatuses[index]?.status === 'failed'
|
||||||
|
? '✗ 실패'
|
||||||
|
: '대기 중'}
|
||||||
|
</div>
|
||||||
|
{transferStatuses[index]?.status === 'failed' &&
|
||||||
|
transferStatuses[index]?.message && (
|
||||||
|
<div
|
||||||
|
className="status-error"
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
marginTop: '2px',
|
||||||
|
color: '#721c24',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{transferStatuses[index].message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error message display */}
|
||||||
|
{errorMessage && (
|
||||||
|
<div className="error-container">
|
||||||
|
<span className="error-title">오류:</span>
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ root.render(
|
|||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
reportWebVitals();
|
reportWebVitals(console.log);
|
||||||
|
|||||||
Reference in New Issue
Block a user