diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 552cc673..6dcb69b6 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -11,7 +11,7 @@ - [text](/?id=text) - [hover-text](/?id=hover-text) - [goal-amount](/?id=goal-amount) - - [editable](/?id=editable) + - [can-edit](/?id=can-edit) - [theme](/?id=theme) - [animation](/?id=animation) - [success-text](/?id=success-text) diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md index 96c00dab..daaf1851 100644 --- a/docs/zh-cn/_sidebar.md +++ b/docs/zh-cn/_sidebar.md @@ -10,7 +10,7 @@ - [text](/zh-cn/?id=text) - [hover-text](/zh-cn/?id=hover-text) - [goal-amount](/zh-cn/?id=goal-amount) - - [editable](/zh-cn/?id=editable) + - [can-edit](/zh-cn/?id=can-edit) - [theme](/zh-cn/?id=theme) - [animation](/zh-cn/?id=animation) - [success-text](/zh-cn/?id=success-text) diff --git a/docs/zh-tw/_sidebar.md b/docs/zh-tw/_sidebar.md index dfba7e04..7548467d 100644 --- a/docs/zh-tw/_sidebar.md +++ b/docs/zh-tw/_sidebar.md @@ -10,7 +10,7 @@ - [text](/zh-tw/?id=text) - [hover-text](/zh-tw/?id=hover-text) - [goal-amount](/zh-tw/?id=goal-amount) - - [editable](/zh-tw/?id=editable) + - [can-edit](/zh-tw/?id=can-edit) - [theme](/zh-tw/?id=theme) - [animation](/zh-tw/?id=animation) - [success-text](/zh-tw/?id=success-text) diff --git a/package.json b/package.json index 3d346439..79d4cc3f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "dev": "cd paybutton && yarn dev", "test": "cd react && yarn test", "test:coverage": "cd react && yarn test:coverage", - "dev:refresh": "yarn build:react && yarn dev", "build:react": "cd react && yarn && yarn build", "build:paybutton": "cd paybutton && yarn && yarn build", "start:paybutton": "cd paybutton && yarn && yarn start", diff --git a/paybutton/dev/demo/index.html b/paybutton/dev/demo/index.html index f92140e2..0cad42fe 100644 --- a/paybutton/dev/demo/index.html +++ b/paybutton/dev/demo/index.html @@ -1,5 +1,5 @@ - + @@ -7,19 +7,73 @@ - - -
@@ -77,7 +131,7 @@

