diff --git a/cashtab/package-lock.json b/cashtab/package-lock.json --- a/cashtab/package-lock.json +++ b/cashtab/package-lock.json @@ -1,12 +1,12 @@ { "name": "cashtab", - "version": "2.9.1", + "version": "2.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cashtab", - "version": "2.9.1", + "version": "2.9.2", "dependencies": { "@ant-design/icons": "^5.3.0", "@bitgo/utxo-lib": "^9.33.0", @@ -23,7 +23,7 @@ "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-easy-crop": "^3.5.3", + "react-easy-crop": "^5.0.6", "react-ga": "^3.3.0", "react-router-dom": "^6.22.0", "react-toastify": "^10.0.5", @@ -16543,8 +16543,9 @@ } }, "node_modules/react-easy-crop": { - "version": "3.5.3", - "license": "MIT", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.6.tgz", + "integrity": "sha512-LV8te8NGC72k3l8uAqPAw73D2i9AbRlZqyo1Xz8VetwiMfkSKYgyqE3IFEwf5h+1g7AS1nMxBKk6ZPdhvLw6MQ==", "dependencies": { "normalize-wheel": "^1.0.1", "tslib": "2.0.1" diff --git a/cashtab/package.json b/cashtab/package.json --- a/cashtab/package.json +++ b/cashtab/package.json @@ -1,6 +1,6 @@ { "name": "cashtab", - "version": "2.9.1", + "version": "2.9.2", "private": true, "scripts": { "start": "node scripts/start.js", @@ -40,7 +40,7 @@ "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-easy-crop": "^3.5.3", + "react-easy-crop": "^5.0.6", "react-ga": "^3.3.0", "react-router-dom": "^6.22.0", "react-toastify": "^10.0.5", diff --git a/cashtab/src/components/Common/CropControlModal.js b/cashtab/src/components/Common/CropControlModal.js deleted file mode 100644 --- a/cashtab/src/components/Common/CropControlModal.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2024 The Bitcoin developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import { Card, Modal } from 'antd'; - -const CropModal = styled(Modal)` - .ant-modal-close-x { - font-size: 2px; - } -`; - -export const CropperContainer = styled.div` - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 175px; -`; -export const ControlsContainer = styled.div` - position: absolute; - padding: 12px; - bottom: 0; - left: 50%; - width: 50%; - transform: translateX(-50%); - height: 175px; - display: block; - align-items: center; -`; - -export const CropControlModal = ({ - expand, - renderExpanded = () => null, - onClose, - style, - ...otherProps -}) => { - return ( - - - {renderExpanded()} - - - ); -}; -CropControlModal.propTypes = { - expand: PropTypes.bool, - renderExpanded: PropTypes.func, - onClose: PropTypes.func, - style: PropTypes.object, -}; diff --git a/cashtab/src/components/Common/EnhancedInputs.js b/cashtab/src/components/Common/EnhancedInputs.js --- a/cashtab/src/components/Common/EnhancedInputs.js +++ b/cashtab/src/components/Common/EnhancedInputs.js @@ -6,14 +6,11 @@ import PropTypes from 'prop-types'; import { Form, Input, Select, Checkbox } from 'antd'; import { - ThemedDollarOutlined, ThemedWalletOutlined, ThemedAliasOutlined, } from 'components/Common/CustomIcons'; import styled, { css } from 'styled-components'; -import ScanQRCode from './ScanQRCode'; import { supportedFiatCurrencies } from 'config/cashtabSettings'; -import appConfig from 'config/app'; export const AntdFormCss = css` input[type='number'] { @@ -125,37 +122,6 @@ ${AntdFormCss} `; -export const InputAddonText = styled.span` - width: 100%; - height: 100%; - display: block; - - ${props => - props.disabled - ? ` - cursor: not-allowed; - ` - : `cursor: pointer;`} -`; - -export const InputNumberAddonText = styled.span` - background-color: ${props => props.theme.forms.addonBackground} !important; - border: 1px solid ${props => props.theme.forms.border}; - color: ${props => props.theme.forms.addonForeground} !important; - height: 50px; - line-height: 47px; - - * { - color: ${props => props.theme.forms.addonForeground} !important; - } - ${props => - props.disabled - ? ` - cursor: not-allowed; - ` - : `cursor: pointer;`} -`; - export const CashtabCheckbox = styled(Checkbox)` .ant-checkbox-checked .ant-checkbox-inner { background-color: ${props => props.theme.eCashBlue} !important; @@ -166,174 +132,6 @@ } `; -export const SendXecInput = ({ - onMax, - inputProps, - selectProps, - activeFiatCode, - ...otherProps -}) => { - const { Option } = Select; - const currencies = [ - { - value: appConfig.ticker, - label: appConfig.ticker, - }, - { - value: activeFiatCode ? activeFiatCode : 'USD', - label: activeFiatCode ? activeFiatCode : 'USD', - }, - ]; - const currencyOptions = currencies.map(currency => { - return ( - - ); - }); - - const CurrencySelect = ( - - ); - return ( - - - - event.target.blur()} - step={ - inputProps.dollar === 1 - ? 0.01 - : 1 / 10 ** appConfig.cashDecimals - } - prefix={ - inputProps.dollar === 1 ? ( - - ) : ( - - ) - } - {...inputProps} - /> - {CurrencySelect} - - max - - - - - ); -}; - -SendXecInput.propTypes = { - onMax: PropTypes.func, - inputProps: PropTypes.object, - selectProps: PropTypes.object, - activeFiatCode: PropTypes.string, -}; - -export const DestinationAmount = ({ onMax, inputProps, ...otherProps }) => { - return ( - - - event.target.blur()} - data-testid="token-amount-input" - prefix={ - - } - addonAfter={ - - max - - } - {...inputProps} - /> - - - ); -}; - -DestinationAmount.propTypes = { - onMax: PropTypes.func, - inputProps: PropTypes.object, -}; - -// loadWithCameraOpen prop: if true, load page with camera scanning open -export const DestinationAddressSingle = ({ - onScan, - loadWithCameraOpen, - inputProps, - ...otherProps -}) => { - return ( - - - } - autoComplete="off" - data-testid="destination-address-single" - addonAfter={ - - } - {...inputProps} - /> - - - ); -}; - -DestinationAddressSingle.propTypes = { - onScan: PropTypes.func, - loadWithCameraOpen: PropTypes.bool, - inputProps: PropTypes.object, -}; - export const DestinationAddressSingleWithoutQRScan = ({ inputProps, ...otherProps diff --git a/cashtab/src/components/Common/Inputs.js b/cashtab/src/components/Common/Inputs.js --- a/cashtab/src/components/Common/Inputs.js +++ b/cashtab/src/components/Common/Inputs.js @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import ScanQRCode from './ScanQRCode'; @@ -362,3 +362,161 @@ handleInput: PropTypes.func, handleOnMax: PropTypes.func, }; + +const CashtabSlider = styled.input` + width: 100%; +`; +export const Slider = ({ name, value, min, max, step, handleSlide }) => { + return ( + { + handleSlide(e.target.value); + }} + /> + ); +}; +Slider.propTypes = { + name: PropTypes.string, + placeholder: PropTypes.string, + value: PropTypes.number, + min: PropTypes.number, + max: PropTypes.number, + step: PropTypes.number, + handleSlide: PropTypes.func, +}; + +const InputFile = styled.input` + display: none; +`; +const DragForm = styled.form` + height: 16rem; + width: 28rem; + max-width: 100%; + text-align: center; + position: relative; +`; +const DragLabel = styled.label` + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 9px; + border: 2px dashed + ${props => + props.dragActive ? props.theme.eCashBlue : props.theme.darkBlue}; + background-color: ${props => + props.dragActive ? props.theme.eCashBlue : '#f8fafc'}; +`; +const UploadText = styled.div` + cursor: pointer; + padding: 0.25rem; + font-size: 1rem; + border: none; + background-color: transparent; + &:hover { + text-decoration-line: underline; + } +`; +const DragText = styled.p``; +const DragHolder = styled.div``; +const DragFileElement = styled.div` + position: absolute; + width: 100%; + height: 100%; + border-radius: 1rem; + top: 0px; + right: 0px; + bottom: 0px; + left: 0px; +`; +const TokenIconPreview = styled.img` + width: 242px; + height: 242px; +`; +export const CashtabDragger = ({ name, handleFile, imageUrl }) => { + // drag state + const [dragActive, setDragActive] = useState(false); + + // handle drag events + const handleDrag = e => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === 'dragenter' || e.type === 'dragover') { + // Update component state for drag enter + setDragActive(true); + } else if (e.type === 'dragleave') { + // Update component state for drag exit + setDragActive(false); + } + }; + + const handleDrop = function (e) { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + handleFile(e.dataTransfer.files[0]); + } + }; + + // User adds file by clicking the component + const handleChange = function (e) { + e.preventDefault(); + if (e.target.files && e.target.files[0]) { + handleFile(e.target.files[0]); + } + }; + + return ( + e.preventDefault()} + > + + + {imageUrl ? ( + + ) : ( + + + Drag and drop a png or jpg for your token icon + + or click to upload + + )} + + {dragActive && ( + + )} + + ); +}; +CashtabDragger.propTypes = { + name: PropTypes.string, + handleFile: PropTypes.func, + imageUrl: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), +}; diff --git a/cashtab/src/components/Common/Modal.js b/cashtab/src/components/Common/Modal.js --- a/cashtab/src/components/Common/Modal.js +++ b/cashtab/src/components/Common/Modal.js @@ -33,27 +33,31 @@ width: 100%; color: ${props => props.theme.eCashBlue}; `; + +const MODAL_HEIGHT_DELTA = 68; const ModalBody = styled.div` position: absolute; top: 0; left: 0; width: 100%; - height: 100%; + height: ${props => props.height - MODAL_HEIGHT_DELTA}px; overflow: auto; padding: 6px; word-wrap: break-word; - ::-webkit-scrollbar { + &::-webkit-scrollbar { width: 12px; } - ::-webkit-scrollbar-track { + &::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + background-color: ${props => props.theme.eCashBlue}; border-radius: 10px; + height: 80%; } - ::-webkit-scrollbar-thumb { + &::-webkit-scrollbar-thumb { border-radius: 10px; - color: red; + color: ${props => props.theme.eCashBlue}; -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); } `; @@ -131,8 +135,7 @@ return ( X - - + {typeof title !== 'undefined' && ( {title} )} @@ -140,17 +143,15 @@ {description} )} {children} - {showButtons && ( - - OK - {showCancelButton && ( - - Cancel - - )} - - )} + {showButtons && ( + + OK + {showCancelButton && ( + Cancel + )} + + )} ); }; diff --git a/cashtab/src/components/Common/PrimaryButton.js b/cashtab/src/components/Common/PrimaryButton.js --- a/cashtab/src/components/Common/PrimaryButton.js +++ b/cashtab/src/components/Common/PrimaryButton.js @@ -32,7 +32,7 @@ ${BaseButtonOrLinkCss} `; -const PrimaryButton = styled(CashtabBaseButton)` +const PrimaryButtonOrLinkCss = css` color: ${props => props.disabled ? props.theme.buttons.disabled.color @@ -49,6 +49,13 @@ } `; +const PrimaryButton = styled(CashtabBaseButton)` + ${PrimaryButtonOrLinkCss} +`; +export const PrimaryLink = styled(CashtabBaseLink)` + ${PrimaryButtonOrLinkCss} +`; + const SecondaryButtonOrLinkCss = css` color: ${props => props.disabled diff --git a/cashtab/src/components/Etokens/CreateTokenForm.js b/cashtab/src/components/Etokens/CreateTokenForm.js --- a/cashtab/src/components/Etokens/CreateTokenForm.js +++ b/cashtab/src/components/Etokens/CreateTokenForm.js @@ -4,12 +4,7 @@ import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; -import { AntdFormWrapper } from 'components/Common/EnhancedInputs'; -import { - CropControlModal, - CropperContainer, - ControlsContainer, -} from '../Common/CropControlModal'; +import Modal from 'components/Common/Modal'; import { WalletContext } from 'wallet/context'; import { isValidTokenName, @@ -19,24 +14,11 @@ isValidTokenDocumentUrl, isProbablyNotAScam, } from 'validation'; -import { - PlusSquareOutlined, - UploadOutlined, - PaperClipOutlined, -} from '@ant-design/icons'; +import { PlusSquareOutlined, PaperClipOutlined } from '@ant-design/icons'; import PrimaryButton from 'components/Common/PrimaryButton'; -import { - Form, - Input, - Modal, - Button, - Slider, - Tooltip, - Upload, - Typography, - Switch, -} from 'antd'; -import { TokenParamLabel, FormLabel } from 'components/Common/Atoms'; +import { Input, Slider, CashtabDragger } from 'components/Common/Inputs'; +import CashtabSwitch from 'components/Common/Switch'; +import { TokenParamLabel } from 'components/Common/Atoms'; import Cropper from 'react-easy-crop'; import getCroppedImg from 'components/Etokens/icons/cropImage'; import getRoundImg from 'components/Etokens/icons/roundImage'; @@ -51,30 +33,68 @@ import { hasEnoughToken } from 'wallet'; import { toast } from 'react-toastify'; -const { Dragger } = Upload; +const Form = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; +const EditIcon = styled.div` + cursor: pointer; + color: ${props => props.theme.contrast}; + &:hover { + color: ${props => props.theme.eCashBlue}; + } + word-wrap: break-word; +`; const TokenCreatedLink = styled.a` color: ${props => props.theme.walletBackground}; text-decoration: none; `; -export const CreateTokenCtn = styled.div` - margin-top: 20px; - h3 { - color: ${props => props.theme.contrast}; - } - .ant-form-item { - margin-bottom: 0px; - } - .ant-typography { - color: ${props => props.theme.lightGrey}; - } - div.ant-upload-list.ant-upload-list-text { - color: ${props => props.theme.lightGrey}; - } - svg { - color: ${props => props.theme.lightGrey}; - } +const IconModalForm = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + justify-content: center; +`; +const IconModalRow = styled.div` + display: flex; + width: 100%; + gap: 3px; +`; +const SliderLabel = styled.div` + color: ${props => props.theme.contrast}; +`; +const SliderBox = styled.div` + width: 100%; +`; +const CropperContainer = styled.div` + height: 200px; + position: relative; +`; + +const CreateTokenTitle = styled.h3` + color: ${props => props.theme.contrast}; +`; + +const TokenCreationSummaryTable = styled.div` + color: ${props => props.theme.contrast}; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 3px; +`; +const SummaryRow = styled.div` + display: flex; + gap: 12px; + justify-content: flex-start; + align-items: start; + width: 100%; +`; +const TokenParam = styled.div` + word-break: break-word; `; const CreateTokenForm = () => { @@ -91,9 +111,9 @@ const [tokenIcon, setTokenIcon] = useState(''); const [loading, setLoading] = useState(false); const [fileName, setFileName] = useState(''); - const [tokenIconFileList, setTokenIconFileList] = useState(); + const [tokenIconFileName, setTokenIconFileName] = useState(undefined); const [rawImageUrl, setRawImageUrl] = useState(''); - const [imageUrl, setImageUrl] = useState(''); + const [imageUrl, setImageUrl] = useState(false); const [showCropModal, setShowCropModal] = useState(false); const [roundSelection, setRoundSelection] = useState(true); @@ -150,8 +170,8 @@ try { const reader = new FileReader(); - const width = 128; - const height = 128; + const width = 512; + const height = 512; reader.readAsDataURL(imgFile); reader.addEventListener('load', () => @@ -198,8 +218,6 @@ ctx.canvas.toBlob( blob => { - console.log(imgFile.name); - let fileNameParts = imgFile.name.split('.'); fileNameParts.pop(); let fileNamePng = @@ -226,54 +244,36 @@ }; }; } catch (err) { - console.log(`Error in handleTokenIconImage()`); - console.log(err); + console.error(`Error in handleTokenIconImage()`, err); reject(err); } }); - const beforeTokenIconUpload = file => { + const validateTokenIconUpload = file => { const approvedFileTypes = ['image/png', 'image/jpg', 'image/jpeg']; try { if (!approvedFileTypes.includes(file.type)) { throw new Error('Only jpg or png image files are accepted'); - } else { - setLoading(true); - handleTokenIconImage(file, imageUrl => setImageUrl(imageUrl)); } + setLoading(true); + handleTokenIconImage(file, imageUrl => setImageUrl(imageUrl)); } catch (e) { - console.error('error', e); - - Modal.error({ - title: 'Icon Upload Error', - content: e.message || e.error || JSON.stringify(e), - }); - setTokenIconFileList(undefined); + toast.error( + `Cashtab can only process jpg or png files for token icon uploads.`, + ); + setTokenIconFileName(undefined); setTokenIcon(undefined); setImageUrl(''); return false; } }; - const handleChangeTokenIconUpload = info => { - let list = [...info.fileList]; - - if (info.file.type.split('/')[0] !== 'image') { - setTokenIconFileList(undefined); - setImageUrl(''); - } else { - setTokenIconFileList(list.slice(-1)); - } - }; - - //end eToken icon adds - // Token name const [name, setName] = useState(''); const [newTokenNameIsValid, setNewTokenNameIsValid] = useState(null); const [newTokenNameIsProbablyNotAScam, setNewTokenNameIsProbablyNotAScam] = useState(null); - const [tokenNameError, setTokenNameError] = useState(''); + const [tokenNameError, setTokenNameError] = useState(false); const handleNewTokenNameInput = e => { const { value } = e.target; // validation @@ -284,13 +284,17 @@ setNewTokenNameIsProbablyNotAScam(probablyNotScam); if (!validTokenName) { - setTokenNameError('Validation Error'); + setTokenNameError( + 'Token name must be a valid string between 1 and 68 characters long.', + ); } if (!probablyNotScam) { - setTokenNameError('Blacklisted Error'); + setTokenNameError( + 'Token name must not conflict with existing crypto or fiat', + ); } if (validTokenName && probablyNotScam) { - setTokenNameError(''); + setTokenNameError(false); } setName(value); @@ -303,7 +307,7 @@ newTokenTickerIsProbablyNotAScam, setNewTokenTickerIsProbablyNotAScam, ] = useState(null); - const [tokenTickerError, setTokenTickerError] = useState(''); + const [tokenTickerError, setTokenTickerError] = useState(false); const handleNewTokenTickerInput = e => { const { value } = e.target; // validation @@ -313,30 +317,41 @@ setNewTokenTickerIsProbablyNotAScam(probablyNotScamTicker); if (!validTokenTicker) { - setTokenTickerError('Validation Error'); + setTokenTickerError( + 'Ticker must be a valid string between 1 and 12 characters long', + ); } if (!probablyNotScamTicker) { - setTokenTickerError('Blacklisted Error'); + setTokenTickerError( + 'Token ticker must not conflict with existing crypto or fiat', + ); } if (validTokenTicker && probablyNotScamTicker) { - setTokenTickerError(''); + setTokenTickerError(false); } setTicker(value); }; // New Token Decimals - const [decimals, setDecimals] = useState(0); - const [newTokenDecimalsIsValid, setNewTokenDecimalsIsValid] = - useState(true); + const [decimals, setDecimals] = useState(''); + const [decimalsError, setDecimalsError] = useState(false); const handleNewTokenDecimalsInput = e => { const { value } = e.target; // validation - setNewTokenDecimalsIsValid(isValidTokenDecimals(value)); - // Also validate the supply here if it has not yet been set - if (newTokenInitialQtyIsValid !== null) { - setNewTokenInitialQtyIsValid( - isValidTokenInitialQty(value, decimals), + setDecimalsError( + isValidTokenDecimals(value) + ? false + : 'Token decimals must be an integer between 0 and 9', + ); + + // Also validate the supply here if the form has been touched + // Supply validation may change when decimals changes + if (initialQty !== '') { + setGenesisSupplyError( + isValidTokenInitialQty(initialQty, value) + ? false + : 'Token supply must be greater than 0 and less than 100,000,000,000. Token supply decimal places cannot exceed token decimal places.', ); } @@ -345,24 +360,29 @@ // New Token Initial Quantity const [initialQty, setInitialQty] = useState(''); - const [newTokenInitialQtyIsValid, setNewTokenInitialQtyIsValid] = - useState(null); + const [genesisSupplyError, setGenesisSupplyError] = useState(null); const handleNewTokenInitialQtyInput = e => { const { value } = e.target; // validation - setNewTokenInitialQtyIsValid(isValidTokenInitialQty(value, decimals)); + setGenesisSupplyError( + isValidTokenInitialQty(value, decimals) + ? false + : 'Token supply must be greater than 0 and less than 100,000,000,000. Token supply decimal places cannot exceed token decimal places.', + ); setInitialQty(value); }; // New Token document URL const [url, setUrl] = useState(''); - // Start with this as true, field is not required - const [newTokenDocumentUrlIsValid, setNewTokenDocumentUrlIsValid] = - useState(true); + const [urlError, setUrlError] = useState(false); const handleNewTokenDocumentUrlInput = e => { const { value } = e.target; // validation - setNewTokenDocumentUrlIsValid(isValidTokenDocumentUrl(value)); + setUrlError( + isValidTokenDocumentUrl(value) + ? false + : 'Must be a valid URL. Cannot exceed 68 characters.', + ); setUrl(value); }; @@ -376,9 +396,9 @@ let tokenGenesisDataIsValid = newTokenNameIsValid && newTokenTickerIsValid && - newTokenDecimalsIsValid && - newTokenInitialQtyIsValid && - newTokenDocumentUrlIsValid && + !decimalsError && + !genesisSupplyError && + !urlError && newTokenNameIsProbablyNotAScam && newTokenTickerIsProbablyNotAScam; @@ -505,297 +525,165 @@ }; return ( <> - setShowConfirmCreateToken(false)} - > - Name: {name} -
- Ticker: {ticker} -
- Decimals: {decimals} -
- Supply: {initialQty} -
- Document URL:{' '} - {url === '' ? tokenConfig.newTokenDefaultUrl : url} -
-
- -

Create a Token

- -
setShowConfirmCreateToken(false)} + showCancelButton + height={260} + > + + + Name: + {name} + + + Ticker:{' '} + {ticker} + + + Decimals: + {decimals} + + + Supply: + {initialQty} + + + URL: + + {url === '' + ? tokenConfig.newTokenDefaultUrl + : url} + + + + + )} + Create a Token + + + + + + + + + {typeof tokenIconFileName === 'string' && ( +

{tokenIconFileName.name}

+ )} + + {!loading && tokenIcon && ( + setShowCropModal(true)}> + + {tokenIcon.name} [edit] + + )} + + {showCropModal && ( + showCroppedImage() && onClose()} + height={400} > - Token Name - - handleNewTokenNameInput(e)} - /> - - Ticker - - handleNewTokenTickerInput(e)} - /> - - Decimals - - handleNewTokenDecimalsInput(e)} - /> - - Supply - - handleNewTokenInitialQtyInput(e)} - /> - - Document URL - - - handleNewTokenDocumentUrlInput(e) - } - /> - - Add Image - - - setTimeout(() => { - onSuccess('ok', null); - }, 0) - } - onChange={handleChangeTokenIconUpload} - onRemove={() => false} - fileList={tokenIconFileList} - name="tokenIcon" - style={{ - backgroundColor: '#f4f4f4', - }} - > - {imageUrl ? ( - avatar + + + + + + setRoundSelection(!roundSelection) + } + /> + + + Zoom: + + - ) : ( - <> - {' '} - -

- Click, or drag file to this area to - upload -

-

- Only jpg or png accepted -

- - )} -
- - {!loading && tokenIcon && ( - <> - - - setShowCropModal(true) - } - > - - {tokenIcon.name} - - - setShowCropModal(true) - } - > - Click here to crop or zoom your icon - - {' '} - - )} - - null} - renderExpanded={() => ( - <> - {' '} - - - - - - setRoundSelection(!checked) - } - />{' '} -
- {'Zoom:'} - setZoom(zoom)} - min={1} - max={10} - step={0.1} - /> - {'Rotation:'} - - setRotation(rotation) - } - min={0} - max={360} - step={1} - /> - -
- - )} - onClose={onClose} - /> -
- -
+ + + + Rotation: + + + + + + + )} setShowConfirmCreateToken(true)} @@ -805,7 +693,7 @@  Create eToken -
+ ); }; diff --git a/cashtab/src/components/Etokens/Etokens.js b/cashtab/src/components/Etokens/Etokens.js --- a/cashtab/src/components/Etokens/Etokens.js +++ b/cashtab/src/components/Etokens/Etokens.js @@ -6,11 +6,11 @@ import styled from 'styled-components'; import { WalletContext } from 'wallet/context'; import { LoadingCtn, SidePaddingCtn } from 'components/Common/Atoms'; -import { Link } from 'react-router-dom'; import TokenList from './TokenList'; import { getWalletState } from 'utils/cashMethods'; import appConfig from 'config/app'; import { getUserLocale } from 'helpers'; +import { PrimaryLink } from 'components/Common/PrimaryButton'; const EtokensCtn = styled.div` color: ${props => props.theme.contrast}; @@ -21,22 +21,13 @@ } `; -const CreateToken = styled(Link)` - color: ${props => props.theme.contrast}; - border: 1px solid ${props => props.theme.contrast}; - padding: 8px 15px; - border-radius: 5px; - margin-top: 10px; - margin-bottom: 20px; - display: inline-block; - width: 200px; - :hover { - background: ${props => props.theme.eCashPurple}; - border-color: ${props => props.theme.eCashPurple}; - color: ${props => props.theme.contrast}; - } +const ButtonHolder = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + justify-content: center; `; - const Etokens = () => { const ContextValue = React.useContext(WalletContext); const { loading, cashtabState } = ContextValue; @@ -53,13 +44,15 @@
- - Create eToken - + + + Create eToken + + {tokens && tokens.size > 0 ? (