From ae18d373b92bece89762002755f29aeb1b0ebb96 Mon Sep 17 00:00:00 2001 From: unclebill Date: Wed, 14 Jul 2021 19:26:49 +0800 Subject: [PATCH 1/8] feat: revamp red packet plugin --- packages/icons/general/Loading.tsx | 55 ++++ packages/icons/general/index.ts | 1 + packages/maskbook/src/UIRoot.tsx | 8 +- .../maskbook/src/_locales/en/messages.json | 4 +- .../maskbook/src/_locales/ja/messages.json | 4 +- .../maskbook/src/_locales/ko/messages.json | 4 +- .../maskbook/src/_locales/zh/messages.json | 4 +- .../RedPacket/SNSAdaptor/RedPacket.tsx | 269 ++++++++++-------- .../RedPacket/SNSAdaptor/RedPacketDialog.tsx | 13 +- .../RedPacket/SNSAdaptor/RedPacketForm.tsx | 60 +++- .../plugins/RedPacket/SNSAdaptor/cover.png | Bin 0 -> 167393 bytes .../hooks/useAvailabilityComputed.ts | 2 +- ...reateCallback.ts => useCreateCallback.tsx} | 62 +++- .../SNSAdaptor/hooks/useTransactionFee.ts | 7 + .../twitter.com/injection/PostDialogIcon.tsx | 6 +- .../maskbook/src/web3/UI/TxFeeEstimation.tsx | 4 +- .../theme/src/Components/Snackbar/index.tsx | 159 ++++++++++- tsconfig.json | 2 +- 18 files changed, 487 insertions(+), 177 deletions(-) create mode 100644 packages/icons/general/Loading.tsx create mode 100644 packages/maskbook/src/plugins/RedPacket/SNSAdaptor/cover.png rename packages/maskbook/src/plugins/RedPacket/SNSAdaptor/hooks/{useCreateCallback.ts => useCreateCallback.tsx} (75%) create mode 100644 packages/maskbook/src/plugins/RedPacket/SNSAdaptor/hooks/useTransactionFee.ts diff --git a/packages/icons/general/Loading.tsx b/packages/icons/general/Loading.tsx new file mode 100644 index 000000000000..5af5816d139c --- /dev/null +++ b/packages/icons/general/Loading.tsx @@ -0,0 +1,55 @@ +import { createIcon } from '../utils' +export const Loading = createIcon( + 'Loading', + <> + + + + + + + + + , + '0 0 18 18', +) diff --git a/packages/icons/general/index.ts b/packages/icons/general/index.ts index fbc29cb1f2c8..4965c5674e94 100644 --- a/packages/icons/general/index.ts +++ b/packages/icons/general/index.ts @@ -26,3 +26,4 @@ export * from './LinkOut' export * from './SignUpAccount' export * from './SignInAccount' export * from './Restore' +export * from './Loading' diff --git a/packages/maskbook/src/UIRoot.tsx b/packages/maskbook/src/UIRoot.tsx index 6051ccb54e5e..5bf5c9846996 100644 --- a/packages/maskbook/src/UIRoot.tsx +++ b/packages/maskbook/src/UIRoot.tsx @@ -2,7 +2,7 @@ import { I18nextProvider } from 'react-i18next' import { StylesProvider } from '@material-ui/styles' import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@material-ui/core' import { Web3Provider } from '@masknet/web3-shared' -import { SnackbarProvider } from '@masknet/theme' +import { CustomSnackbarProvider } from '@masknet/theme' import { ErrorBoundary, ErrorBoundaryBuildInfoContext } from '@masknet/shared' import i18nNextInstance from './utils/i18n-next' import { useClassicMaskTheme } from './utils/theme' @@ -15,9 +15,11 @@ export function MaskUIRootWithinShadow(JSX: JSX.Element) { - + {JSX} - + diff --git a/packages/maskbook/src/_locales/en/messages.json b/packages/maskbook/src/_locales/en/messages.json index 7e78f13ac2e2..83a462c019ee 100644 --- a/packages/maskbook/src/_locales/en/messages.json +++ b/packages/maskbook/src/_locales/en/messages.json @@ -373,14 +373,14 @@ "plugin_wallet_fail_to_sign": "Failed to sign password.", "plugin_wallet_cancel_sign": "Signature cancelled.", "plugin_red_packet_display_name": "Plugin: Red Packet", + "plugin_red_packet_details": "Red Packet Details", "plugin_red_packet_split_mode": "Split Mode", "plugin_red_packet_average": "Average", "plugin_red_packet_random": "Random", "plugin_red_packet_shares": "Shares", "plugin_red_packet_best_wishes": "Best Wishes!", "plugin_red_packet_create_new": "New", - "plugin_red_packet_claim": "Claim", - "plugin_red_packet_claiming": "Claiming...", + "plugin_red_packet_open": "Open Red Packet", "plugin_red_packet_refund": "Refund", "plugin_red_packet_empty": "Empty", "plugin_red_packet_refunding": "Refunding", diff --git a/packages/maskbook/src/_locales/ja/messages.json b/packages/maskbook/src/_locales/ja/messages.json index eed9f3f852e3..ba0dbe7f8266 100644 --- a/packages/maskbook/src/_locales/ja/messages.json +++ b/packages/maskbook/src/_locales/ja/messages.json @@ -373,14 +373,14 @@ "plugin_wallet_fail_to_sign": "", "plugin_wallet_cancel_sign": "", "plugin_red_packet_display_name": "プラグイン: 投げ銭", + "plugin_red_packet_details": "", "plugin_red_packet_split_mode": "分割モード", "plugin_red_packet_average": "平均", "plugin_red_packet_random": "ランダム", "plugin_red_packet_shares": "株数", "plugin_red_packet_best_wishes": "幸運を祈ります!", "plugin_red_packet_create_new": "新しい投げ銭を作成", - "plugin_red_packet_claim": "受け取る", - "plugin_red_packet_claiming": "", + "plugin_red_packet_open": "", "plugin_red_packet_refund": "無効", "plugin_red_packet_empty": "", "plugin_red_packet_refunding": "", diff --git a/packages/maskbook/src/_locales/ko/messages.json b/packages/maskbook/src/_locales/ko/messages.json index 46397d4f14a8..65bcd4024f23 100644 --- a/packages/maskbook/src/_locales/ko/messages.json +++ b/packages/maskbook/src/_locales/ko/messages.json @@ -373,14 +373,14 @@ "plugin_wallet_fail_to_sign": "", "plugin_wallet_cancel_sign": "", "plugin_red_packet_display_name": "플러그인: 빨간 백", + "plugin_red_packet_details": "", "plugin_red_packet_split_mode": "분할 모드", "plugin_red_packet_average": "평균", "plugin_red_packet_random": "렌덤", "plugin_red_packet_shares": "공유", "plugin_red_packet_best_wishes": "행운을 빌어요!", "plugin_red_packet_create_new": "새로운 것 만들기", - "plugin_red_packet_claim": "수령", - "plugin_red_packet_claiming": "", + "plugin_red_packet_open": "", "plugin_red_packet_refund": "반환", "plugin_red_packet_empty": "", "plugin_red_packet_refunding": "", diff --git a/packages/maskbook/src/_locales/zh/messages.json b/packages/maskbook/src/_locales/zh/messages.json index 75446ec6b43f..6d84f4e1264e 100644 --- a/packages/maskbook/src/_locales/zh/messages.json +++ b/packages/maskbook/src/_locales/zh/messages.json @@ -373,14 +373,14 @@ "plugin_wallet_fail_to_sign": "", "plugin_wallet_cancel_sign": "", "plugin_red_packet_display_name": "插件:紅包", + "plugin_red_packet_details": "", "plugin_red_packet_split_mode": "分割模式", "plugin_red_packet_average": "平均", "plugin_red_packet_random": "隨機", "plugin_red_packet_shares": "分享數", "plugin_red_packet_best_wishes": "祝好運!", "plugin_red_packet_create_new": "建立新的", - "plugin_red_packet_claim": "認領", - "plugin_red_packet_claiming": "", + "plugin_red_packet_open": "", "plugin_red_packet_refund": "退款", "plugin_red_packet_empty": "", "plugin_red_packet_refunding": "", diff --git a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacket.tsx b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacket.tsx index ccd284a7f290..41e9f8514cd7 100644 --- a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacket.tsx +++ b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacket.tsx @@ -2,10 +2,9 @@ import { ChainId, formatBalance, getChainIdFromName, - isDAI, - isOKB, resolveNetworkName, TransactionStateType, + TransactionState, useAccount, useChainIdValid, useTokenDetailed, @@ -13,7 +12,8 @@ import { } from '@masknet/web3-shared' import { Box, Card, makeStyles, Skeleton, Typography } from '@material-ui/core' import classNames from 'classnames' -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useMemo } from 'react' +import { Loading } from '@masknet/icons' import { usePostLink } from '../../../components/DataSource/usePostInfo' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../social-network' @@ -31,15 +31,17 @@ import { RedPacketStatus } from '../types' const useStyles = makeStyles((theme) => ({ root: { borderRadius: theme.spacing(1), - padding: theme.spacing(2), + padding: theme.spacing(3), background: '#DB0632', position: 'relative', display: 'flex', color: theme.palette.common.white, flexDirection: 'column', justifyContent: 'space-between', - height: 136, + height: 335, boxSizing: 'border-box', + backgroundImage: `url(${new URL('./cover.png', import.meta.url)})`, + backgroundSize: 'cover', }, header: { display: 'flex', @@ -51,22 +53,29 @@ const useStyles = makeStyles((theme) => ({ flex: 1, flexDirection: 'column', alignItems: 'flex-start', - justifyContent: 'center', + justifyContent: 'flex-start', }, footer: { paddingTop: theme.spacing(2), + width: '100%', display: 'flex', + gap: theme.spacing(2), justifyContent: 'center', }, from: { - flex: '1', - textAlign: 'left', + position: 'absolute', + fontSize: '14px', + right: '26px', + bottom: '20px', }, label: { borderRadius: theme.spacing(1), padding: theme.spacing(0.2, 1), background: 'rgba(0, 0, 0, 0.2)', textTransform: 'capitalize', + position: 'absolute', + right: 18, + top: 27, }, words: { color: '#FAF2BF', @@ -78,23 +87,14 @@ const useStyles = makeStyles((theme) => ({ button: { color: theme.palette.common.white, }, - packet: { - top: 40, - right: -10, - width: 90, - height: 90, - position: 'absolute', - backgroundAttachment: 'local', - backgroundPosition: 'center', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundImage: `url(${new URL('./present-default.png', import.meta.url)})`, - }, - dai: { - backgroundImage: `url(${new URL('./present-dai.png', import.meta.url)})`, + spinning: { + display: 'flex', + animation: '$spinning 2s infinite linear', }, - okb: { - backgroundImage: `url(${new URL('./present-okb.png', import.meta.url)})`, + '@keyframes spinning': { + to: { + transform: 'rotate(360deg)', + }, }, text: { padding: theme.spacing(0.5, 2), @@ -104,9 +104,6 @@ const useStyles = makeStyles((theme) => ({ justifyContent: 'space-between', boxSizing: 'border-box', }, - dimmer: { - backgroundColor: 'rgba(0, 0, 0, 0.6)', - }, cursor: { cursor: 'pointer', }, @@ -142,7 +139,6 @@ export function RedPacket(props: RedPacketProps) { // context const account = useAccount() - const chainIdValid = useChainIdValid() const networkType = useNetworkType() //#region token detailed @@ -157,23 +153,15 @@ export function RedPacket(props: RedPacketProps) { const { canFetch, canClaim, canRefund, listOfStatus } = availabilityComputed - //#region remote controlled select provider dialog - const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog( - WalletMessages.events.selectProviderDialogUpdated, - ) - //#endregion - //#region remote controlled transaction dialog const postLink = usePostLink() const shareLink = activatedSocialNetworkUI.utils .getShareLinkURL?.( - canClaim - ? t('plugin_red_packet_share_message', { - sender: payload.sender.name, - payload: postLink, - network: resolveNetworkName(networkType), - }).trim() - : '', + t('plugin_red_packet_share_message', { + sender: payload.sender.name, + payload: postLink, + network: resolveNetworkName(networkType), + }).trim(), ) .toString() const [claimState, claimCallback, resetClaimCallback] = useClaimCallback( @@ -191,7 +179,6 @@ export function RedPacket(props: RedPacketProps) { // close the transaction dialog const { setDialog: setTransactionDialog } = useRemoteControlledDialog( WalletMessages.events.transactionDialogUpdated, - (ev) => undefined, ) // open the transation dialog @@ -226,6 +213,36 @@ export function RedPacket(props: RedPacketProps) { else if (canRefund) await refundCallback() }, [canClaim, canRefund, claimCallback, refundCallback]) + const subtle = useMemo(() => { + if (!availability || !token) return '' + + if (listOfStatus.includes(RedPacketStatus.expired) && canRefund) + return t('plugin_red_packet_description_refund', { + balance: formatBalance(availability.balance, token.decimals), + symbol: token.symbol, + }) + if (listOfStatus.includes(RedPacketStatus.claimed)) + return t( + 'plugin_red_packet_description_claimed', + (availability as RedPacketAvailability).claimed_amount + ? { + amount: formatBalance((availability as RedPacketAvailability).claimed_amount, token.decimals), + symbol: token.symbol, + } + : { amount: '', symbol: '' }, + ) + if (listOfStatus.includes(RedPacketStatus.refunded)) return t('plugin_red_packet_description_refunded') + if (listOfStatus.includes(RedPacketStatus.expired)) return t('plugin_red_packet_description_expired') + if (listOfStatus.includes(RedPacketStatus.empty)) return t('plugin_red_packet_description_empty') + if (!payload.password) return t('plugin_red_packet_description_broken') + return t('plugin_red_packet_description_failover', { + total: formatBalance(payload.total, token.decimals), + symbol: token.symbol, + name: payload.sender.name ?? '-', + shares: payload.shares ?? '-', + }) + }, [availability, token, listOfStatus, payload, t]) + // the red packet can fetch without account if (!availability || !token) return ( @@ -260,9 +277,6 @@ export function RedPacket(props: RedPacketProps) {
- - {t('plugin_red_packet_from', { name: payload.sender.name ?? '-' })} - {/* it might be fontSize: 12 on twitter based on theme? */} {canFetch && listOfStatus.length ? ( @@ -274,90 +288,101 @@ export function RedPacket(props: RedPacketProps) { {payload.sender.message} - - {(() => { - if (listOfStatus.includes(RedPacketStatus.expired) && canRefund) - return t('plugin_red_packet_description_refund', { - balance: formatBalance(availability.balance, token.decimals), - symbol: token.symbol, - }) - if (listOfStatus.includes(RedPacketStatus.claimed)) - return t( - 'plugin_red_packet_description_claimed', - (availability as RedPacketAvailability).claimed_amount - ? { - amount: formatBalance( - (availability as RedPacketAvailability).claimed_amount, - token.decimals, - ), - symbol: token.symbol, - } - : { amount: '', symbol: '' }, - ) - if (listOfStatus.includes(RedPacketStatus.refunded)) - return t('plugin_red_packet_description_refunded') - if (listOfStatus.includes(RedPacketStatus.expired)) - return t('plugin_red_packet_description_expired') - if (listOfStatus.includes(RedPacketStatus.empty)) - return t('plugin_red_packet_description_empty') - if (!payload.password) return t('plugin_red_packet_description_broken') - return t('plugin_red_packet_description_failover', { - total: formatBalance(payload.total, token.decimals), - symbol: token.symbol, - name: payload.sender.name ?? '-', - shares: payload.shares ?? '-', - }) - })()} + {subtle} + + {t('plugin_red_packet_from', { name: payload.sender.name ?? '-' })}
-
-
- {canClaim || canRefund ? ( - - - {!account ? ( - - {t('plugin_wallet_connect_a_wallet')} - - ) : !chainIdValid ? ( - - {t('plugin_wallet_invalid_network')} - - ) : ( - - {canClaim - ? claimState.type === TransactionStateType.HASH - ? t('plugin_red_packet_claiming') - : t('plugin_red_packet_claim') - : refundState.type === TransactionStateType.HASH - ? t('plugin_red_packet_refunding') - : t('plugin_red_packet_refund')} - - )} - - - ) : null} + ) } +interface OperationFooterProps { + account: string + canClaim: boolean + canRefund: boolean + claimState: TransactionState + refundState: TransactionState + shareLink?: string + onClaimOrRefund: () => void +} + +const SpinningIcon = () => { + const classes = useStyles() + return ( + + + + ) +} + +function OperationFooter({ + account, + canClaim, + canRefund, + claimState, + refundState, + shareLink, + onClaimOrRefund, +}: OperationFooterProps) { + const classes = useStyles() + const { t } = useI18N() + const chainIdValid = useChainIdValid() + + //#region remote controlled select provider dialog + const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog( + WalletMessages.events.selectProviderDialogUpdated, + ) + //#endregion + + const handleShare = () => { + window.open(shareLink, '_blank', 'noopener noreferrer') + } + + const canClaimOrRefund = canClaim || canRefund + const claimingOrRefunding = + claimState.type === TransactionStateType.HASH || refundState.type === TransactionStateType.HASH + + return ( + + + + {t('share')} + + {canClaimOrRefund && + (!account ? ( + + {t('plugin_wallet_connect_a_wallet')} + + ) : !chainIdValid ? ( + + {t('plugin_wallet_invalid_network')} + + ) : ( + : null}> + {canClaim ? t('plugin_red_packet_open') : t('plugin_red_packet_refund')} + + ))} + + + ) +} + function resolveRedPacketStatus(listOfStatus: RedPacketStatus[]) { if (listOfStatus.includes(RedPacketStatus.claimed)) return 'Claimed' if (listOfStatus.includes(RedPacketStatus.refunded)) return 'Refunded' diff --git a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx index 980603d2a25a..222a1f44d005 100644 --- a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx +++ b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx @@ -232,18 +232,23 @@ export default function RedPacketDialog(props: RedPacketDialogProps) { state, } + const isCreating = step === CreateRedPacketPageStep.NewRedPacketPage + + const title = isCreating ? t('plugin_red_packet_display_name') : t('plugin_red_packet_details') + return ( - + - {step === CreateRedPacketPageStep.NewRedPacketPage ? : null} - {step === CreateRedPacketPageStep.ConfirmPage ? ( + {isCreating ? ( + + ) : ( - ) : null} + )} ) diff --git a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketForm.tsx b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketForm.tsx index 0298737f166d..0c3c4e3e4ca3 100644 --- a/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketForm.tsx +++ b/packages/maskbook/src/plugins/RedPacket/SNSAdaptor/RedPacketForm.tsx @@ -1,3 +1,4 @@ +import { useRemoteControlledDialog } from '@masknet/shared' import { EthereumTokenType, formatBalance, @@ -20,17 +21,19 @@ import { useStylesExtends } from '../../../components/custom-ui-helper' import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { useI18N } from '../../../utils' -import { useRemoteControlledDialog } from '@masknet/shared' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' +import { TxFeeEstimation } from '../../../web3/UI/TxFeeEstimation' import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' import { RED_PACKET_DEFAULT_SHARES, RED_PACKET_MAX_SHARES, RED_PACKET_MIN_SHARES } from '../constants' -import type { RedPacketJSONPayload } from '../types' -import type { RedPacketSettings } from './hooks/useCreateCallback' +import { RedPacketSettings, useCreateParams } from './hooks/useCreateCallback' + +// seconds of 1 day +const duration = 60 * 60 * 24 const useStyles = makeStyles((theme) => ({ - line: { + field: { display: 'flex', margin: theme.spacing(1), }, @@ -56,10 +59,23 @@ const useStyles = makeStyles((theme) => ({ inputShrinkLabel: { transform: 'translate(17px, -3px) scale(0.75) !important', }, + label: { + textAlign: 'left', + color: theme.palette.text.secondary, + }, + gasEstimation: { + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + cursor: 'pointer', + '& > p': { + marginRight: 5, + color: theme.palette.mode === 'light' ? '#7B8192' : '#6F767C', + }, + }, })) export interface RedPacketFormProps extends withClasses { - onCreate?(payload: RedPacketJSONPayload): void SelectMenuProps?: Partial onChange(settings: Omit): void origin?: Omit @@ -76,6 +92,7 @@ export function RedPacketForm(props: RedPacketFormProps) { const { HAPPY_RED_PACKET_ADDRESS_V2, HAPPY_RED_PACKET_ADDRESS_V3 } = useRedPacketConstants() const contract_address = networkType === NetworkType.Ethereum ? HAPPY_RED_PACKET_ADDRESS_V2 : HAPPY_RED_PACKET_ADDRESS_V3 + const contract_version = networkType === NetworkType.Ethereum ? 2 : 3 //#region select token const { value: nativeTokenDetailed } = useNativeTokenDetailed() @@ -131,7 +148,10 @@ export function RedPacketForm(props: RedPacketFormProps) { : formatBalance(new BigNumber(origin?.total ?? '0').div(origin?.shares ?? 1), origin?.token?.decimals ?? 0), ) const amount = new BigNumber(rawAmount ?? '0').multipliedBy(pow10(token?.decimals ?? 0)) - const totalAmount = isRandom ? new BigNumber(amount) : new BigNumber(amount).multipliedBy(shares ?? '0') + const totalAmount = useMemo( + () => (isRandom ? new BigNumber(amount) : new BigNumber(amount).multipliedBy(shares ?? '0')), + [amount, shares], + ) // balance const { value: tokenBalance = '0', loading: loadingTokenBalance } = useTokenBalance( @@ -151,23 +171,30 @@ export function RedPacketForm(props: RedPacketFormProps) { return '' }, [account, amount, totalAmount, shares, token, tokenBalance]) - const onClick = useCallback(() => { - onChange({ - duration: 60 /* seconds */ * 60 /* mins */ * 24 /* hours */, + const creatingParams = useMemo( + () => ({ + duration, isRandom: Boolean(isRandom), name: senderName, message: message || t('plugin_red_packet_best_wishes'), shares: shares || 0, token, total: totalAmount.toFixed(), - }) + }), + [isRandom, senderName, message, t('plugin_red_packet_best_wishes'), shares, token, totalAmount], + ) + + const onClick = useCallback(() => { + onChange(creatingParams) onNext() - }, [onChange, totalAmount, token, shares, senderName, isRandom]) + }, [creatingParams, onChange, onNext]) + + const creatingResult = useCreateParams(creatingParams, contract_version) if (!token) return null return ( <> -
+
{t('plugin_red_packet_split_mode')}