-
+
- - - - -
-
-
-
- - -
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- -
- -
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/paybutton/dev/demo/style.css b/paybutton/dev/demo/style.css deleted file mode 100644 index 8a3d022a..00000000 --- a/paybutton/dev/demo/style.css +++ /dev/null @@ -1,130 +0,0 @@ -.navbar { - color: #333333; - display: flex; - justify-content: center; - padding: 10px 20px; -} -.navbar a { - color: #333333; - text-decoration: none; - padding: 10px; -} -.buttons-section { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 20px; - padding: 50px; - align-items: center; -} -.widgets-section { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); - gap: 40px; - padding: 50px; - align-items: center; -} -.btn { - padding: 20px; - display: flex; -} -.btn button { - text-transform: none; - color: #fff; - border: 1px solid rgb(58, 48, 166); - margin: auto !important; - padding: 0.618em 1.618em !important; - min-width: 14em !important; - background: #518efe; - transition: background-position 0.8s ease 0s, color 0.15s ease 0s, - border-color 0.4s ease 0s; - border-radius: 10px !important; -} -.btn button:hover { - cursor: pointer !important; - background: #518efeb7; - color: #101563; -} -.btn.purple button { - border: 2px solid #7a33ff; - background: #9a55ff; -} -.btn.purple button:hover { - background: #ebe0fb; - color: #5805f3; -} - -.toggle { - margin-bottom: 0 !important; -} -.toggle input[type="checkbox"] { - visibility: hidden; -} - -.toggle label { - display: block; - cursor: pointer; - position: relative; - padding-left: 40px; - font-size: 12px; -} - -.toggle label::before, .toggle label::after { - content: ""; - position: absolute; - transition: all 0.3s ease; -} - -.toggle label::before { - width: 34px; - height: 20px; - background-color: #e7e5e5; - border-radius: 10px; - top: 50%; - transform: translateY(-50%); - left: 0; -} - -.toggle label::after { - width: 20px; - height: 20px; - background-color: #c3c2c2; - border-radius: 50%; - top: 50%; - transform: translateY(-50%); - left: 2px; - box-shadow: 0 0 2px rgba(0,0,0,0.4); -} - -.toggle input[type="checkbox"]:checked + label::before { - background-color: #ccc; -} - -.toggle input[type="checkbox"]:checked + label::after { - left: 12px; - background-color: #2186fa ; -} - -form { - width: 300px; -} -.card { - border: 1px solid #ccc; - padding: 20px; - box-shadow: 0 0 5px rgba(0, 0, 0, 0.169); - border-radius: 10px; - min-height: 650px; - min-width: 310px; - height: 100%; - align-self: center; - margin: 10px; -} -.form-input { - margin-bottom: 10px; - display: flex; - flex-direction: column; - min-width: 25px; - font-size: 12px; -} -label { - padding-bottom: 6px; -} \ No newline at end of file diff --git a/react/lib/components/PayButton/PayButton.tsx b/react/lib/components/PayButton/PayButton.tsx index eefacd8d..86801280 100644 --- a/react/lib/components/PayButton/PayButton.tsx +++ b/react/lib/components/PayButton/PayButton.tsx @@ -56,6 +56,7 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { const priceRef = useRef(price); const cryptoAmountRef = useRef(cryptoAmount); + const { to, opReturn, @@ -157,11 +158,13 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { } }, [dialogOpen, props.amount, currency, randomSatoshis]); - const getPrice = useCallback(async () => { - const price = await getFiatPrice(currency, to, apiBaseUrl); - if (price !== null) - setPrice(price); -}, [currency, to, apiBaseUrl]); + const getPrice = useCallback( + async () => { + const price = await getFiatPrice(currency, to, apiBaseUrl) + if (price !== null) setPrice(price) + } + , [currency, to, apiBaseUrl] + ); useEffect(() => { (async () => { diff --git a/react/lib/components/Widget/Widget.tsx b/react/lib/components/Widget/Widget.tsx index 9cdbea1e..2f936c66 100644 --- a/react/lib/components/Widget/Widget.tsx +++ b/react/lib/components/Widget/Widget.tsx @@ -1,5 +1,3 @@ -import React, { useEffect, useMemo, useState } from 'react'; - import { Box, CircularProgress, @@ -9,10 +7,10 @@ import { TextField, Grid, } from '@material-ui/core'; - +import React, { useEffect, useMemo, useState } from 'react'; import copy from 'copy-to-clipboard'; import QRCode, { BaseQRCodeProps } from 'qrcode.react'; -import io from 'socket.io-client'; +import io, { Socket } from 'socket.io-client'; import PencilIcon from '../../assets/edit-pencil'; import config from '../../config.json'; import { Theme, ThemeName, ThemeProvider, useTheme } from '../../themes'; @@ -162,10 +160,10 @@ export const Widget: React.FunctionComponent = props => { const [errorMsg, setErrorMsg] = useState(''); const [goalText, setGoalText] = useState(''); const [goalPercent, setGoalPercent] = useState(0); + const [socket, setSocket] = useState(undefined); const [addressType, setAddressType] = useState( getCurrencyTypeFromAddress(to), ); - const [convertedCurrencyObj, setConvertedCurrencyObj] = useState(); const price = props.price ?? 0; diff --git a/react/lib/components/Widget/WidgetContainer.tsx b/react/lib/components/Widget/WidgetContainer.tsx index 9ee95d13..da590ca5 100644 --- a/react/lib/components/Widget/WidgetContainer.tsx +++ b/react/lib/components/Widget/WidgetContainer.tsx @@ -3,10 +3,24 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import successSound from '../../assets/success.mp3.json'; +import { + getFiatPrice, + Currency, + CurrencyObject, + Transaction, + generatePaymentId, + getCurrencyTypeFromAddress, + isCrypto, + isFiat, + isGreaterThanZero, + isValidCurrency, + resolveNumber, + compareAddresses, + isAddressSupported, + shouldTriggerOnSuccess +} from '../../util'; import Widget, { WidgetProps } from './Widget'; -import { Currency } from 'currency-formatter'; -import { CurrencyObject, Transaction, getCurrencyTypeFromAddress, isValidCurrency, isCrypto, isAddressSupported, compareAddresses, getFiatPrice, isFiat, resolveNumber } from '../../util'; export interface WidgetContainerProps extends Omit { @@ -177,13 +191,21 @@ export const WidgetContainer: React.FunctionComponent = } }, [currency, price]); - useEffect(() => { - newTxs?.map(tx => { - if (tx.confirmed === false && - isLessThanZero(tx.amount)){ - handlePayment(tx); - setNewTxs([]) + if (isFiat(currency) && price === undefined) { + (async () => { + getPrice(); + })() + } + }, [currency, price]); + + const handleNewTransaction = useCallback( + (tx: Transaction) => { + if ( + tx.confirmed === false && + isGreaterThanZero(resolveNumber(tx.amount)) + ) { + handlePayment(tx); } }, [handlePayment], diff --git a/react/lib/tests/util/api-client.test.ts b/react/lib/tests/util/api-client.test.ts index e22e9381..4f5ab008 100644 --- a/react/lib/tests/util/api-client.test.ts +++ b/react/lib/tests/util/api-client.test.ts @@ -7,7 +7,7 @@ global.fetch = jest.fn(); describe('API Client Util Tests', () => { describe('shouldTriggerOnSuccess', () => { - it('should return true when all conditions match', () => { + it('returns true when all conditions match', () => { const transaction:Transaction = { amount: '100.00', paymentId: '123', @@ -20,7 +20,31 @@ describe('API Client Util Tests', () => { }); - it('should return false when the payment ID does not match', () => { + it('returns true when tx paymentId is empty and component paymentId is undefined', () => { + const transaction:Transaction = { + amount: '101.00', + paymentId: '', + message: 'test opReturn message', + hash: '', + timestamp: 0, + address: '' + }; + expect(shouldTriggerOnSuccess(transaction, undefined, '101.00', 'test opReturn message')).toBe(true); + }); + + it('returns true when tx paymentId is empty and component paymentId is empty', () => { + const transaction:Transaction = { + amount: '101.00', + paymentId: '', + message: 'test opReturn message', + hash: '', + timestamp: 0, + address: '' + }; + expect(shouldTriggerOnSuccess(transaction, '', '101.00', 'test opReturn message')).toBe(true); + }); + + it('returns false when the payment ID does not match', () => { const transaction:Transaction = { amount: '100.00', paymentId: '123', @@ -32,7 +56,7 @@ describe('API Client Util Tests', () => { expect(shouldTriggerOnSuccess(transaction, '999', '100.00', 'test opReturn message')).toBe(false); }); - it('should return false when crypto amounts do not match', () => { + it('returns false when crypto amounts do not match', () => { const transaction:Transaction = { amount: '101.00', paymentId: '123', @@ -42,9 +66,9 @@ describe('API Client Util Tests', () => { address: '' }; expect(shouldTriggerOnSuccess(transaction, '123', '100.00', 'test opReturn message')).toBe(false); - }); // todo - check if amount higher than the one requested is also valid + }); - it('should return false when OpReturn data does not match', () => { + it('returns false when OpReturn data does not match', () => { const transaction:Transaction = { amount: '101.00', paymentId: '123', @@ -80,7 +104,7 @@ describe('API Client Util Tests', () => { expect(shouldTriggerOnSuccess(transaction, undefined, '101.00', 'test opReturn')).toBe(true); }); - it('should ignore opReturn validation when opReturn does not exists', () => { + it('should fail when there is a message but opReturn is not enabled', () => { const transaction:Transaction = { amount: '101.00', paymentId: '123', @@ -89,7 +113,7 @@ describe('API Client Util Tests', () => { timestamp: 0, address: '' }; - expect(shouldTriggerOnSuccess(transaction, '123', '101.00', undefined)).toBe(true); + expect(shouldTriggerOnSuccess(transaction, '123', '101.00', undefined)).toBe(false); }); }); }); diff --git a/react/lib/util/api-client.ts b/react/lib/util/api-client.ts index 5b104c2e..f176c7cc 100644 --- a/react/lib/util/api-client.ts +++ b/react/lib/util/api-client.ts @@ -10,6 +10,7 @@ import { Currency, } from './types'; import { isFiat } from './currency'; +import { resolveNumber } from './number'; export const shouldTriggerOnSuccess = ( transaction: Transaction, @@ -30,7 +31,7 @@ export const shouldTriggerOnSuccess = ( const isPaymentIdValid = thisPaymentId ? thisPaymentId === paymentId : true; const isOpReturnValid = thisOpReturn ? formattedOpReturn === formattedTxOpReturn - : true; + : (message === ''); return isAmountValid && isPaymentIdValid && isOpReturnValid; }; diff --git a/react/lib/util/randomizeSats.ts b/react/lib/util/randomizeSats.ts index 68ff9e1e..e9c481f2 100644 --- a/react/lib/util/randomizeSats.ts +++ b/react/lib/util/randomizeSats.ts @@ -1,64 +1,53 @@ import { CryptoCurrency } from "./types"; -const DEFAULT = 3; -const MAX = 4; +const DEFAULT = 3 +const MAX = 4 -export const getNSatoshis = ( - amount: number, - randomSatoshis: boolean | number, - satsPrecision: number, -): number => { - const amountDigits = amount - .toFixed(satsPrecision) - .toString() - .replace('.', '').length; + +export const getNSatoshis = (amount: number, randomSatoshis: boolean | number, satsPrecision: number): number => { + const amountDigits = amount.toFixed(satsPrecision).toString().replace('.', '').length; if (randomSatoshis === true) { - return Math.min(DEFAULT, amountDigits); + return Math.min(DEFAULT, amountDigits) } else if (randomSatoshis === false) { - throw new Error('Trying to randomize satoshis when not allowed.'); + throw new Error("Trying to randomize satoshis when not allowed.") } if (randomSatoshis > MAX) { - randomSatoshis = MAX; + randomSatoshis = MAX } - return Math.min(randomSatoshis, amountDigits); -}; + return Math.min(randomSatoshis, amountDigits) +} -export const randomizeSatoshis = ( - amount: number, - addressType: CryptoCurrency, - randomSatoshis: boolean | number, -): number => { +export const randomizeSatoshis = (amount: number, addressType: CryptoCurrency, randomSatoshis: boolean | number): number => { if (amount === 0) { return 0; } - let nSatoshis: number; - let random: number; - let randomizedAmount: number; - let ret: number; + let nSatoshis: number + let random: number + let randomizedAmount: number + let ret: number switch (addressType) { case 'BCH': - nSatoshis = getNSatoshis(amount, randomSatoshis, 8); - random = Math.floor(Math.random() * 10 ** nSatoshis) * 1e-8; + nSatoshis = getNSatoshis(amount, randomSatoshis, 8) + random = Math.floor(Math.random() * 10 ** nSatoshis) * 1e-8 randomizedAmount = Math.max(0, +amount.toFixed(nSatoshis)) + // zero out the least-significant digits - random; + random ret = +randomizedAmount.toFixed(8); - break; + break case 'XEC': - nSatoshis = getNSatoshis(amount, randomSatoshis, 2); - random = Math.floor(Math.random() * 10 ** nSatoshis) * 1e-2; + nSatoshis = getNSatoshis(amount, randomSatoshis, 2) + random = Math.floor(Math.random() * 10 ** nSatoshis) * 1e-2 - const multiplier = 10 ** (nSatoshis - 2); - randomizedAmount = - Math.max(0, +(Math.floor(amount / multiplier) * multiplier)) + // zero out the least-significant digits - random; + const multiplier = 10 ** (nSatoshis - 2) + randomizedAmount = Math.max(0, +(Math.floor(amount / multiplier) * multiplier)) + // zero out the least-significant digits + random ret = +randomizedAmount.toFixed(2); - break; + break default: - throw new Error(`Invalid currency: ${addressType}`); + throw new Error(`Invalid currency: ${addressType}`) } - return ret; + return ret }; -export default randomizeSatoshis; +export default randomizeSatoshis; \ No newline at end of file diff --git a/react/lib/util/satoshis.ts b/react/lib/util/satoshis.ts index 981941de..aea18b74 100644 --- a/react/lib/util/satoshis.ts +++ b/react/lib/util/satoshis.ts @@ -1,5 +1,9 @@ -import { formatPrice, formatBCH, formatXEC, DECIMALS, Currency, isCrypto, randomizeSatoshis, CurrencyObject, resolveNumber } from './index'; - +import { DECIMALS } from './constants'; +import { isCrypto } from './currency'; +import { formatBCH, formatPrice, formatXEC } from './format'; +import { resolveNumber } from './number'; +import randomizeSatoshis from './randomizeSats'; +import { Currency, CurrencyObject } from './types'; export const getCurrencyObject = ( amount: number,