From 62661fa71b9c2d278ac80584588e0895ebabeabe Mon Sep 17 00:00:00 2001 From: gitwoz <177856586+gitwoz@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:23:23 +0700 Subject: [PATCH 01/39] feat(donation): allow to support authors w/o ETH address --- lang/default.json | 3 - lang/en.json | 3 - lang/zh-Hans.json | 7 +- lang/zh-Hant.json | 7 +- package-lock.json | 4 +- src/common/enums/contract.ts | 26 +- src/common/utils/abis/curationVault.ts | 243 ++++++++++++++++++ src/common/utils/abis/index.ts | 1 + .../PaymentForm/PayTo/Complete/index.tsx | 2 +- .../Forms/PaymentForm/PayTo/Confirm/index.tsx | 4 +- .../PaymentForm/PayTo/SetAmount/index.tsx | 4 +- .../Forms/PaymentForm/Processing/index.tsx | 43 +++- .../Forms/PaymentForm/SwitchNetwork/index.tsx | 1 - src/components/Hook/index.ts | 1 - src/components/Hook/useCuration.ts | 14 - src/components/Hook/useERC20.ts | 18 +- .../SupportAuthor/DisableSupport/index.tsx | 7 - .../Support/SupportAuthor/index.tsx | 5 +- 18 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 src/common/utils/abis/curationVault.ts delete mode 100644 src/components/Hook/useCuration.ts diff --git a/lang/default.json b/lang/default.json index 784c113eba..1fc650e546 100644 --- a/lang/default.json +++ b/lang/default.json @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "Applied successfully" }, - "4tqFCR": { - "defaultMessage": "The author has not bound the USDT wallet yet" - }, "4wOWfp": { "defaultMessage": "What's this?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/en.json b/lang/en.json index 2fcbe0300c..9fe60683ba 100644 --- a/lang/en.json +++ b/lang/en.json @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "Applied successfully" }, - "4tqFCR": { - "defaultMessage": "The author has not bound the USDT wallet yet" - }, "4wOWfp": { "defaultMessage": "What's this?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 82a99d37af..309209eeb4 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -87,7 +87,7 @@ "defaultMessage": "支持排行榜" }, "/NX9L+": { - "defaultMessage": "确认授权", + "defaultMessage": "去钱包确认", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" }, "/TXBJR": { @@ -95,7 +95,7 @@ "description": "UNKNOWN_ERROR" }, "/cyuh2": { - "defaultMessage": "前往授权" + "defaultMessage": "去钱包授权" }, "/dKzfc": { "defaultMessage": "该作品因违反社区约章,已被站方强制封存。" @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "报名成功" }, - "4tqFCR": { - "defaultMessage": "作者尚未启用 USDT 钱包" - }, "4wOWfp": { "defaultMessage": "这是什么?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index 4d31476398..3808917ac8 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -87,7 +87,7 @@ "defaultMessage": "支持排行榜" }, "/NX9L+": { - "defaultMessage": "確認授權", + "defaultMessage": "去錢包確認", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" }, "/TXBJR": { @@ -95,7 +95,7 @@ "description": "UNKNOWN_ERROR" }, "/cyuh2": { - "defaultMessage": "前往授權" + "defaultMessage": "去錢包授權" }, "/dKzfc": { "defaultMessage": "該作品因違反社區約章,已被站方強制歸檔。" @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "報名成功" }, - "4tqFCR": { - "defaultMessage": "作者尚未啓用 USDT 錢包" - }, "4wOWfp": { "defaultMessage": "這是什麼?", "description": "src/components/Billboard/index.tsx" diff --git a/package-lock.json b/package-lock.json index 1746b77ffd..fd1b6dbb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matters-web", - "version": "5.6.2", + "version": "5.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "matters-web", - "version": "5.6.2", + "version": "5.7.0", "license": "Apache-2.0", "dependencies": { "@apollo/react-common": "^3.1.3", diff --git a/src/common/enums/contract.ts b/src/common/enums/contract.ts index 10202e5403..1dbec7183e 100644 --- a/src/common/enums/contract.ts +++ b/src/common/enums/contract.ts @@ -15,37 +15,43 @@ export const contract = { logbookAddress: '0xcdf8D568EC808de5fCBb35849B5bAFB5d444D4c0' as `0x${string}`, curationAddress: - '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8' as `0x${string}`, + '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8'.toLowerCase() as `0x${string}`, curationBlockNum: '34564355', tokenAddress: - '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' as `0x${string}`, + '0xc2132D05D31c914a87C6611C10748AEb04B58e8F'.toLowerCase() as `0x${string}`, tokenDecimals: 6, } : { logbookAddress: '0x203197e074b7a2f4ff6890815e4657a9c47c68b1' as `0x${string}`, curationAddress: - '0xa219C6722008aa22828B31A13ab9Ba93bB91222c' as `0x${string}`, - curationBlockNum: '28675517' as `0x${string}`, + '0xa219C6722008aa22828B31A13ab9Ba93bB91222c'.toLowerCase() as `0x${string}`, + curationBlockNum: '28675517', tokenAddress: - '0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1' as `0x${string}`, + '0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1'.toLowerCase() as `0x${string}`, tokenDecimals: 6, }, Optimism: isProd ? { curationAddress: - '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8' as `0x${string}`, - curationBlockNum: '117058632' as `0x${string}`, + '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8'.toLowerCase() as `0x${string}`, + curationBlockNum: '117058632', tokenAddress: - '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58' as `0x${string}`, + '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58'.toLowerCase() as `0x${string}`, tokenDecimals: 6, + curationVaultAddress: + '0x7CC566aa9488a9990977Cb31D856C47e67b35465'.toLowerCase() as `0x${string}`, + curationVaultBlockNum: '128886733', } : { curationAddress: - '0x92a117aeA74963Cd0CEdF9C50f99435451a291F7' as `0x${string}`, + '0x92a117aeA74963Cd0CEdF9C50f99435451a291F7'.toLowerCase() as `0x${string}`, curationBlockNum: '8438904', tokenAddress: - '0x5fd84259d66Cd46123540766Be93DFE6D43130D7' as `0x${string}`, + '0x5fd84259d66Cd46123540766Be93DFE6D43130D7'.toLowerCase() as `0x${string}`, tokenDecimals: 6, + curationVaultAddress: + '0x891060263b8397cB3c69F01E3383e7f8838Fd8a8'.toLowerCase() as `0x${string}`, + curationVaultBlockNum: '20457192', }, } diff --git a/src/common/utils/abis/curationVault.ts b/src/common/utils/abis/curationVault.ts new file mode 100644 index 0000000000..6ef9baac2d --- /dev/null +++ b/src/common/utils/abis/curationVault.ts @@ -0,0 +1,243 @@ +export const CurationVaultABI = [ + { + inputs: [ + { internalType: 'address', name: 'signer_', type: 'address' }, + { internalType: 'address', name: 'owner_', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'AlreadyWithdrawn', type: 'error' }, + { inputs: [], name: 'Expired', type: 'error' }, + { inputs: [], name: 'InvalidSignature', type: 'error' }, + { inputs: [], name: 'InvalidURI', type: 'error' }, + { inputs: [], name: 'TransferFailed', type: 'error' }, + { inputs: [], name: 'ZeroAddress', type: 'error' }, + { inputs: [], name: 'ZeroAmount', type: 'error' }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { indexed: false, internalType: 'string', name: 'uri', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Curation', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { indexed: false, internalType: 'string', name: 'uri', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Curation', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'signer', + type: 'address', + }, + ], + name: 'SignerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdraw', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdraw', + type: 'event', + }, + { + inputs: [ + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'contract IERC20', name: 'token_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', type: 'uint256' }, + { internalType: 'string', name: 'uri_', type: 'string' }, + ], + name: 'curate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'string', name: 'uri_', type: 'string' }, + ], + name: 'curate', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'address', name: '', type: 'address' }, + ], + name: 'erc20Balances', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: '', type: 'string' }], + name: 'nativeBalances', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'signer_', type: 'address' }], + name: 'setSigner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'signer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId_', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'uint256', name: 'expiredAt_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'contract IERC20', name: 'token_', type: 'address' }, + { internalType: 'uint256', name: 'expiredAt_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + name: 'withdrawals', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/src/common/utils/abis/index.ts b/src/common/utils/abis/index.ts index 567fa47105..6023b3ac6f 100644 --- a/src/common/utils/abis/index.ts +++ b/src/common/utils/abis/index.ts @@ -1,3 +1,4 @@ export * from './billboard' export * from './curation' +export * from './curationVault' export * from './ensPublicResolver' diff --git a/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx b/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx index eb8fa03e60..3e2f5cfcfc 100644 --- a/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx @@ -83,7 +83,7 @@ const Complete: React.FC = ({ currency={currency} recipient={recipient} showLikerID={isLikecoin} - showEthAddress={isUSDT} + showEthAddress={isUSDT && !!recipient.info.ethAddress} > <> diff --git a/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx b/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx index d55e4757d4..2b0e69c69d 100644 --- a/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx @@ -205,7 +205,9 @@ const Confirm: React.FC = ({ currency={currency} recipient={recipient} showLikerID={currency === CURRENCY.LIKE} - showEthAddress={currency === CURRENCY.USDT} + showEthAddress={ + currency === CURRENCY.USDT && !!recipient.info.ethAddress + } /> {isSubmitting && ( diff --git a/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx b/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx index ed65c74e11..2966a5f65b 100644 --- a/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx @@ -149,13 +149,13 @@ const SetAmount: React.FC = ({ refetch: refetchAllowanceData, isLoading: allowanceLoading, error: allowanceError, - } = useAllowanceUSDT() + } = useAllowanceUSDT(!recipient.info.ethAddress) const { data: approveData, isLoading: approving, write: approveWrite, error: approveError, - } = useApproveUSDT() + } = useApproveUSDT(!recipient.info.ethAddress) const { data: balanceUSDTData, error: balanceUSDTError } = useBalanceUSDT({ address, }) diff --git a/src/components/Forms/PaymentForm/Processing/index.tsx b/src/components/Forms/PaymentForm/Processing/index.tsx index a2078d8e42..bfd4128e60 100644 --- a/src/components/Forms/PaymentForm/Processing/index.tsx +++ b/src/components/Forms/PaymentForm/Processing/index.tsx @@ -14,7 +14,7 @@ import { PAYMENT_CURRENCY as CURRENCY, SUPPORT_SUCCESS_USDT_VISITOR, } from '~/common/enums' -import { CurationABI } from '~/common/utils' +import { CurationABI, CurationVaultABI } from '~/common/utils' import { Dialog, Icon, @@ -217,11 +217,30 @@ const USDTProcessingForm: React.FC = ({ const [draftTxId, setDraftTxId] = useState() + const useCurationVault = !recipient.info.ethAddress + const normalizedAmount = parseUnits( + amount.toString() as `${number}`, + contract.Optimism.tokenDecimals + ) + const uri = `ipfs://${article?.dataHash}` + + const { + data: vaultData, + error: vaultError, + isError: isVaultError, + write: curateVault, + } = useContractWrite({ + address: contract.Optimism.curationVaultAddress, + abi: CurationVaultABI, + functionName: 'curate', + args: [recipient.id, contract.Optimism.tokenAddress, normalizedAmount, uri], + }) + const { - data, - error, - isError, - write: curate, + data: curationData, + error: curationError, + isError: isCurationError, + write: curateDirect, } = useContractWrite({ address: contract.Optimism.curationAddress, abi: CurationABI, @@ -229,14 +248,16 @@ const USDTProcessingForm: React.FC = ({ args: [ recipient.info.ethAddress as `0x${string}`, contract.Optimism.tokenAddress, - parseUnits( - amount.toString() as `${number}`, - contract.Optimism.tokenDecimals - ), - `ipfs://${article?.dataHash}`, + normalizedAmount, + uri, ], }) + const data = useCurationVault ? vaultData : curationData + const error = useCurationVault ? vaultError : curationError + const isError = useCurationVault ? isVaultError : isCurationError + const curate = useCurationVault ? curateVault : curateDirect + const payToData = { amount, currency, @@ -327,7 +348,7 @@ const USDTProcessingForm: React.FC = ({ amount={amount} currency={currency} recipient={recipient} - showEthAddress={true} + showEthAddress={!!recipient.info.ethAddress} > {!isError && ( <> diff --git a/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx b/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx index 914747cd2c..2df6549b9c 100644 --- a/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx +++ b/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx @@ -22,7 +22,6 @@ interface SwitchNetworkProps { const SwitchNetwork: React.FC = ({ submitCallback }) => { const { lang } = useContext(LanguageContext) - // TODO: support multiple networks const targetNetork = featureSupportedChains.curation[0] const { isUnsupportedNetwork, switchToTargetNetwork, isSwitchingNetwork } = useTargetNetwork(targetNetork) diff --git a/src/components/Hook/index.ts b/src/components/Hook/index.ts index 0c40be308d..1e5f2f1696 100644 --- a/src/components/Hook/index.ts +++ b/src/components/Hook/index.ts @@ -2,7 +2,6 @@ export * from './useBillboard' export * from './useCarousel' export * from './useColorThief' export * from './useCountdown' -export * from './useCuration' export * from './useDialogSwitch' export * from './useDirectImageUpload' export * from './useERC20' diff --git a/src/components/Hook/useCuration.ts b/src/components/Hook/useCuration.ts deleted file mode 100644 index b158cb370c..0000000000 --- a/src/components/Hook/useCuration.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContractWrite, usePrepareContractWrite } from 'wagmi' - -import { contract } from '~/common/enums' -import { CurationABI } from '~/common/utils' - -export const useCurate = () => { - const { config } = usePrepareContractWrite({ - address: contract.Optimism.curationAddress, - abi: CurationABI, - functionName: 'curate', - }) - - return useContractWrite(config) -} diff --git a/src/components/Hook/useERC20.ts b/src/components/Hook/useERC20.ts index 28d36ad35d..9d4d37a41d 100644 --- a/src/components/Hook/useERC20.ts +++ b/src/components/Hook/useERC20.ts @@ -12,14 +12,19 @@ import { contract } from '~/common/enums' import { featureSupportedChains, MaxApprovedUSDTAmount } from '~/common/utils' import { ViewerContext } from '~/components' -export const useAllowanceUSDT = () => { +export const useAllowanceUSDT = (useCurationVault: boolean) => { const { address } = useAccount() return useContractRead({ address: contract.Optimism.tokenAddress, abi: erc20ABI, functionName: 'allowance', - args: [address as `0x${string}`, contract.Optimism.curationAddress], + args: [ + address as `0x${string}`, + useCurationVault + ? contract.Optimism.curationVaultAddress + : contract.Optimism.curationAddress, + ], }) } @@ -56,12 +61,17 @@ export const useBalanceEther = ({ }) } -export const useApproveUSDT = () => { +export const useApproveUSDT = (useCurationVault: boolean) => { const { config } = usePrepareContractWrite({ address: contract.Optimism.tokenAddress, abi: erc20ABI, functionName: 'approve', - args: [contract.Optimism.curationAddress, MaxApprovedUSDTAmount], + args: [ + useCurationVault + ? contract.Optimism.curationVaultAddress + : contract.Optimism.curationAddress, + MaxApprovedUSDTAmount, + ], }) return useContractWrite(config) diff --git a/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx b/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx index ad112a57aa..31543f89c2 100644 --- a/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx +++ b/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx @@ -20,7 +20,6 @@ export const DisableSupport = ({ recipient, onClose, }: Props) => { - const isUSDT = currency === CURRENCY.USDT const isLikecoin = currency === CURRENCY.LIKE return ( @@ -33,12 +32,6 @@ export const DisableSupport = ({
- {isUSDT && ( - - )} {isLikecoin && ( { const viewer = useContext(ViewerContext) const [windowRef, setWindowRef] = useState(undefined) const { currStep, forward: _forward } = useStep('setAmount') - const hasAuthorAddress = recipient.info.ethAddress const hasAuthorLikeID = !!recipient.liker.likerId const supportCurrency = storage.get(SUPPORT_TAB_PREFERENCE_KEY) const { address } = useAccount() - // TODO: support multiple networks const targetNetork = featureSupportedChains.curation[0] const { isUnsupportedNetwork } = useTargetNetwork(targetNetork) @@ -104,7 +102,6 @@ const SupportAuthor = (props: SupportAuthorProps) => { forward('setAmount') } - const isUSDT = currency === CURRENCY.USDT const isLikecoin = currency === CURRENCY.LIKE const [payToTx, setPayToTx] = @@ -177,7 +174,7 @@ const SupportAuthor = (props: SupportAuthorProps) => { const showTabs = isSetAmount || isWalletSelect || isNetworkSelect - if ((!hasAuthorAddress && isUSDT) || (!hasAuthorLikeID && isLikecoin)) { + if (!hasAuthorLikeID && isLikecoin) { return ( Date: Thu, 5 Dec 2024 15:44:27 +0700 Subject: [PATCH 02/39] feat(donation): add WithdrewLockedTokens notice --- lang/default.json | 3 + lang/en.json | 3 + lang/zh-Hans.json | 3 + lang/zh-Hant.json | 3 + src/common/enums/test.ts | 1 + .../WithdrewLockedTokensNotice.tsx | 74 +++++++++++++++++++ .../Notice/TransactionNotice/index.tsx | 5 ++ src/stories/components/Notices/mock.ts | 11 +++ src/stories/mocks/index.ts | 6 ++ 9 files changed, 109 insertions(+) create mode 100644 src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx diff --git a/lang/default.json b/lang/default.json index 1fc650e546..9939beb6df 100644 --- a/lang/default.json +++ b/lang/default.json @@ -1265,6 +1265,9 @@ "defaultMessage": "New followers", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "The vault contract has sent {amount} USDT to the linked wallet. Click here for details." + }, "Jmg5do": { "defaultMessage": "Archived Work" }, diff --git a/lang/en.json b/lang/en.json index 9fe60683ba..7650708501 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1265,6 +1265,9 @@ "defaultMessage": "New followers", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "The vault contract has sent {amount} USDT to the linked wallet. Click here for details." + }, "Jmg5do": { "defaultMessage": "Archived Work" }, diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 309209eeb4..9a4f77662e 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -1265,6 +1265,9 @@ "defaultMessage": "被关注", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "交易合约已将金额 {amount} USDT 发送至绑定钱包,点此查看详情" + }, "Jmg5do": { "defaultMessage": "作品已归档" }, diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index 3808917ac8..e6f8717518 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -1265,6 +1265,9 @@ "defaultMessage": "被追蹤", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "交易合約已將金額 {amount} USDT 發送至綁定錢包,點此查看詳情" + }, "Jmg5do": { "defaultMessage": "作品已封存" }, diff --git a/src/common/enums/test.ts b/src/common/enums/test.ts index 3cb983ce83..04553f177c 100644 --- a/src/common/enums/test.ts +++ b/src/common/enums/test.ts @@ -127,6 +127,7 @@ export enum TEST_ID { NOTICE_TAG_LEAVE_EDITOR = 'notice/tag-leave-editor', NOTICE_PAYMENT_PAYOUT = 'notice/payment-payout', NOTICE_PAYMENT_RECEIVE_DONATION = 'notice/payment-receive-donation', + NOTICE_WITHDREW_LOCKED_TOKENS = 'notice/withdrew-locked-tokens', NOTICE_CIRCLE_NEW_FOLLOWER = 'notice/circle-new-follower', NOTICE_CIRCLE_NEW_SUBSCRIBER = 'notice/circle-new-subscriber', NOTICE_CIRCLE_NEW_UNSUBSCRIBER = 'notice/circle-new-unsubscriber', diff --git a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx new file mode 100644 index 0000000000..f183776f1d --- /dev/null +++ b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx @@ -0,0 +1,74 @@ +import gql from 'graphql-tag' +import { FormattedMessage } from 'react-intl' +import { parseUnits } from 'viem' + +import { contract, TEST_ID } from '~/common/enums' +import { explorers } from '~/common/utils/wallet' +import { WithdrewLockedTokensNoticeFragment } from '~/gql/graphql' + +import NoticeActorAvatar from '../NoticeActorAvatar' +import NoticeActorName from '../NoticeActorName' +import NoticeDate from '../NoticeDate' +import styles from '../styles.module.css' + +const WithdrewLockedTokensNotice = ({ + notice, +}: { + notice: WithdrewLockedTokensNoticeFragment +}) => { + const tx = notice.tx + const blockchainTx = tx.blockchainTx + + if (!notice.actors || !blockchainTx) { + return null + } + + const link = `${explorers[blockchainTx.chain].url}/tx/${blockchainTx.txHash}` + + return ( +
+
+ + + +
+
+ +
+
+ ) +} + +WithdrewLockedTokensNotice.fragments = { + notice: gql` + fragment WithdrewLockedTokensNotice on TransactionNotice { + id + ...NoticeDate + actors { + ...NoticeActorAvatarUser + ...NoticeActorNameUser + } + tx: target { + id + amount + currency + blockchainTx { + chain + txHash + } + } + } + ${NoticeDate.fragments.notice} + ${NoticeActorAvatar.fragments.user} + ${NoticeActorName.fragments.user} + `, +} + +export default WithdrewLockedTokensNotice diff --git a/src/components/Notice/TransactionNotice/index.tsx b/src/components/Notice/TransactionNotice/index.tsx index 5c7f189862..0c0b618433 100644 --- a/src/components/Notice/TransactionNotice/index.tsx +++ b/src/components/Notice/TransactionNotice/index.tsx @@ -3,6 +3,7 @@ import gql from 'graphql-tag' import { TransactionNoticeFragment } from '~/gql/graphql' import PaymentReceivedDonationNotice from './PaymentReceivedDonationNotice' +import WithdrewLockedTokensNotice from './WithdrewLockedTokensNotice' const TransactionNotice = ({ notice, @@ -12,6 +13,8 @@ const TransactionNotice = ({ switch (notice.txNoticeType) { case 'PaymentReceivedDonation': return + case 'WithdrewLockedTokens': + return default: return null } @@ -25,8 +28,10 @@ TransactionNotice.fragments = { __typename txNoticeType: type ...PaymentReceivedDonationNotice + ...WithdrewLockedTokensNotice } ${PaymentReceivedDonationNotice.fragments.notice} + ${WithdrewLockedTokensNotice.fragments.notice} `, } diff --git a/src/stories/components/Notices/mock.ts b/src/stories/components/Notices/mock.ts index 6ea0d8336b..79e5a40c32 100644 --- a/src/stories/components/Notices/mock.ts +++ b/src/stories/components/Notices/mock.ts @@ -1,5 +1,6 @@ import { MOCK_ARTILCE, + MOCK_BLOCKCHAIN_TRANSACTION, MOCK_CIRCLE, MOCK_CIRCLE_ARTICLE, MOCK_CIRCLE_COMMENT, @@ -323,6 +324,16 @@ export const MOCK_NOTICE_LIST = [ txNoticeType: 'PaymentReceivedDonation' as any, tx: MOCK_TRANSACTION, }, + // WithdrewLockedTokens + { + __typename: 'TransactionNotice' as any, + id: 'WithdrewLockedTokens', + unread: false, + createdAt: '2020-12-24T07:29:17.682Z', + actors: [MOCK_USER], + txNoticeType: 'WithdrewLockedTokens' as any, + tx: { ...MOCK_TRANSACTION, blockchainTx: MOCK_BLOCKCHAIN_TRANSACTION }, + }, /** * Circle diff --git a/src/stories/mocks/index.ts b/src/stories/mocks/index.ts index d4e02d4997..e00f9011d9 100644 --- a/src/stories/mocks/index.ts +++ b/src/stories/mocks/index.ts @@ -279,6 +279,12 @@ export const MOCK_TRANSACTION = { target: MOCK_ARTILCE, } +export const MOCK_BLOCKCHAIN_TRANSACTION = { + __typename: 'BlockchainTransaction' as any, + chain: 'Optimism' as any, + txHash: '0x1234567890abcdef', +} + // Crypto wallet export const MOCK_CRYPTO_WALLET = { __typename: 'CryptoWallet' as any, From 10ad7f7f67bb940ad5786f535d2fa35c65dbaee5 Mon Sep 17 00:00:00 2001 From: gitwoz <177856586+gitwoz@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:33:38 +0700 Subject: [PATCH 03/39] feat(donation): revise PaymentReceivedDonationNotice --- lang/default.json | 3 +++ lang/en.json | 3 +++ lang/zh-Hans.json | 3 +++ lang/zh-Hant.json | 3 +++ .../PaymentReceivedDonationNotice.tsx | 11 +++++++++++ .../TransactionNotice/WithdrewLockedTokensNotice.tsx | 3 +-- 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lang/default.json b/lang/default.json index 9939beb6df..5750a0a095 100644 --- a/lang/default.json +++ b/lang/default.json @@ -869,6 +869,9 @@ "defaultMessage": "Copy comment", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ", connect your wallet to claim" + }, "Ci7dxf": { "defaultMessage": "Comment deleted", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/en.json b/lang/en.json index 7650708501..0205359d4b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -869,6 +869,9 @@ "defaultMessage": "Copy comment", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ", connect your wallet to claim" + }, "Ci7dxf": { "defaultMessage": "Comment deleted", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 9a4f77662e..acc34265b0 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -869,6 +869,9 @@ "defaultMessage": "复制留言", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ",绑定钱包后可领取" + }, "Ci7dxf": { "defaultMessage": "留言已删除", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index e6f8717518..9bcf8756ca 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -869,6 +869,9 @@ "defaultMessage": "複製留言", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ",連接錢包領取" + }, "Ci7dxf": { "defaultMessage": "留言已刪除", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx b/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx index 41d89f7411..ece92363cb 100644 --- a/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx +++ b/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx @@ -1,7 +1,9 @@ import gql from 'graphql-tag' +import { useContext } from 'react' import { FormattedMessage } from 'react-intl' import { TEST_ID } from '~/common/enums' +import { ViewerContext } from '~/components/Context' import { PaymentReceivedDonationNoticeFragment } from '~/gql/graphql' import NoticeActorAvatar from '../NoticeActorAvatar' @@ -16,11 +18,14 @@ const PaymentReceivedDonationNotice = ({ }: { notice: PaymentReceivedDonationNoticeFragment }) => { + const viewer = useContext(ViewerContext) + if (!notice.actors) { return null } const tx = notice.tx + const hasEthAddress = !!viewer?.info?.ethAddress return ( {tx.amount} {tx.currency} + {!hasEthAddress && ( + + )} )) || '' diff --git a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx index f183776f1d..54d7283e2e 100644 --- a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx +++ b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx @@ -1,8 +1,7 @@ import gql from 'graphql-tag' import { FormattedMessage } from 'react-intl' -import { parseUnits } from 'viem' -import { contract, TEST_ID } from '~/common/enums' +import { TEST_ID } from '~/common/enums' import { explorers } from '~/common/utils/wallet' import { WithdrewLockedTokensNoticeFragment } from '~/gql/graphql' From c8a4cba52c9f0684b9fc52dfd00d5ab8bea08250 Mon Sep 17 00:00:00 2001 From: gitwoz <177856586+gitwoz@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:41:40 +0700 Subject: [PATCH 04/39] feat(settings): show claim button for viewer hasn't connected a wallet but has balance in vault --- lang/default.json | 3 +++ lang/en.json | 3 +++ lang/zh-Hans.json | 3 +++ lang/zh-Hant.json | 3 +++ src/common/utils/contract.ts | 11 +++++++++ src/common/utils/index.ts | 1 + .../Forms/PaymentForm/Processing/index.tsx | 14 +++++++++-- src/components/Hook/useERC20.ts | 24 ++++++++++++++++++- .../Me/Settings/Account/Wallet/index.tsx | 19 +++++++++++---- 9 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/common/utils/contract.ts diff --git a/lang/default.json b/lang/default.json index 5750a0a095..bf57dae841 100644 --- a/lang/default.json +++ b/lang/default.json @@ -500,6 +500,9 @@ "defaultMessage": "subscribers_empty", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, + "6Sj2lN": { + "defaultMessage": "Claim" + }, "6Vznnt": { "defaultMessage": "left a comment in your circle", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" diff --git a/lang/en.json b/lang/en.json index 0205359d4b..56dd960aac 100644 --- a/lang/en.json +++ b/lang/en.json @@ -500,6 +500,9 @@ "defaultMessage": "subscribers_empty", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, + "6Sj2lN": { + "defaultMessage": "Claim" + }, "6Vznnt": { "defaultMessage": "left a comment in your circle", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index acc34265b0..b04c30a4c1 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -500,6 +500,9 @@ "defaultMessage": "目前总订阅人数", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, + "6Sj2lN": { + "defaultMessage": "提领支持" + }, "6Vznnt": { "defaultMessage": "在你的围炉中留言", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index 9bcf8756ca..8e38536882 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -500,6 +500,9 @@ "defaultMessage": "目前總訂閱人數", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, + "6Sj2lN": { + "defaultMessage": "提領支持" + }, "6Vznnt": { "defaultMessage": "在你的圍爐中留言", "description": "src/components/Notice/CircleNotice/CircleNewDiscussionComments.tsx" diff --git a/src/common/utils/contract.ts b/src/common/utils/contract.ts new file mode 100644 index 0000000000..24623b4880 --- /dev/null +++ b/src/common/utils/contract.ts @@ -0,0 +1,11 @@ +export const toCurationVaultUID = (userId: string) => { + return `matters:${userId}` +} + +export const parseCurationVaultUID = (uid: string) => { + const match = uid.match(/^matters:(\d+)$/) + if (!match) { + return null + } + return match[1] +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 9fe9be7425..def7f2824f 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -3,6 +3,7 @@ export * from './analytics' export * from './audioPlayer' export * from './comment' export * from './connections' +export * from './contract' export * from './cookie' export * from './crypto' export * from './datetime' diff --git a/src/components/Forms/PaymentForm/Processing/index.tsx b/src/components/Forms/PaymentForm/Processing/index.tsx index bfd4128e60..406368deb4 100644 --- a/src/components/Forms/PaymentForm/Processing/index.tsx +++ b/src/components/Forms/PaymentForm/Processing/index.tsx @@ -14,7 +14,12 @@ import { PAYMENT_CURRENCY as CURRENCY, SUPPORT_SUCCESS_USDT_VISITOR, } from '~/common/enums' -import { CurationABI, CurationVaultABI } from '~/common/utils' +import { + CurationABI, + CurationVaultABI, + fromGlobalId, + toCurationVaultUID, +} from '~/common/utils' import { Dialog, Icon, @@ -233,7 +238,12 @@ const USDTProcessingForm: React.FC = ({ address: contract.Optimism.curationVaultAddress, abi: CurationVaultABI, functionName: 'curate', - args: [recipient.id, contract.Optimism.tokenAddress, normalizedAmount, uri], + args: [ + toCurationVaultUID(fromGlobalId(recipient.id).id), + contract.Optimism.tokenAddress, + normalizedAmount, + uri, + ], }) const { diff --git a/src/components/Hook/useERC20.ts b/src/components/Hook/useERC20.ts index 9d4d37a41d..e183644088 100644 --- a/src/components/Hook/useERC20.ts +++ b/src/components/Hook/useERC20.ts @@ -9,7 +9,13 @@ import { } from 'wagmi' import { contract } from '~/common/enums' -import { featureSupportedChains, MaxApprovedUSDTAmount } from '~/common/utils' +import { + CurationVaultABI, + featureSupportedChains, + fromGlobalId, + MaxApprovedUSDTAmount, + toCurationVaultUID, +} from '~/common/utils' import { ViewerContext } from '~/components' export const useAllowanceUSDT = (useCurationVault: boolean) => { @@ -61,6 +67,22 @@ export const useBalanceEther = ({ }) } +export const useVaultBalanceUSDT = () => { + const viewer = useContext(ViewerContext) + const viewerId = viewer.id + const uid = toCurationVaultUID(fromGlobalId(viewerId).id) + const targetNetwork = featureSupportedChains.curation[0] + + return useContractRead({ + address: contract.Optimism.curationVaultAddress, + abi: CurationVaultABI, + functionName: 'erc20Balances', + args: [uid, contract.Optimism.tokenAddress], + chainId: targetNetwork.id, + cacheTime: 5_000, + }) +} + export const useApproveUSDT = (useCurationVault: boolean) => { const { config } = usePrepareContractWrite({ address: contract.Optimism.tokenAddress, diff --git a/src/views/Me/Settings/Account/Wallet/index.tsx b/src/views/Me/Settings/Account/Wallet/index.tsx index 9ac16a588d..70b54daf45 100644 --- a/src/views/Me/Settings/Account/Wallet/index.tsx +++ b/src/views/Me/Settings/Account/Wallet/index.tsx @@ -8,6 +8,7 @@ import { Icon, RemoveWalletLoginDialog, TableView, + useVaultBalanceUSDT, ViewerContext, } from '~/components' import { SocialAccountType } from '~/gql/graphql' @@ -30,6 +31,9 @@ const Wallet = () => { const canRemoveNonFacebookLogins = +canEmailLogin + +canWalletLogin + nonFacebookSocials.length > 1 + const { data: vaultBalanceUSDT } = useVaultBalanceUSDT() + const hasVaultBalanceUSDT = vaultBalanceUSDT && vaultBalanceUSDT > 0 + return ( {({ openDialog: openAddWalletLoginDialog }) => { @@ -61,10 +65,17 @@ const Wallet = () => { right={ ethAddress ? undefined : ( - + {hasVaultBalanceUSDT ? ( + + ) : ( + + )} ) } From 61ff081a7c21f3e57abb49a462f775f3f524dfaa Mon Sep 17 00:00:00 2001 From: gitwoz <177856586+gitwoz@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:28:13 +0700 Subject: [PATCH 05/39] feat(wallet): revise USDT balance button; retire payment pointer; --- lang/default.json | 31 +---- lang/en.json | 31 +---- lang/zh-Hans.json | 31 +---- lang/zh-Hant.json | 31 +---- src/common/utils/form/validate.test.ts | 13 -- src/common/utils/form/validate.ts | 16 +-- src/common/utils/validator.test.ts | 13 -- src/common/utils/validator.ts | 2 - src/components/Context/Viewer/index.tsx | 1 - src/components/CurrencyFormatter/index.tsx | 12 +- .../CurrencyFormatter/styles.module.css | 14 +- .../PaymentPointerDialog/Explainer/index.tsx | 70 ---------- .../Explainer/styles.module.css | 5 - .../SetPaymentPointerForm.tsx | 129 ------------------ .../Dialogs/PaymentPointerDialog/index.tsx | 71 ---------- src/components/Dialogs/index.tsx | 1 - src/components/Head/index.tsx | 5 +- src/views/ArticleDetail/gql.ts | 1 - src/views/ArticleDetail/index.tsx | 2 - src/views/Me/Wallet/Balance/FiatCurrency.tsx | 1 + src/views/Me/Wallet/Balance/LikeCoin.tsx | 12 +- src/views/Me/Wallet/Balance/USDT.tsx | 37 +++-- src/views/Me/Wallet/PaymentPointer/index.tsx | 20 --- src/views/Me/Wallet/index.tsx | 14 +- 24 files changed, 98 insertions(+), 465 deletions(-) delete mode 100644 src/components/Dialogs/PaymentPointerDialog/Explainer/index.tsx delete mode 100644 src/components/Dialogs/PaymentPointerDialog/Explainer/styles.module.css delete mode 100644 src/components/Dialogs/PaymentPointerDialog/SetPaymentPointerForm.tsx delete mode 100644 src/components/Dialogs/PaymentPointerDialog/index.tsx delete mode 100644 src/views/Me/Wallet/PaymentPointer/index.tsx diff --git a/lang/default.json b/lang/default.json index bf57dae841..051ebff536 100644 --- a/lang/default.json +++ b/lang/default.json @@ -134,9 +134,6 @@ "defaultMessage": "Matters ID has been set up. More account info can be found in Settings", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" }, - "0HdNPs": { - "defaultMessage": "{link1}: In order to increase the sources of income for creators, Matters introduced {link2} and {link3}, enabling the payer and the payee to conduct transactions in different currencies. Through {link4}, and other services you can acqure a {link6}, and receive payments from {link7}, applicable on both Matters website and IPFS." - }, "0JlaP1": { "defaultMessage": "Oops!This link has expired", "description": "src/views/Callback/UI.tsx" @@ -702,6 +699,10 @@ "9Vkz9W": { "defaultMessage": "Maximum image area is limited to 100 megapixels (for example, 10,000×10,000 pixels)." }, + "9WMs5q": { + "defaultMessage": "Connect", + "description": "src/views/Me/Wallet/Balance" + }, "9WRlF4": { "defaultMessage": "Send" }, @@ -1232,9 +1233,6 @@ "IpMWC4": { "defaultMessage": "You’ve posted several times in a short period. Please take a break." }, - "Is5PqP": { - "defaultMessage": "Interledger Protocol" - }, "ItUuuX": { "defaultMessage": "invites you to join the circle {circleName} , and you can experience it for {freePeriod} days for free", "description": "src/components/Notice/CircleNotice/CircleInvitationNotice.tsx" @@ -1388,9 +1386,6 @@ "defaultMessage": "Article published", "description": "src/views/Me/DraftDetail/PublishState/PublishedState.tsx" }, - "LklXfd": { - "defaultMessage": "Payment Pointer" - }, "LmiUWG": { "defaultMessage": "You do not have permissionn to perform this operation", "description": "FORBIDDEN_BY_TARGET_STATE" @@ -1958,16 +1953,10 @@ "WQT8ZA": { "defaultMessage": "Edit collection" }, - "WiGHyF": { - "defaultMessage": "Web Monetization standard" - }, "WpvsPu": { "defaultMessage": "Subscribe", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, - "WrQjnc": { - "defaultMessage": "Payment pointer updated" - }, "WsPbZT": { "defaultMessage": "Continue payout" }, @@ -2321,6 +2310,9 @@ "defaultMessage": "No blocked users yet", "description": "src/views/Me/Settings/Blocked/SettingsBlocked.tsx" }, + "dK7Dnj": { + "defaultMessage": "🔥 Claim for free" + }, "dQhNbF": { "defaultMessage": "Payment will be processed by Stripe, allowing your support to be unrestricted by region.", "description": "src/components/Forms/PaymentForm/StripeCheckout/index.tsx" @@ -2544,9 +2536,6 @@ "i4OBEw": { "defaultMessage": "\"{name}\" is invalid." }, - "i7cXnf": { - "defaultMessage": "The wallet address starts with \"$\"." - }, "iEJeQH": { "defaultMessage": "Liker ID" }, @@ -2890,9 +2879,6 @@ "defaultMessage": "liked", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" }, - "p6D+Uc": { - "defaultMessage": "Enter Payment Pointer" - }, "p9gEZh": { "defaultMessage": "Up to 3 images can be uploaded" }, @@ -3252,9 +3238,6 @@ "defaultMessage": "reset your login password", "description": "src/components/Dialogs/SetEmailDialog/Content.tsx" }, - "vyG38g": { - "defaultMessage": "wallet address" - }, "w+6UiO": { "defaultMessage": "Add Invitation" }, diff --git a/lang/en.json b/lang/en.json index 56dd960aac..a67050ba68 100644 --- a/lang/en.json +++ b/lang/en.json @@ -134,9 +134,6 @@ "defaultMessage": "Matters ID has been set up. More account info can be found in Settings", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" }, - "0HdNPs": { - "defaultMessage": "{link1}: In order to increase the sources of income for creators, Matters introduced {link2} and {link3}, enabling the payer and the payee to conduct transactions in different currencies. Through {link4}, and other services you can acqure a {link6}, and receive payments from {link7}, applicable on both Matters website and IPFS." - }, "0JlaP1": { "defaultMessage": "Oops!This link has expired", "description": "src/views/Callback/UI.tsx" @@ -702,6 +699,10 @@ "9Vkz9W": { "defaultMessage": "Maximum image area is limited to 100 megapixels (for example, 10,000×10,000 pixels)." }, + "9WMs5q": { + "defaultMessage": "Connect", + "description": "src/views/Me/Wallet/Balance" + }, "9WRlF4": { "defaultMessage": "Send" }, @@ -1232,9 +1233,6 @@ "IpMWC4": { "defaultMessage": "You’ve posted several times in a short period. Please take a break." }, - "Is5PqP": { - "defaultMessage": "Interledger Protocol" - }, "ItUuuX": { "defaultMessage": "invites you to join the circle {circleName} , and you can experience it for {freePeriod} days for free", "description": "src/components/Notice/CircleNotice/CircleInvitationNotice.tsx" @@ -1388,9 +1386,6 @@ "defaultMessage": "Article published", "description": "src/views/Me/DraftDetail/PublishState/PublishedState.tsx" }, - "LklXfd": { - "defaultMessage": "Payment Pointer" - }, "LmiUWG": { "defaultMessage": "You do not have permissionn to perform this operation", "description": "FORBIDDEN_BY_TARGET_STATE" @@ -1958,16 +1953,10 @@ "WQT8ZA": { "defaultMessage": "Edit collection" }, - "WiGHyF": { - "defaultMessage": "Web Monetization standard" - }, "WpvsPu": { "defaultMessage": "Subscribe", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, - "WrQjnc": { - "defaultMessage": "Payment pointer updated" - }, "WsPbZT": { "defaultMessage": "Continue payout" }, @@ -2321,6 +2310,9 @@ "defaultMessage": "No blocked users yet", "description": "src/views/Me/Settings/Blocked/SettingsBlocked.tsx" }, + "dK7Dnj": { + "defaultMessage": "🔥 Claim for free" + }, "dQhNbF": { "defaultMessage": "Payment will be processed by Stripe, allowing your support to be unrestricted by region.", "description": "src/components/Forms/PaymentForm/StripeCheckout/index.tsx" @@ -2544,9 +2536,6 @@ "i4OBEw": { "defaultMessage": "\"{name}\" is invalid." }, - "i7cXnf": { - "defaultMessage": "The wallet address starts with \"$\"." - }, "iEJeQH": { "defaultMessage": "Liker ID" }, @@ -2890,9 +2879,6 @@ "defaultMessage": "liked", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" }, - "p6D+Uc": { - "defaultMessage": "Enter Payment Pointer" - }, "p9gEZh": { "defaultMessage": "Up to 3 images can be uploaded" }, @@ -3252,9 +3238,6 @@ "defaultMessage": "reset your login password", "description": "src/components/Dialogs/SetEmailDialog/Content.tsx" }, - "vyG38g": { - "defaultMessage": "wallet address" - }, "w+6UiO": { "defaultMessage": "Add Invitation" }, diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index b04c30a4c1..ff4a5f7eed 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -134,9 +134,6 @@ "defaultMessage": "Matters ID 已设置,更多帐号相关设置可前往设置页修改", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" }, - "0HdNPs": { - "defaultMessage": "{link1}:为增加创作者的收入来源,Matters 引入了 {link2} 与 {link3},实现付款方与收款方即使使用不同的货币进行交易,也可以对交易进行自动币种转换。你可以通过 {link4}等服务配置 {link6},获得来自 {link7} 平台的收入,在 Matters 网站与 IPFS 网络中均可使用。" - }, "0JlaP1": { "defaultMessage": "Oops!链接已失效", "description": "src/views/Callback/UI.tsx" @@ -702,6 +699,10 @@ "9Vkz9W": { "defaultMessage": "图片面积不得超过 1 亿像素(如 10,000 x 10,000 像素)" }, + "9WMs5q": { + "defaultMessage": "绑定钱包", + "description": "src/views/Me/Wallet/Balance" + }, "9WRlF4": { "defaultMessage": "送出" }, @@ -1232,9 +1233,6 @@ "IpMWC4": { "defaultMessage": "短时间内已发布多条动态,请先休息一会吧~" }, - "Is5PqP": { - "defaultMessage": "跨账本协议" - }, "ItUuuX": { "defaultMessage": "邀你加入围炉 {circleName} ,你可以免费体验 {freePeriod} 天", "description": "src/components/Notice/CircleNotice/CircleInvitationNotice.tsx" @@ -1388,9 +1386,6 @@ "defaultMessage": "作品已发布", "description": "src/views/Me/DraftDetail/PublishState/PublishedState.tsx" }, - "LklXfd": { - "defaultMessage": "跨链收款地址" - }, "LmiUWG": { "defaultMessage": "你无法对此对象进行该操作", "description": "FORBIDDEN_BY_TARGET_STATE" @@ -1958,16 +1953,10 @@ "WQT8ZA": { "defaultMessage": "编辑选集" }, - "WiGHyF": { - "defaultMessage": "Web Monetization 标准" - }, "WpvsPu": { "defaultMessage": "订阅", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, - "WrQjnc": { - "defaultMessage": "收款地址已更新" - }, "WsPbZT": { "defaultMessage": "继续提现" }, @@ -2321,6 +2310,9 @@ "defaultMessage": "尚无屏蔽记录", "description": "src/views/Me/Settings/Blocked/SettingsBlocked.tsx" }, + "dK7Dnj": { + "defaultMessage": "🔥 免费提领" + }, "dQhNbF": { "defaultMessage": "付款将由 Stripe 处理,让你的支持不受地域限制", "description": "src/components/Forms/PaymentForm/StripeCheckout/index.tsx" @@ -2544,9 +2536,6 @@ "i4OBEw": { "defaultMessage": "不能使用 “{name}”" }, - "i7cXnf": { - "defaultMessage": "钱包地址以“$”开头" - }, "iEJeQH": { "defaultMessage": "设置 Liker ID" }, @@ -2890,9 +2879,6 @@ "defaultMessage": "赞赏了", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" }, - "p6D+Uc": { - "defaultMessage": "请输入地址" - }, "p9gEZh": { "defaultMessage": "动态最多支持 3 张图片" }, @@ -3252,9 +3238,6 @@ "defaultMessage": "重置你的登录密码", "description": "src/components/Dialogs/SetEmailDialog/Content.tsx" }, - "vyG38g": { - "defaultMessage": "钱包地址" - }, "w+6UiO": { "defaultMessage": "新增邀请" }, diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index 8e38536882..644f7abff9 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -134,9 +134,6 @@ "defaultMessage": "Matters ID 已設置,更多帳號相關設置可前往設定頁修改", "description": "src/components/Dialogs/SetUserNameDialog/ConfirmStep.tsx" }, - "0HdNPs": { - "defaultMessage": "{link1}:為增加創作者的收入來源,Matters 引入了 {link2} 與 {link3},實現付款方與收款方即使使用不同的貨幣進行交易,也可以對交易進行自動幣種轉換。你可以通過 {link4}等服務配置 {link6},獲得來自 {link7} 平臺的收入,在 Matters 網站與 IPFS 網絡中均可使用。" - }, "0JlaP1": { "defaultMessage": "Oops!連結已失效", "description": "src/views/Callback/UI.tsx" @@ -702,6 +699,10 @@ "9Vkz9W": { "defaultMessage": "圖片面積不得超過 1 億像素(如 10,000x10,000 像素)" }, + "9WMs5q": { + "defaultMessage": "綁定錢包", + "description": "src/views/Me/Wallet/Balance" + }, "9WRlF4": { "defaultMessage": "送出" }, @@ -1232,9 +1233,6 @@ "IpMWC4": { "defaultMessage": "短時間內已發布多條動態,請先休息一會吧~" }, - "Is5PqP": { - "defaultMessage": "跨賬本協議" - }, "ItUuuX": { "defaultMessage": "邀你加入圍爐 {circleName} ,你可以免費體驗 {freePeriod} 天", "description": "src/components/Notice/CircleNotice/CircleInvitationNotice.tsx" @@ -1388,9 +1386,6 @@ "defaultMessage": "作品已發布", "description": "src/views/Me/DraftDetail/PublishState/PublishedState.tsx" }, - "LklXfd": { - "defaultMessage": "跨鏈收款地址" - }, "LmiUWG": { "defaultMessage": "你無法對此對象進行該操作", "description": "FORBIDDEN_BY_TARGET_STATE" @@ -1958,16 +1953,10 @@ "WQT8ZA": { "defaultMessage": "編輯選集" }, - "WiGHyF": { - "defaultMessage": "Web Monetization 標準" - }, "WpvsPu": { "defaultMessage": "訂閱", "description": "src/views/Circle/Analytics/SubscriberAnalytics/index.tsx" }, - "WrQjnc": { - "defaultMessage": "收款地址已更新" - }, "WsPbZT": { "defaultMessage": "繼續提現" }, @@ -2321,6 +2310,9 @@ "defaultMessage": "尚無封鎖紀錄", "description": "src/views/Me/Settings/Blocked/SettingsBlocked.tsx" }, + "dK7Dnj": { + "defaultMessage": "🔥 免費提領" + }, "dQhNbF": { "defaultMessage": "付款將由 Stripe 處理,讓你的支持不受地域限制", "description": "src/components/Forms/PaymentForm/StripeCheckout/index.tsx" @@ -2544,9 +2536,6 @@ "i4OBEw": { "defaultMessage": "不能使用「{name}」" }, - "i7cXnf": { - "defaultMessage": "錢包地址以「$」開頭" - }, "iEJeQH": { "defaultMessage": "設置 Liker ID" }, @@ -2890,9 +2879,6 @@ "defaultMessage": "讚賞了", "description": "src/components/Notice/ArticleNotice/ArticleNewAppreciationNotice.tsx" }, - "p6D+Uc": { - "defaultMessage": "請輸入地址" - }, "p9gEZh": { "defaultMessage": "動態最多支持 3 張圖片" }, @@ -3252,9 +3238,6 @@ "defaultMessage": "重置你的登入密碼", "description": "src/components/Dialogs/SetEmailDialog/Content.tsx" }, - "vyG38g": { - "defaultMessage": "錢包地址" - }, "w+6UiO": { "defaultMessage": "新增邀請" }, diff --git a/src/common/utils/form/validate.test.ts b/src/common/utils/form/validate.test.ts index c4afbefb90..0cd066783a 100644 --- a/src/common/utils/form/validate.test.ts +++ b/src/common/utils/form/validate.test.ts @@ -37,7 +37,6 @@ import { validateEmail, validatePassword, validatePaymentPassword, - validatePaymentPointer, validatePayoutAmount, validateTagName, validateUserName, @@ -297,18 +296,6 @@ describe('utils/form/validate/validatePaymentPassword', () => { }) }) -describe('utils/form/validate/validatePaymentPointer', () => { - it('should validate payment pointer correctly', () => { - expect(validatePaymentPointer('', intl)).toBe('Required') - expect(validatePaymentPointer('$123456', intl)).toBeUndefined() - - const error = 'The wallet address starts with "$".' - expect(validatePaymentPointer('你好世界🌍', intl)).toBe(error) - expect(validatePaymentPointer('1234', intl)).toBe(error) - expect(validatePaymentPointer('abcd', intl)).toBe(error) - }) -}) - describe('utils/form/validate/validatePayoutAmount', () => { it('should validate payout amount correctly', () => { const min = 100 diff --git a/src/common/utils/form/validate.ts b/src/common/utils/form/validate.ts index 7ac71cc36c..66042e36b7 100644 --- a/src/common/utils/form/validate.ts +++ b/src/common/utils/form/validate.ts @@ -26,7 +26,7 @@ import { ValidEmailOptions, } from '~/common/utils' -import { hasUpperCase, isValidPaymentPointer } from '../validator' +import { hasUpperCase } from '../validator' export const PUNCTUATION_CHINESE = '\u3002\uff1f\uff01\uff0c\u3001\uff1b\uff1a\u201c\u201d\u2018\u2019\uff08\uff09\u300a\u300b\u3008\u3009\u3010\u3011\u300e\u300f\u300c\u300d\ufe43\ufe44\u3014\u3015\u2026\u2014\uff5e\ufe4f\uffe5' @@ -132,20 +132,6 @@ export const validatePaymentPassword = (value: string, intl: IntlShape) => { } } -export const validatePaymentPointer = (value: string, intl: IntlShape) => { - if (!value) { - return intl.formatMessage({ - defaultMessage: 'Required', - id: 'Seanpx', - }) - } else if (!isValidPaymentPointer(value)) { - return intl.formatMessage({ - defaultMessage: 'The wallet address starts with "$".', - id: 'i7cXnf', - }) - } -} - export const validateUserName = (value: string, intl: IntlShape) => { if (value.length > MAX_USER_NAME_LENGTH) { return intl.formatMessage( diff --git a/src/common/utils/validator.test.ts b/src/common/utils/validator.test.ts index 4d915bdf9b..42eb1430ca 100644 --- a/src/common/utils/validator.test.ts +++ b/src/common/utils/validator.test.ts @@ -4,7 +4,6 @@ import { isValidEmail, isValidPassword, isValidPaymentPassword, - isValidPaymentPointer, } from './validator' describe('utils/validator/isValidEmail', () => { @@ -50,15 +49,3 @@ describe('utils/validator/isValidPaymentPassword', () => { expect(isValidPassword('你好世界你你')).toBe(false) }) }) - -describe('utils/validator/isValidPaymentPointer', () => { - it('should return true for valid payment pointers', () => { - expect(isValidPaymentPointer('$example.com')).toBe(true) - expect(isValidPaymentPointer('$www.example.com')).toBe(true) - }) - - it('should return false for invalid payment pointers', () => { - expect(isValidPaymentPointer('example.com')).toBe(false) - expect(isValidPaymentPointer('www.example.com')).toBe(false) - }) -}) diff --git a/src/common/utils/validator.ts b/src/common/utils/validator.ts index 54c75b00c4..c966010870 100644 --- a/src/common/utils/validator.ts +++ b/src/common/utils/validator.ts @@ -49,8 +49,6 @@ export const isValidPaymentPassword = (password: string): boolean => { /** * Validate payment pointer. */ -export const isValidPaymentPointer = (paymentPointer: string): boolean => - paymentPointer.startsWith('$') export const hasUpperCase = (str: string) => str.toLowerCase() !== str diff --git a/src/components/Context/Viewer/index.tsx b/src/components/Context/Viewer/index.tsx index fcf44623b2..8dfa48de64 100644 --- a/src/components/Context/Viewer/index.tsx +++ b/src/components/Context/Viewer/index.tsx @@ -16,7 +16,6 @@ const ViewerFragments = { userName displayName avatar - paymentPointer liker { likerId civicLiker diff --git a/src/components/CurrencyFormatter/index.tsx b/src/components/CurrencyFormatter/index.tsx index 75323f66ee..53db34381b 100644 --- a/src/components/CurrencyFormatter/index.tsx +++ b/src/components/CurrencyFormatter/index.tsx @@ -5,6 +5,8 @@ interface Props { currency: string subValue?: number | string subCurrency?: string + subtitle?: React.ReactNode + weight?: 'normal' | 'medium' } export const CurrencyFormatter: React.FC = ({ @@ -12,15 +14,21 @@ export const CurrencyFormatter: React.FC = ({ currency, subValue, subCurrency, + subtitle, + weight = 'medium', }) => { return ( - + {currency} {value} + {subtitle && {subtitle}} + {subCurrency && ( - + ≈ {subCurrency} {subValue} )} diff --git a/src/components/CurrencyFormatter/styles.module.css b/src/components/CurrencyFormatter/styles.module.css index f888329756..f81932872a 100644 --- a/src/components/CurrencyFormatter/styles.module.css +++ b/src/components/CurrencyFormatter/styles.module.css @@ -6,8 +6,15 @@ & .currency { font-size: var(--text16); - font-weight: var(--font-medium); color: black; + + &.currency-medium { + font-weight: var(--font-medium); + } + + &.currency-normal { + font-weight: var(--font-normal); + } } & .subCurrency { @@ -15,4 +22,9 @@ font-size: var(--text12); color: var(--color-grey); } + + & .subtitle { + padding-top: var(--sp8); + font-size: var(--text12); + } } diff --git a/src/components/Dialogs/PaymentPointerDialog/Explainer/index.tsx b/src/components/Dialogs/PaymentPointerDialog/Explainer/index.tsx deleted file mode 100644 index 102c08ea5d..0000000000 --- a/src/components/Dialogs/PaymentPointerDialog/Explainer/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { FormattedMessage } from 'react-intl' - -import styles from './styles.module.css' - -const PaymentPointerExplainer = () => ( - <> -

- - - - ), - link2: ( - - - - ), - link3: ( - - - - ), - link4: ( - - Uphold - - ), - link5: ( - - GateHub - - ), - link6: ( - - - - ), - link7: ( - - Coil - - ), - }} - /> -

- -) - -export default PaymentPointerExplainer diff --git a/src/components/Dialogs/PaymentPointerDialog/Explainer/styles.module.css b/src/components/Dialogs/PaymentPointerDialog/Explainer/styles.module.css deleted file mode 100644 index 54b0bf0522..0000000000 --- a/src/components/Dialogs/PaymentPointerDialog/Explainer/styles.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.content { - & a { - text-decoration: underline; - } -} diff --git a/src/components/Dialogs/PaymentPointerDialog/SetPaymentPointerForm.tsx b/src/components/Dialogs/PaymentPointerDialog/SetPaymentPointerForm.tsx deleted file mode 100644 index 2175cb4b0d..0000000000 --- a/src/components/Dialogs/PaymentPointerDialog/SetPaymentPointerForm.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useFormik } from 'formik' -import gql from 'graphql-tag' -import _pickBy from 'lodash/pickBy' -import { useContext, useState } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' - -import { parseFormSubmitErrors, validatePaymentPointer } from '~/common/utils' -import { Dialog, Form, toast, ViewerContext } from '~/components' -import { useMutation } from '~/components/GQL' -import { UpdatePaymentPointerMutation } from '~/gql/graphql' - -import Explainer from './Explainer' - -interface FormProps { - setIsSubmitting: (submitting: boolean) => void - setIsValid: (valid: boolean) => void - closeDialog: () => void - formId?: string -} - -interface FormValues { - paymentPointer: string -} - -const UPDATE_PAYMENT_POINTER = gql` - mutation UpdatePaymentPointer($input: UpdateUserInfoInput!) { - updateUserInfo(input: $input) { - paymentPointer - } - } -` - -const SetPaymentPointerForm: React.FC = ({ - setIsValid, - setIsSubmitting, - closeDialog, - formId = `set-payment-pointer-form`, -}) => { - const intl = useIntl() - const [submitPaymentPointer] = useMutation( - UPDATE_PAYMENT_POINTER - ) - const viewer = useContext(ViewerContext) - - const [defaultPaymentPointer, setDefaultPaymentPointer] = useState( - viewer.paymentPointer || '' - ) - - const { - values, - errors, - touched, - handleBlur, - handleChange, - handleSubmit, - isValid, - } = useFormik({ - initialValues: { - paymentPointer: defaultPaymentPointer, - }, - validateOnBlur: false, - validateOnChange: false, - validate: ({ paymentPointer }) => - _pickBy({ - paymentPointer: validatePaymentPointer(paymentPointer, intl), - }), - onSubmit: async ({ paymentPointer }, { setFieldError }) => { - try { - setIsSubmitting(true) - await submitPaymentPointer({ - variables: { input: { paymentPointer } }, - }) - - toast.success({ - message: ( - - ), - }) - - setDefaultPaymentPointer(paymentPointer) - setIsSubmitting(false) - closeDialog() - } catch (error) { - setIsSubmitting(false) - - const [messages, codes] = parseFormSubmitErrors(error as any) - codes.forEach((code) => { - setFieldError('paymentPointer', intl.formatMessage(messages[code])) - }) - } - }, - }) - - const updateValidity = () => - setIsValid(isValid && values.paymentPointer !== defaultPaymentPointer) - - return ( - - - - -
- { - handleChange(e) - updateValidity() - }} - spacingTop="base" - /> - -
-
- ) -} - -export default SetPaymentPointerForm diff --git a/src/components/Dialogs/PaymentPointerDialog/index.tsx b/src/components/Dialogs/PaymentPointerDialog/index.tsx deleted file mode 100644 index 69de601515..0000000000 --- a/src/components/Dialogs/PaymentPointerDialog/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useState } from 'react' -import { FormattedMessage } from 'react-intl' - -import { Dialog, useDialogSwitch } from '~/components' - -import SetPaymentPointerForm from './SetPaymentPointerForm' - -interface PaymentPointerProps { - children: ({ openDialog }: { openDialog: () => void }) => React.ReactNode -} - -const BasePaymentPointerDialog: React.FC = ({ - children, -}) => { - const formId = `set-payment-pointer-form` - const [isSubmitting, setIsSubmitting] = useState(false) - const [isValid, setIsValid] = useState(false) - const { show, openDialog, closeDialog } = useDialogSwitch(true) - - const SubmitButton = ( - } - loading={isSubmitting} - /> - ) - - return ( - <> - {children({ openDialog })} - - - - } - closeDialog={closeDialog} - rightBtn={SubmitButton} - /> - - - - - } - color="greyDarker" - onClick={closeDialog} - /> - {SubmitButton} - - } - /> - - - ) -} - -export const PaymentPointerDialog = (props: PaymentPointerProps) => ( - }> - {({ openDialog }) => <>{props.children({ openDialog })}} - -) diff --git a/src/components/Dialogs/index.tsx b/src/components/Dialogs/index.tsx index 629e2215c0..2513050cf7 100644 --- a/src/components/Dialogs/index.tsx +++ b/src/components/Dialogs/index.tsx @@ -49,4 +49,3 @@ export * from './UnsubscribeCircleDialog' // Misc export * from './BillboardDialog' -export * from './PaymentPointerDialog' diff --git a/src/components/Head/index.tsx b/src/components/Head/index.tsx index b78b2aad02..95dbb57285 100644 --- a/src/components/Head/index.tsx +++ b/src/components/Head/index.tsx @@ -29,7 +29,6 @@ interface HeadProps { path?: string image?: string | null noSuffix?: boolean - paymentPointer?: string | null jsonLdData?: Record | null availableLanguages?: UserLanguage[] } @@ -224,9 +223,7 @@ export const Head: React.FC = (props) => { key="ld-json-data" /> )} - {props.paymentPointer && ( - - )} + {/* noindex for non-production enviroment */} {!isProdServingCanonical && ( diff --git a/src/views/ArticleDetail/gql.ts b/src/views/ArticleDetail/gql.ts index 338c82fc2c..1b4c37b3ab 100644 --- a/src/views/ArticleDetail/gql.ts +++ b/src/views/ArticleDetail/gql.ts @@ -28,7 +28,6 @@ const articlePublicFragment = gql` language author { id - paymentPointer ...UserDigestRichUserPublic ...UserDigestRichUserPrivate } diff --git a/src/views/ArticleDetail/index.tsx b/src/views/ArticleDetail/index.tsx index 17fba58490..0505babfca 100644 --- a/src/views/ArticleDetail/index.tsx +++ b/src/views/ArticleDetail/index.tsx @@ -151,7 +151,6 @@ const BaseArticleDetail = ({ } const authorId = article.author?.id - const paymentPointer = article.author?.paymentPointer const collectionCount = article.collection?.totalCount || 0 const isAuthor = viewer.id === authorId const circle = article.access.circle @@ -274,7 +273,6 @@ const BaseArticleDetail = ({ description={summary} keywords={keywords} image={article.cover} - paymentPointer={paymentPointer} jsonLdData={{ '@context': 'https://schema.org', '@type': 'Article', diff --git a/src/views/Me/Wallet/Balance/FiatCurrency.tsx b/src/views/Me/Wallet/Balance/FiatCurrency.tsx index f16288b6ed..1361ae0a51 100644 --- a/src/views/Me/Wallet/Balance/FiatCurrency.tsx +++ b/src/views/Me/Wallet/Balance/FiatCurrency.tsx @@ -177,6 +177,7 @@ export const FiatCurrencyBalance: React.FC = ({ balanceHKD * exchangeRate, 2 )} + weight="normal" />
diff --git a/src/views/Me/Wallet/Balance/LikeCoin.tsx b/src/views/Me/Wallet/Balance/LikeCoin.tsx index 8415ffa359..5c6f42eeb0 100644 --- a/src/views/Me/Wallet/Balance/LikeCoin.tsx +++ b/src/views/Me/Wallet/Balance/LikeCoin.tsx @@ -2,6 +2,7 @@ import { useQuery } from '@apollo/react-hooks' import classNames from 'classnames' import gql from 'graphql-tag' import React, { useContext } from 'react' +import { FormattedMessage } from 'react-intl' import { ReactComponent as IconLikeCoin } from '@/public/static/icons/24px/likecoin.svg' import { PATHS } from '~/common/enums' @@ -115,6 +116,7 @@ export const LikeCoinBalance = ({ currency="LIKE" subValue={formatAmount(total * exchangeRate, 2)} subCurrency={currency} + weight="normal" /> ) @@ -125,11 +127,15 @@ export const LikeCoinBalance = ({ diff --git a/src/views/Me/Wallet/Balance/USDT.tsx b/src/views/Me/Wallet/Balance/USDT.tsx index a3c75d33de..e7e42ef235 100644 --- a/src/views/Me/Wallet/Balance/USDT.tsx +++ b/src/views/Me/Wallet/Balance/USDT.tsx @@ -1,8 +1,10 @@ import classNames from 'classnames' import { useContext } from 'react' +import { FormattedMessage } from 'react-intl' +import { formatUnits } from 'viem' import { ReactComponent as IconTether } from '@/public/static/icons/24px/tether.svg' -import { PATHS } from '~/common/enums' +import { contract, PATHS } from '~/common/enums' import { formatAmount } from '~/common/utils' import { Button, @@ -11,6 +13,7 @@ import { TextIcon, Translate, useBalanceUSDT, + useVaultBalanceUSDT, ViewerContext, } from '~/components' import { QuoteCurrency } from '~/gql/graphql' @@ -26,14 +29,22 @@ export const USDTBalance = ({ currency, exchangeRate }: USDTBalanceProps) => { const viewer = useContext(ViewerContext) const address = viewer.info.ethAddress const { data: balanceUSDTData } = useBalanceUSDT({}) + const { data: vaultBalanceUSDTData } = useVaultBalanceUSDT() const balanceUSDT = parseFloat(balanceUSDTData?.formatted || '0') + const vaultBalanceUSDT = parseFloat( + formatUnits( + BigInt(vaultBalanceUSDTData || '0'), + contract.Optimism.tokenDecimals + ) + ) + const balance = address ? balanceUSDT : vaultBalanceUSDT const classes = classNames({ [styles.assetsItem]: true, assetsItem: true, // global selector for overriding }) - if (!address) { + if (!address && !vaultBalanceUSDT) { return (
{
@@ -69,10 +84,16 @@ export const USDTBalance = ({ currency, exchangeRate }: USDTBalanceProps) => { + ) + } + weight="normal" />
) diff --git a/src/views/Me/Wallet/PaymentPointer/index.tsx b/src/views/Me/Wallet/PaymentPointer/index.tsx deleted file mode 100644 index 42145a4a8a..0000000000 --- a/src/views/Me/Wallet/PaymentPointer/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { FormattedMessage } from 'react-intl' - -import { PaymentPointerDialog, TableView } from '~/components' - -const PaymentPointer = () => ( - - {({ openDialog }) => ( - - } - onClick={openDialog} - role="button" - ariaHasPopup="dialog" - /> - )} - -) - -export default PaymentPointer diff --git a/src/views/Me/Wallet/index.tsx b/src/views/Me/Wallet/index.tsx index 25e3247a22..4444f55c08 100644 --- a/src/views/Me/Wallet/index.tsx +++ b/src/views/Me/Wallet/index.tsx @@ -24,7 +24,6 @@ import { ExchangeRatesQuery, WalletBalanceQuery } from '~/gql/graphql' import { FiatCurrencyBalance, LikeCoinBalance, USDTBalance } from './Balance' import PaymentPassword from './PaymentPassword' -import PaymentPointer from './PaymentPointer' import styles from './styles.module.css' import ViewStripeAccount from './ViewStripeAccount' import ViewStripeCustomerPortal from './ViewStripeCustomerPortal' @@ -96,10 +95,10 @@ const Wallet = () => { @@ -119,14 +118,14 @@ const Wallet = () => { currency={currency} exchangeRate={exchangeRateHKD?.rate || 0} /> - +
@@ -134,7 +133,6 @@ const Wallet = () => { {hasPaymentPassword && } {hasStripeAccount && } - From 2521653ba27187c2b7b11046c4a402f2e91c2d02 Mon Sep 17 00:00:00 2001 From: Kechicode <186776112+Kechicode@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:48:02 +0800 Subject: [PATCH 06/39] feat(ArticleCommentForm): add slide down fade animation --- src/components/Comment/FooterActions/index.tsx | 1 + .../Forms/ArticleCommentForm/index.tsx | 10 +++++++++- .../Forms/ArticleCommentForm/styles.module.css | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/Comment/FooterActions/index.tsx b/src/components/Comment/FooterActions/index.tsx index 290d4cb443..be629286da 100644 --- a/src/components/Comment/FooterActions/index.tsx +++ b/src/components/Comment/FooterActions/index.tsx @@ -283,6 +283,7 @@ const BaseFooterActions = ({ }} isInCommentDetail={isInCommentDetail} defaultContent={defaultContent} + playAnimation /> )} diff --git a/src/components/Forms/ArticleCommentForm/index.tsx b/src/components/Forms/ArticleCommentForm/index.tsx index 7fbf4c381b..18cf2e8c5e 100644 --- a/src/components/Forms/ArticleCommentForm/index.tsx +++ b/src/components/Forms/ArticleCommentForm/index.tsx @@ -1,4 +1,5 @@ import { Editor } from '@matters/matters-editor' +import classNames from 'classnames' import { useContext, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' @@ -38,6 +39,7 @@ export interface ArticleCommentFormProps { showClear?: boolean placeholder?: string + playAnimation?: boolean isFallbackEditor?: boolean setEditor?: (editor: Editor | null) => void } @@ -54,6 +56,7 @@ export const ArticleCommentForm: React.FC = ({ showClear, placeholder, isFallbackEditor, + playAnimation, setEditor: propsSetEditor, }) => { const intl = useIntl() @@ -178,9 +181,14 @@ export const ArticleCommentForm: React.FC = ({ handleSubmit() }) + const formClasses = classNames({ + [styles.form]: true, + [styles.playAnimation]: playAnimation, + }) + return (
Date: Thu, 5 Dec 2024 15:23:23 +0700 Subject: [PATCH 07/39] feat(donation): allow to support authors w/o ETH address --- lang/default.json | 3 - lang/en.json | 3 - lang/zh-Hans.json | 7 +- lang/zh-Hant.json | 7 +- package-lock.json | 4 +- src/common/enums/contract.ts | 26 +- src/common/utils/abis/curationVault.ts | 243 ++++++++++++++++++ src/common/utils/abis/index.ts | 1 + .../PaymentForm/PayTo/Complete/index.tsx | 2 +- .../Forms/PaymentForm/PayTo/Confirm/index.tsx | 4 +- .../PaymentForm/PayTo/SetAmount/index.tsx | 4 +- .../Forms/PaymentForm/Processing/index.tsx | 43 +++- .../Forms/PaymentForm/SwitchNetwork/index.tsx | 1 - src/components/Hook/index.ts | 1 - src/components/Hook/useCuration.ts | 14 - src/components/Hook/useERC20.ts | 18 +- .../SupportAuthor/DisableSupport/index.tsx | 7 - .../Support/SupportAuthor/index.tsx | 5 +- 18 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 src/common/utils/abis/curationVault.ts delete mode 100644 src/components/Hook/useCuration.ts diff --git a/lang/default.json b/lang/default.json index ec07a834b3..c991a270c7 100644 --- a/lang/default.json +++ b/lang/default.json @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "Applied successfully" }, - "4tqFCR": { - "defaultMessage": "The author has not bound the USDT wallet yet" - }, "4wOWfp": { "defaultMessage": "What's this?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/en.json b/lang/en.json index 828e86a159..27d674b4bd 100644 --- a/lang/en.json +++ b/lang/en.json @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "Applied successfully" }, - "4tqFCR": { - "defaultMessage": "The author has not bound the USDT wallet yet" - }, "4wOWfp": { "defaultMessage": "What's this?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 377a667420..6edbbfc9b0 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -87,7 +87,7 @@ "defaultMessage": "支持排行榜" }, "/NX9L+": { - "defaultMessage": "确认授权", + "defaultMessage": "去钱包确认", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" }, "/TXBJR": { @@ -95,7 +95,7 @@ "description": "UNKNOWN_ERROR" }, "/cyuh2": { - "defaultMessage": "前往授权" + "defaultMessage": "去钱包授权" }, "/dKzfc": { "defaultMessage": "该作品因违反社区约章,已被站方强制封存。" @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "报名成功" }, - "4tqFCR": { - "defaultMessage": "作者尚未启用 USDT 钱包" - }, "4wOWfp": { "defaultMessage": "这是什么?", "description": "src/components/Billboard/index.tsx" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index d08e228708..63c66f133e 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -87,7 +87,7 @@ "defaultMessage": "支持排行榜" }, "/NX9L+": { - "defaultMessage": "確認授權", + "defaultMessage": "去錢包確認", "description": "src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx" }, "/TXBJR": { @@ -95,7 +95,7 @@ "description": "UNKNOWN_ERROR" }, "/cyuh2": { - "defaultMessage": "前往授權" + "defaultMessage": "去錢包授權" }, "/dKzfc": { "defaultMessage": "該作品因違反社區約章,已被站方強制歸檔。" @@ -389,9 +389,6 @@ "4nHH2x": { "defaultMessage": "報名成功" }, - "4tqFCR": { - "defaultMessage": "作者尚未啓用 USDT 錢包" - }, "4wOWfp": { "defaultMessage": "這是什麼?", "description": "src/components/Billboard/index.tsx" diff --git a/package-lock.json b/package-lock.json index 1746b77ffd..fd1b6dbb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matters-web", - "version": "5.6.2", + "version": "5.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "matters-web", - "version": "5.6.2", + "version": "5.7.0", "license": "Apache-2.0", "dependencies": { "@apollo/react-common": "^3.1.3", diff --git a/src/common/enums/contract.ts b/src/common/enums/contract.ts index 10202e5403..1dbec7183e 100644 --- a/src/common/enums/contract.ts +++ b/src/common/enums/contract.ts @@ -15,37 +15,43 @@ export const contract = { logbookAddress: '0xcdf8D568EC808de5fCBb35849B5bAFB5d444D4c0' as `0x${string}`, curationAddress: - '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8' as `0x${string}`, + '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8'.toLowerCase() as `0x${string}`, curationBlockNum: '34564355', tokenAddress: - '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' as `0x${string}`, + '0xc2132D05D31c914a87C6611C10748AEb04B58e8F'.toLowerCase() as `0x${string}`, tokenDecimals: 6, } : { logbookAddress: '0x203197e074b7a2f4ff6890815e4657a9c47c68b1' as `0x${string}`, curationAddress: - '0xa219C6722008aa22828B31A13ab9Ba93bB91222c' as `0x${string}`, - curationBlockNum: '28675517' as `0x${string}`, + '0xa219C6722008aa22828B31A13ab9Ba93bB91222c'.toLowerCase() as `0x${string}`, + curationBlockNum: '28675517', tokenAddress: - '0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1' as `0x${string}`, + '0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1'.toLowerCase() as `0x${string}`, tokenDecimals: 6, }, Optimism: isProd ? { curationAddress: - '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8' as `0x${string}`, - curationBlockNum: '117058632' as `0x${string}`, + '0x5edebbdae7B5C79a69AaCF7873796bb1Ec664DB8'.toLowerCase() as `0x${string}`, + curationBlockNum: '117058632', tokenAddress: - '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58' as `0x${string}`, + '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58'.toLowerCase() as `0x${string}`, tokenDecimals: 6, + curationVaultAddress: + '0x7CC566aa9488a9990977Cb31D856C47e67b35465'.toLowerCase() as `0x${string}`, + curationVaultBlockNum: '128886733', } : { curationAddress: - '0x92a117aeA74963Cd0CEdF9C50f99435451a291F7' as `0x${string}`, + '0x92a117aeA74963Cd0CEdF9C50f99435451a291F7'.toLowerCase() as `0x${string}`, curationBlockNum: '8438904', tokenAddress: - '0x5fd84259d66Cd46123540766Be93DFE6D43130D7' as `0x${string}`, + '0x5fd84259d66Cd46123540766Be93DFE6D43130D7'.toLowerCase() as `0x${string}`, tokenDecimals: 6, + curationVaultAddress: + '0x891060263b8397cB3c69F01E3383e7f8838Fd8a8'.toLowerCase() as `0x${string}`, + curationVaultBlockNum: '20457192', }, } diff --git a/src/common/utils/abis/curationVault.ts b/src/common/utils/abis/curationVault.ts new file mode 100644 index 0000000000..6ef9baac2d --- /dev/null +++ b/src/common/utils/abis/curationVault.ts @@ -0,0 +1,243 @@ +export const CurationVaultABI = [ + { + inputs: [ + { internalType: 'address', name: 'signer_', type: 'address' }, + { internalType: 'address', name: 'owner_', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'AlreadyWithdrawn', type: 'error' }, + { inputs: [], name: 'Expired', type: 'error' }, + { inputs: [], name: 'InvalidSignature', type: 'error' }, + { inputs: [], name: 'InvalidURI', type: 'error' }, + { inputs: [], name: 'TransferFailed', type: 'error' }, + { inputs: [], name: 'ZeroAddress', type: 'error' }, + { inputs: [], name: 'ZeroAmount', type: 'error' }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { indexed: false, internalType: 'string', name: 'uri', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Curation', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { indexed: false, internalType: 'string', name: 'uri', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Curation', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'signer', + type: 'address', + }, + ], + name: 'SignerChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdraw', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: true, internalType: 'string', name: 'uid', type: 'string' }, + { + indexed: true, + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdraw', + type: 'event', + }, + { + inputs: [ + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'contract IERC20', name: 'token_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', type: 'uint256' }, + { internalType: 'string', name: 'uri_', type: 'string' }, + ], + name: 'curate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'string', name: 'uri_', type: 'string' }, + ], + name: 'curate', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'address', name: '', type: 'address' }, + ], + name: 'erc20Balances', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: '', type: 'string' }], + name: 'nativeBalances', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'signer_', type: 'address' }], + name: 'setSigner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'signer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId_', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'uint256', name: 'expiredAt_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'string', name: 'uid_', type: 'string' }, + { internalType: 'contract IERC20', name: 'token_', type: 'address' }, + { internalType: 'uint256', name: 'expiredAt_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: '', type: 'string' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + ], + name: 'withdrawals', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/src/common/utils/abis/index.ts b/src/common/utils/abis/index.ts index 567fa47105..6023b3ac6f 100644 --- a/src/common/utils/abis/index.ts +++ b/src/common/utils/abis/index.ts @@ -1,3 +1,4 @@ export * from './billboard' export * from './curation' +export * from './curationVault' export * from './ensPublicResolver' diff --git a/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx b/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx index eb8fa03e60..3e2f5cfcfc 100644 --- a/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/Complete/index.tsx @@ -83,7 +83,7 @@ const Complete: React.FC = ({ currency={currency} recipient={recipient} showLikerID={isLikecoin} - showEthAddress={isUSDT} + showEthAddress={isUSDT && !!recipient.info.ethAddress} > <> diff --git a/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx b/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx index d55e4757d4..2b0e69c69d 100644 --- a/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/Confirm/index.tsx @@ -205,7 +205,9 @@ const Confirm: React.FC = ({ currency={currency} recipient={recipient} showLikerID={currency === CURRENCY.LIKE} - showEthAddress={currency === CURRENCY.USDT} + showEthAddress={ + currency === CURRENCY.USDT && !!recipient.info.ethAddress + } /> {isSubmitting && ( diff --git a/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx b/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx index ed65c74e11..2966a5f65b 100644 --- a/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx +++ b/src/components/Forms/PaymentForm/PayTo/SetAmount/index.tsx @@ -149,13 +149,13 @@ const SetAmount: React.FC = ({ refetch: refetchAllowanceData, isLoading: allowanceLoading, error: allowanceError, - } = useAllowanceUSDT() + } = useAllowanceUSDT(!recipient.info.ethAddress) const { data: approveData, isLoading: approving, write: approveWrite, error: approveError, - } = useApproveUSDT() + } = useApproveUSDT(!recipient.info.ethAddress) const { data: balanceUSDTData, error: balanceUSDTError } = useBalanceUSDT({ address, }) diff --git a/src/components/Forms/PaymentForm/Processing/index.tsx b/src/components/Forms/PaymentForm/Processing/index.tsx index a2078d8e42..bfd4128e60 100644 --- a/src/components/Forms/PaymentForm/Processing/index.tsx +++ b/src/components/Forms/PaymentForm/Processing/index.tsx @@ -14,7 +14,7 @@ import { PAYMENT_CURRENCY as CURRENCY, SUPPORT_SUCCESS_USDT_VISITOR, } from '~/common/enums' -import { CurationABI } from '~/common/utils' +import { CurationABI, CurationVaultABI } from '~/common/utils' import { Dialog, Icon, @@ -217,11 +217,30 @@ const USDTProcessingForm: React.FC = ({ const [draftTxId, setDraftTxId] = useState() + const useCurationVault = !recipient.info.ethAddress + const normalizedAmount = parseUnits( + amount.toString() as `${number}`, + contract.Optimism.tokenDecimals + ) + const uri = `ipfs://${article?.dataHash}` + + const { + data: vaultData, + error: vaultError, + isError: isVaultError, + write: curateVault, + } = useContractWrite({ + address: contract.Optimism.curationVaultAddress, + abi: CurationVaultABI, + functionName: 'curate', + args: [recipient.id, contract.Optimism.tokenAddress, normalizedAmount, uri], + }) + const { - data, - error, - isError, - write: curate, + data: curationData, + error: curationError, + isError: isCurationError, + write: curateDirect, } = useContractWrite({ address: contract.Optimism.curationAddress, abi: CurationABI, @@ -229,14 +248,16 @@ const USDTProcessingForm: React.FC = ({ args: [ recipient.info.ethAddress as `0x${string}`, contract.Optimism.tokenAddress, - parseUnits( - amount.toString() as `${number}`, - contract.Optimism.tokenDecimals - ), - `ipfs://${article?.dataHash}`, + normalizedAmount, + uri, ], }) + const data = useCurationVault ? vaultData : curationData + const error = useCurationVault ? vaultError : curationError + const isError = useCurationVault ? isVaultError : isCurationError + const curate = useCurationVault ? curateVault : curateDirect + const payToData = { amount, currency, @@ -327,7 +348,7 @@ const USDTProcessingForm: React.FC = ({ amount={amount} currency={currency} recipient={recipient} - showEthAddress={true} + showEthAddress={!!recipient.info.ethAddress} > {!isError && ( <> diff --git a/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx b/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx index 914747cd2c..2df6549b9c 100644 --- a/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx +++ b/src/components/Forms/PaymentForm/SwitchNetwork/index.tsx @@ -22,7 +22,6 @@ interface SwitchNetworkProps { const SwitchNetwork: React.FC = ({ submitCallback }) => { const { lang } = useContext(LanguageContext) - // TODO: support multiple networks const targetNetork = featureSupportedChains.curation[0] const { isUnsupportedNetwork, switchToTargetNetwork, isSwitchingNetwork } = useTargetNetwork(targetNetork) diff --git a/src/components/Hook/index.ts b/src/components/Hook/index.ts index 0c40be308d..1e5f2f1696 100644 --- a/src/components/Hook/index.ts +++ b/src/components/Hook/index.ts @@ -2,7 +2,6 @@ export * from './useBillboard' export * from './useCarousel' export * from './useColorThief' export * from './useCountdown' -export * from './useCuration' export * from './useDialogSwitch' export * from './useDirectImageUpload' export * from './useERC20' diff --git a/src/components/Hook/useCuration.ts b/src/components/Hook/useCuration.ts deleted file mode 100644 index b158cb370c..0000000000 --- a/src/components/Hook/useCuration.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContractWrite, usePrepareContractWrite } from 'wagmi' - -import { contract } from '~/common/enums' -import { CurationABI } from '~/common/utils' - -export const useCurate = () => { - const { config } = usePrepareContractWrite({ - address: contract.Optimism.curationAddress, - abi: CurationABI, - functionName: 'curate', - }) - - return useContractWrite(config) -} diff --git a/src/components/Hook/useERC20.ts b/src/components/Hook/useERC20.ts index 28d36ad35d..9d4d37a41d 100644 --- a/src/components/Hook/useERC20.ts +++ b/src/components/Hook/useERC20.ts @@ -12,14 +12,19 @@ import { contract } from '~/common/enums' import { featureSupportedChains, MaxApprovedUSDTAmount } from '~/common/utils' import { ViewerContext } from '~/components' -export const useAllowanceUSDT = () => { +export const useAllowanceUSDT = (useCurationVault: boolean) => { const { address } = useAccount() return useContractRead({ address: contract.Optimism.tokenAddress, abi: erc20ABI, functionName: 'allowance', - args: [address as `0x${string}`, contract.Optimism.curationAddress], + args: [ + address as `0x${string}`, + useCurationVault + ? contract.Optimism.curationVaultAddress + : contract.Optimism.curationAddress, + ], }) } @@ -56,12 +61,17 @@ export const useBalanceEther = ({ }) } -export const useApproveUSDT = () => { +export const useApproveUSDT = (useCurationVault: boolean) => { const { config } = usePrepareContractWrite({ address: contract.Optimism.tokenAddress, abi: erc20ABI, functionName: 'approve', - args: [contract.Optimism.curationAddress, MaxApprovedUSDTAmount], + args: [ + useCurationVault + ? contract.Optimism.curationVaultAddress + : contract.Optimism.curationAddress, + MaxApprovedUSDTAmount, + ], }) return useContractWrite(config) diff --git a/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx b/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx index ad112a57aa..31543f89c2 100644 --- a/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx +++ b/src/views/ArticleDetail/Support/SupportAuthor/DisableSupport/index.tsx @@ -20,7 +20,6 @@ export const DisableSupport = ({ recipient, onClose, }: Props) => { - const isUSDT = currency === CURRENCY.USDT const isLikecoin = currency === CURRENCY.LIKE return ( @@ -33,12 +32,6 @@ export const DisableSupport = ({
- {isUSDT && ( - - )} {isLikecoin && ( { const viewer = useContext(ViewerContext) const [windowRef, setWindowRef] = useState(undefined) const { currStep, forward: _forward } = useStep('setAmount') - const hasAuthorAddress = recipient.info.ethAddress const hasAuthorLikeID = !!recipient.liker.likerId const supportCurrency = storage.get(SUPPORT_TAB_PREFERENCE_KEY) const { address } = useAccount() - // TODO: support multiple networks const targetNetork = featureSupportedChains.curation[0] const { isUnsupportedNetwork } = useTargetNetwork(targetNetork) @@ -104,7 +102,6 @@ const SupportAuthor = (props: SupportAuthorProps) => { forward('setAmount') } - const isUSDT = currency === CURRENCY.USDT const isLikecoin = currency === CURRENCY.LIKE const [payToTx, setPayToTx] = @@ -177,7 +174,7 @@ const SupportAuthor = (props: SupportAuthorProps) => { const showTabs = isSetAmount || isWalletSelect || isNetworkSelect - if ((!hasAuthorAddress && isUSDT) || (!hasAuthorLikeID && isLikecoin)) { + if (!hasAuthorLikeID && isLikecoin) { return ( Date: Thu, 5 Dec 2024 15:44:27 +0700 Subject: [PATCH 08/39] feat(donation): add WithdrewLockedTokens notice --- lang/default.json | 3 + lang/en.json | 3 + lang/zh-Hans.json | 3 + lang/zh-Hant.json | 3 + src/common/enums/test.ts | 1 + .../WithdrewLockedTokensNotice.tsx | 74 +++++++++++++++++++ .../Notice/TransactionNotice/index.tsx | 5 ++ src/stories/components/Notices/mock.ts | 11 +++ src/stories/mocks/index.ts | 6 ++ 9 files changed, 109 insertions(+) create mode 100644 src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx diff --git a/lang/default.json b/lang/default.json index c991a270c7..2e6cec99cf 100644 --- a/lang/default.json +++ b/lang/default.json @@ -1268,6 +1268,9 @@ "defaultMessage": "New followers", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "The vault contract has sent {amount} USDT to the linked wallet. Click here for details." + }, "Jmg5do": { "defaultMessage": "Archived Work" }, diff --git a/lang/en.json b/lang/en.json index 27d674b4bd..a5abd97bec 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1268,6 +1268,9 @@ "defaultMessage": "New followers", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "The vault contract has sent {amount} USDT to the linked wallet. Click here for details." + }, "Jmg5do": { "defaultMessage": "Archived Work" }, diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 6edbbfc9b0..54f19dd150 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -1268,6 +1268,9 @@ "defaultMessage": "被关注", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "交易合约已将金额 {amount} USDT 发送至绑定钱包,点此查看详情" + }, "Jmg5do": { "defaultMessage": "作品已归档" }, diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index 63c66f133e..df91ee37bf 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -1268,6 +1268,9 @@ "defaultMessage": "被追蹤", "description": "src/views/Me/Settings/Notifications/GeneralSettings/index.tsx" }, + "JlpeDH": { + "defaultMessage": "交易合約已將金額 {amount} USDT 發送至綁定錢包,點此查看詳情" + }, "Jmg5do": { "defaultMessage": "作品已封存" }, diff --git a/src/common/enums/test.ts b/src/common/enums/test.ts index 3cb983ce83..04553f177c 100644 --- a/src/common/enums/test.ts +++ b/src/common/enums/test.ts @@ -127,6 +127,7 @@ export enum TEST_ID { NOTICE_TAG_LEAVE_EDITOR = 'notice/tag-leave-editor', NOTICE_PAYMENT_PAYOUT = 'notice/payment-payout', NOTICE_PAYMENT_RECEIVE_DONATION = 'notice/payment-receive-donation', + NOTICE_WITHDREW_LOCKED_TOKENS = 'notice/withdrew-locked-tokens', NOTICE_CIRCLE_NEW_FOLLOWER = 'notice/circle-new-follower', NOTICE_CIRCLE_NEW_SUBSCRIBER = 'notice/circle-new-subscriber', NOTICE_CIRCLE_NEW_UNSUBSCRIBER = 'notice/circle-new-unsubscriber', diff --git a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx new file mode 100644 index 0000000000..f183776f1d --- /dev/null +++ b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx @@ -0,0 +1,74 @@ +import gql from 'graphql-tag' +import { FormattedMessage } from 'react-intl' +import { parseUnits } from 'viem' + +import { contract, TEST_ID } from '~/common/enums' +import { explorers } from '~/common/utils/wallet' +import { WithdrewLockedTokensNoticeFragment } from '~/gql/graphql' + +import NoticeActorAvatar from '../NoticeActorAvatar' +import NoticeActorName from '../NoticeActorName' +import NoticeDate from '../NoticeDate' +import styles from '../styles.module.css' + +const WithdrewLockedTokensNotice = ({ + notice, +}: { + notice: WithdrewLockedTokensNoticeFragment +}) => { + const tx = notice.tx + const blockchainTx = tx.blockchainTx + + if (!notice.actors || !blockchainTx) { + return null + } + + const link = `${explorers[blockchainTx.chain].url}/tx/${blockchainTx.txHash}` + + return ( +
+
+ + + +
+
+ +
+
+ ) +} + +WithdrewLockedTokensNotice.fragments = { + notice: gql` + fragment WithdrewLockedTokensNotice on TransactionNotice { + id + ...NoticeDate + actors { + ...NoticeActorAvatarUser + ...NoticeActorNameUser + } + tx: target { + id + amount + currency + blockchainTx { + chain + txHash + } + } + } + ${NoticeDate.fragments.notice} + ${NoticeActorAvatar.fragments.user} + ${NoticeActorName.fragments.user} + `, +} + +export default WithdrewLockedTokensNotice diff --git a/src/components/Notice/TransactionNotice/index.tsx b/src/components/Notice/TransactionNotice/index.tsx index 5c7f189862..0c0b618433 100644 --- a/src/components/Notice/TransactionNotice/index.tsx +++ b/src/components/Notice/TransactionNotice/index.tsx @@ -3,6 +3,7 @@ import gql from 'graphql-tag' import { TransactionNoticeFragment } from '~/gql/graphql' import PaymentReceivedDonationNotice from './PaymentReceivedDonationNotice' +import WithdrewLockedTokensNotice from './WithdrewLockedTokensNotice' const TransactionNotice = ({ notice, @@ -12,6 +13,8 @@ const TransactionNotice = ({ switch (notice.txNoticeType) { case 'PaymentReceivedDonation': return + case 'WithdrewLockedTokens': + return default: return null } @@ -25,8 +28,10 @@ TransactionNotice.fragments = { __typename txNoticeType: type ...PaymentReceivedDonationNotice + ...WithdrewLockedTokensNotice } ${PaymentReceivedDonationNotice.fragments.notice} + ${WithdrewLockedTokensNotice.fragments.notice} `, } diff --git a/src/stories/components/Notices/mock.ts b/src/stories/components/Notices/mock.ts index 6ea0d8336b..79e5a40c32 100644 --- a/src/stories/components/Notices/mock.ts +++ b/src/stories/components/Notices/mock.ts @@ -1,5 +1,6 @@ import { MOCK_ARTILCE, + MOCK_BLOCKCHAIN_TRANSACTION, MOCK_CIRCLE, MOCK_CIRCLE_ARTICLE, MOCK_CIRCLE_COMMENT, @@ -323,6 +324,16 @@ export const MOCK_NOTICE_LIST = [ txNoticeType: 'PaymentReceivedDonation' as any, tx: MOCK_TRANSACTION, }, + // WithdrewLockedTokens + { + __typename: 'TransactionNotice' as any, + id: 'WithdrewLockedTokens', + unread: false, + createdAt: '2020-12-24T07:29:17.682Z', + actors: [MOCK_USER], + txNoticeType: 'WithdrewLockedTokens' as any, + tx: { ...MOCK_TRANSACTION, blockchainTx: MOCK_BLOCKCHAIN_TRANSACTION }, + }, /** * Circle diff --git a/src/stories/mocks/index.ts b/src/stories/mocks/index.ts index d4e02d4997..e00f9011d9 100644 --- a/src/stories/mocks/index.ts +++ b/src/stories/mocks/index.ts @@ -279,6 +279,12 @@ export const MOCK_TRANSACTION = { target: MOCK_ARTILCE, } +export const MOCK_BLOCKCHAIN_TRANSACTION = { + __typename: 'BlockchainTransaction' as any, + chain: 'Optimism' as any, + txHash: '0x1234567890abcdef', +} + // Crypto wallet export const MOCK_CRYPTO_WALLET = { __typename: 'CryptoWallet' as any, From 4922172f4b1f0de4e611822e4b2f39f1c45b687a Mon Sep 17 00:00:00 2001 From: gitwoz <177856586+gitwoz@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:33:38 +0700 Subject: [PATCH 09/39] feat(donation): revise PaymentReceivedDonationNotice --- lang/default.json | 3 +++ lang/en.json | 3 +++ lang/zh-Hans.json | 3 +++ lang/zh-Hant.json | 3 +++ .../PaymentReceivedDonationNotice.tsx | 11 +++++++++++ .../TransactionNotice/WithdrewLockedTokensNotice.tsx | 3 +-- 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lang/default.json b/lang/default.json index 2e6cec99cf..f831700c6c 100644 --- a/lang/default.json +++ b/lang/default.json @@ -869,6 +869,9 @@ "defaultMessage": "Copy comment", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ", connect your wallet to claim" + }, "Ci7dxf": { "defaultMessage": "Comment deleted", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/en.json b/lang/en.json index a5abd97bec..466f1dc5eb 100644 --- a/lang/en.json +++ b/lang/en.json @@ -869,6 +869,9 @@ "defaultMessage": "Copy comment", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ", connect your wallet to claim" + }, "Ci7dxf": { "defaultMessage": "Comment deleted", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/zh-Hans.json b/lang/zh-Hans.json index 54f19dd150..a4e6f53f38 100644 --- a/lang/zh-Hans.json +++ b/lang/zh-Hans.json @@ -869,6 +869,9 @@ "defaultMessage": "复制留言", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ",绑定钱包后可领取" + }, "Ci7dxf": { "defaultMessage": "留言已删除", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/lang/zh-Hant.json b/lang/zh-Hant.json index df91ee37bf..a9a6846fc7 100644 --- a/lang/zh-Hant.json +++ b/lang/zh-Hant.json @@ -869,6 +869,9 @@ "defaultMessage": "複製留言", "description": "src/components/Comment/DropdownActions/index.tsx" }, + "CgPGAu": { + "defaultMessage": ",連接錢包領取" + }, "Ci7dxf": { "defaultMessage": "留言已刪除", "description": "src/components/Notice/NoticeComment.tsx/moment" diff --git a/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx b/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx index 41d89f7411..ece92363cb 100644 --- a/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx +++ b/src/components/Notice/TransactionNotice/PaymentReceivedDonationNotice.tsx @@ -1,7 +1,9 @@ import gql from 'graphql-tag' +import { useContext } from 'react' import { FormattedMessage } from 'react-intl' import { TEST_ID } from '~/common/enums' +import { ViewerContext } from '~/components/Context' import { PaymentReceivedDonationNoticeFragment } from '~/gql/graphql' import NoticeActorAvatar from '../NoticeActorAvatar' @@ -16,11 +18,14 @@ const PaymentReceivedDonationNotice = ({ }: { notice: PaymentReceivedDonationNoticeFragment }) => { + const viewer = useContext(ViewerContext) + if (!notice.actors) { return null } const tx = notice.tx + const hasEthAddress = !!viewer?.info?.ethAddress return ( {tx.amount} {tx.currency} + {!hasEthAddress && ( + + )} )) || '' diff --git a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx index f183776f1d..54d7283e2e 100644 --- a/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx +++ b/src/components/Notice/TransactionNotice/WithdrewLockedTokensNotice.tsx @@ -1,8 +1,7 @@ import gql from 'graphql-tag' import { FormattedMessage } from 'react-intl' -import { parseUnits } from 'viem' -import { contract, TEST_ID } from '~/common/enums' +import { TEST_ID } from '~/common/enums' import { explorers } from '~/common/utils/wallet' import { WithdrewLockedTokensNoticeFragment } from '~/gql/graphql' From 0a6b1ae64659e8895b4aaf1cd5c68f1792d720de Mon Sep 17 00:00:00 2001 From: Kechicode <186776112+Kechicode@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:31:57 +0800 Subject: [PATCH 10/39] feat(ArticleCommentForm): add smooth hide animation for comment forms --- .../Comment/FooterActions/index.tsx | 41 +++++++++++++------ .../Forms/ArticleCommentForm/index.tsx | 12 +++++- .../ArticleCommentForm/styles.module.css | 23 +++++++++-- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/components/Comment/FooterActions/index.tsx b/src/components/Comment/FooterActions/index.tsx index be629286da..f9e0984f91 100644 --- a/src/components/Comment/FooterActions/index.tsx +++ b/src/components/Comment/FooterActions/index.tsx @@ -189,6 +189,31 @@ const BaseFooterActions = ({ )} ` : '' + const [isHiding, setIsHiding] = useState(false) + + const handleHideForm = () => { + // Start hide animation + setIsHiding(true) + } + + const handleHideComplete = () => { + // End hide animation + setIsHiding(false) + + if (editor === activeEditor) { + setActiveEditor(null) + } + setShowForm(false) + } + + const handleReplyButtonClick = () => { + if (showForm) { + handleHideForm() + } else { + toggleShowForm() + } + } + return ( <>