Skip to content

Commit

Permalink
feat: allow user to enter multisend data in csv format (#1205)
Browse files Browse the repository at this point in the history
* feat: allow user to enter data manually

* chore: ui change
  • Loading branch information
Hemanthghs authored Apr 23, 2024
1 parent b39dc6c commit 4faffd7
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Messages = ({
return (
<div className="flex flex-col h-full">
<div className="flex flex-col h-full space-y-6">
<div className="flex justify-between h-6 items-center">
<div className="flex justify-between h-[34px] items-center">
<div className="text-sm not-italic font-normal leading-[normal]">
Messages
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const MultiTransfer = ({
</div>
<CustomSubmitButton
pendingStatus={txPendingStatus === TxStatus.PENDING}
circularProgressSize={12}
circularProgressSize={20}
buttonStyle="primary-custom-btn w-[144px]"
buttonContent="Send"
/>
Expand Down
251 changes: 193 additions & 58 deletions frontend/src/app/(routes)/transfers/components/MultiTxUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import Image from 'next/image';
import { parseSendMsgsFromContent } from '@/utils/parseMsgs';
import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks';
import { setError } from '@/store/features/common/commonSlice';
import { SEND_TEMPLATE } from '@/utils/constants';
import { MULTISEND_PLACEHOLDER, SEND_TEMPLATE } from '@/utils/constants';
import { ChangeEvent, useEffect, useState } from 'react';
import { TextField } from '@mui/material';
import { multiSendInputFieldStyles } from '../styles';

const MultiTxUpload = ({
chainID,
Expand Down Expand Up @@ -33,12 +36,76 @@ const MultiTxUpload = ({
}
};

const [isFileUpload, setIsFileUpload] = useState(true);
const [inputs, setInputs] = useState('');
const handleInputChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setInputs(e.target.value);
};

const addInputs = () => {
const [parsedTxns, error] = parseSendMsgsFromContent(
address,
'\n' + inputs
);
if (error) {
dispatch(
setError({
type: 'error',
message: error,
})
);
} else {
addMsgs(parsedTxns);
if (parsedTxns?.length) {
setInputs('');
} else {
dispatch(
setError({
type: 'error',
message: 'Invalid input',
})
);
}
}
};

const handleToggle = (value: boolean) => {
addMsgs([]);
setIsFileUpload(value);
};

const getInputRowsCountWithScreenSize = () => {
const divElement = document.getElementById('multisend-inputs');
const height = divElement?.offsetHeight || 500;
const rows = Math.min((height - 46) / 23, 18);
return parseInt(rows.toString());
};

const [inputRows, setInputRows] = useState(getInputRowsCountWithScreenSize());

const resetInputsRowsCount = () => {
setInputRows(getInputRowsCountWithScreenSize());
};

useEffect(() => {
window.addEventListener('resize', resetInputsRowsCount);
return () => {
window.removeEventListener('resize', resetInputsRowsCount);
};
}, []);

useEffect(() => {
if (!isFileUpload) {
resetInputsRowsCount();
}
}, [isFileUpload]);

return (
<div className="space-y-2 flex flex-col flex-1">
<div className="space-y-6 flex flex-col flex-1">
<div className="w-full flex justify-between items-center">
<div className="text-sm not-italic font-normal leading-[normal]">
File Upload
</div>
<ButtonGroup isFileUpload={isFileUpload} handleToggle={handleToggle} />
<div className="flex items-center">
<div className="text-right text-xs not-italic font-normal leading-[normal]">
Download Sample{' '}
Expand All @@ -56,64 +123,132 @@ const MultiTxUpload = ({
</div>
</div>
</div>
<div
className="file-upload-box flex flex-col flex-1"
style={{
backgroundColor: '#1a1731',
height: '228px',
}}
onClick={() => {
const element = document.getElementById('multiTxns_file');
if (element) element.click();
}}
>
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center">
<Image
src="/file-upload-icon.svg"
width={48}
height={48}
alt="Upload file"
/>
<div className="mt-2 mx-12 text-center text-xs not-italic font-normal leading-5">
Upload CSV File, Each line must contain “Recipient Amount”
{isFileUpload ? (
<div
className="file-upload-box flex flex-col flex-1"
style={{
backgroundColor: '#1a1731',
height: '228px',
}}
onClick={() => {
const element = document.getElementById('multiTxns_file');
if (element) element.click();
}}
>
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center">
<Image
src="/file-upload-icon.svg"
width={48}
height={48}
alt="Upload file"
/>
<div className="mt-2 mx-12 text-center text-xs not-italic font-normal leading-5">
Upload CSV File, Each line must contain “Recipient Amount”
</div>
<div />
</div>
<div />
</div>
<input
id="multiTxns_file"
accept="*.csv"
hidden
type="file"
onChange={(e) => {
if (!e?.target?.files) return;
const file = e.target.files[0];
if (!file) {
return;
}
<input
id="multiTxns_file"
accept="*.csv"
hidden
type="file"
onChange={(e) => {
if (!e?.target?.files) return;
const file = e.target.files[0];
if (!file) {
return;
}

const reader = new FileReader();
reader.onload = (e) => {
if (!e.target) return null;
const contents = e.target.result;
onFileContents(contents);
};
reader.onerror = (e) => {
dispatch(
setError({
type: 'error',
message: '' + (e.target?.error || 'Something went wrong. '),
})
);
};
reader.readAsText(file);
e.target.value = '';
}}
/>
const reader = new FileReader();
reader.onload = (e) => {
if (!e.target) return null;
const contents = e.target.result;
onFileContents(contents);
};
reader.onerror = (e) => {
dispatch(
setError({
type: 'error',
message:
'' + (e.target?.error || 'Something went wrong. '),
})
);
};
reader.readAsText(file);
e.target.value = '';
}}
/>
</div>
</div>
</div>
) : (
<div
className="multisend-input-box flex flex-col flex-1 gap-2"
style={{
backgroundColor: '#1a1731',
height: '228px',
justifyContent: 'space-between',
}}
onClick={() => {
const element = document.getElementById('multiTxns_file');
if (element) element.click();
}}
>
<div
id="multisend-inputs"
className="w-full h-full overflow-y-scroll"
>
<TextField
multiline
fullWidth
className="text-white"
onChange={handleInputChange}
value={inputs}
spellCheck={false}
rows={inputRows}
sx={multiSendInputFieldStyles}
placeholder={MULTISEND_PLACEHOLDER}
autoFocus={true}
/>
</div>
<button
onClick={addInputs}
type="button"
className="primary-gradient w-full py-1 rounded-lg"
>
Add
</button>
</div>
)}
</div>
);
};

export default MultiTxUpload;

const ButtonGroup = ({
handleToggle,
isFileUpload,
}: {
isFileUpload: boolean;
handleToggle: (value: boolean) => void;
}) => {
return (
<div className="multisend-toggle-btn-group">
<button
onClick={() => handleToggle(true)}
type="button"
className={`multisend-btn ${isFileUpload ? 'multisend-btn-active' : ''} `}
>
Upload File
</button>
<button
onClick={() => handleToggle(false)}
type="button"
className={`multisend-btn ${!isFileUpload ? 'multisend-btn-active' : ''} `}
>
Enter Manually
</button>
</div>
);
};
19 changes: 19 additions & 0 deletions frontend/src/app/(routes)/transfers/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ export const customDialogPaper = {
background: 'linear-gradient(90deg, #704290 0.11%, #241b61 70.28%)',
};

export const multiSendInputFieldStyles = {
'& .MuiInputBase-input': {
color: 'white',
fontSize: '14px',
fontWeight: 200,
},
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
},
'& .MuiOutlinedInput-root': {
border: '1px solid #ffffff1a',
borderRadius: '16px',
},
'& .Mui-focused': {
border: '1px solid #ffffff4a',
borderRadius: '16px',
},
};

export const swapTextFieldStyles = {
'& .MuiTypography-body1': {
color: 'white',
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/app/(routes)/transfers/transfers.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,22 @@
.drop-down {
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
}

.multisend-input-box {
@apply flex justify-center items-center px-4 py-4 bg-[#FFFFFF1A] min-h-[152px] rounded-3xl w-full cursor-pointer;
}

.multisend-toggle-btn-group {
@apply bg-[#FFFFFF1A] rounded-2xl flex h-[34px];
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25) inset;
}

.multisend-btn {
@apply text-[#FFFFFF80] text-[12px] text-center px-4 min-w-[127px];
}

.multisend-btn-active {
@apply text-white rounded-2xl;
background: linear-gradient(180deg, #4aa29c 0%, #8b3da7 100%);
box-shadow: 0px 4px 4px 0px #100;
}
2 changes: 1 addition & 1 deletion frontend/src/components/CustomButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const CustomSubmitButton = ({
<button disabled={pendingStatus || (isIBC && isMetaMask)}
type="submit" className={buttonStyle}>
{pendingStatus ? (
<CircularProgress size={circularProgressSize} />
<CircularProgress size={circularProgressSize} sx={{color: 'white'}} />
) : (
<>{buttonContent}</>
)}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ export const COIN_GECKO_IDS: Record<string, string> = {
ucmdx: 'cmdx',
};

export const MULTISEND_PLACEHOLDER = `Enter here\n\nExample:\ncosmos1hzq8fmhmd52fdhjprj2uj8ht3q0wxxc29th0l6, 35uatom\ncosmos1h0t3funxenm54ke2z9tfdtgrctex575ufpz3kw, 2506uatom`;

export const voteOptionNumber: VoteOptionNumber = {
yes: 1,
no: 3,
Expand All @@ -302,12 +304,10 @@ export const MULTIOPS_MSG_TYPES = {
vote: 'Vote',
deposit: 'Deposit',
};

export const MULTIOPS_NOTE = `Note: Please ensure to allocate additional gas if the
transaction involves multiple messages, and be sure to
select the appropriate fee option in the signing
wallet.`;

export const MULTIOPS_SAMPLE_FILES = {
delegate:
'https://raw.githubusercontent.com/vitwit/resolute/a6a02cc1b74ee34604e6df35cfce7a46c39980ea/frontend/src/example-files/delegate.csv',
Expand Down

0 comments on commit 4faffd7

Please sign in to comment.