Skip to content

Commit

Permalink
support multi-NFT burn and duplicating when minting
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Nov 13, 2024
1 parent 3d56981 commit bcf2960
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 153 deletions.
8 changes: 4 additions & 4 deletions packages/i18n/locales/bad/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
"searchDaos": "bad bad",
"selectAllNfts": "bad bad bad bad",
"selectChain": "bad bad",
"selectNft": "bad bad",
"selectNfts": "bad bad",
"selectToken": "bad bad",
"selectValidator": "bad bad",
"selectWidget": "bad bad",
Expand Down Expand Up @@ -854,7 +854,7 @@
"blocksBehind": "bad bad",
"bulkImportActionExplanation": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
"bulkImportActionsDescription": "bad bad bad bad bad",
"burnNftDescription": "bad bad bad",
"burnNftsDescription": "bad bad bad",
"bypassSimulationExplanation": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
"cannotRemoveNoneOption": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
"catchingUp": "bad bad",
Expand Down Expand Up @@ -1210,7 +1210,6 @@
"to": "bad",
"today": "bad",
"token": "bad",
"tokenBurned": "bad bad bad bad bad bad bad",
"tokenDaoNotMemberInfo_dao": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
"tokenDaoNotMemberInfo_proposal": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
"tokenOnChain": "bad bad bad",
Expand All @@ -1220,6 +1219,7 @@
"token_one": "bad",
"token_other": "bad",
"tokens": "bad",
"tokensBurned": "bad bad bad bad bad bad bad",
"tokensWillBeSentToTreasury": "bad bad bad bad bad bad bad bad bad",
"tokensWillBeSplitAmongContributors": "bad bad bad bad bad bad",
"tos": "bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad bad",
Expand Down Expand Up @@ -1424,7 +1424,7 @@
"beginVesting": "bad bad",
"browserWallets": "bad bad",
"bulkImportActions": "bad bad bad",
"burnNft": "bad bad",
"burnNfts": "bad bad",
"cancelVesting": "bad bad",
"canceled": "bad",
"casting": "bad",
Expand Down
9 changes: 4 additions & 5 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@
"selectAccount": "Select account",
"selectAllNfts": "Select all {{count}} NFTs",
"selectChain": "Select chain",
"selectNft": "Select NFT",
"selectNfts": "Select NFT(s)",
"selectToken": "Select token",
"selectValidator": "Select validator",
Expand Down Expand Up @@ -1023,7 +1022,7 @@
"blocksBehind": "Blocks behind",
"bulkImportActionExplanation": "Choose a JSON or CSV file below that matches the format described in <2>this guide</2>.",
"bulkImportActionsDescription": "Import many actions at once.",
"burnNftDescription": "Burn an NFT.",
"burnNftsDescription": "Burn NFT(s).",
"bypassSimulationExplanation": "Simulating the proposal failed. If you don't know what this means, one of the actions above is probably misconfigured. If you are sure you want to publish it anyway, click the button again.",
"cannotRemoveNoneOption": "You cannot remove this option. It will be added to the end of the list.",
"cannotWithdrawImmediateRewardDistribution": "There are no rewards to withdraw from an immediate distribution.",
Expand Down Expand Up @@ -1467,7 +1466,6 @@
"to": "to",
"today": "today",
"token": "token",
"tokenBurned": "NFT with ID {{tokenId}} has been burned.",
"tokenCreationNoTokenFactory": "Token creation is not supported by this chain because it does not have the token factory module.",
"tokenCreationUnderDevelopment": "Token creation is under development and will be available soon.",
"tokenDaoNotMemberInfo_dao": "You are not a member of {{daoName}}. To become a member, you must obtain and stake ${{tokenSymbol}}.",
Expand All @@ -1480,6 +1478,7 @@
"token_one": "token",
"token_other": "tokens",
"tokens": "tokens",
"tokensBurned": "NFTs have been burned: {{tokenIds}}.",
"tokensWillBeSentToTreasury": "These tokens will be sent to the DAO's treasury.",
"tokensWillBeSplitAmongContributors": "{{tokens}} will be split among contributors.",
"tokensWillBeWithdrawn": "{{amount}} ${{tokenSymbol}} would be withdrawn if this were executed right now.",
Expand Down Expand Up @@ -1726,7 +1725,7 @@
"breakdown": "Breakdown",
"browserWallets": "Browser wallets",
"bulkImportActions": "Bulk Import Actions",
"burnNft": "Burn NFT",
"burnNfts": "Burn NFT(s)",
"cancelVesting": "Cancel vesting",
"canceled": "Canceled",
"casting": "Casting",
Expand Down Expand Up @@ -2023,7 +2022,7 @@
"saved": "Saved",
"scanQrCode": "Scan QR Code",
"search": "Search",
"selectNftToBurn": "Select NFT To Burn",
"selectNftsToBurn": "Select NFT(s) To Burn",
"selectNftsToTransfer": "Select NFT(s) To Transfer",
"send": "Send",
"setAdminToParent": "Set admin to {{parent}}",
Expand Down
12 changes: 6 additions & 6 deletions packages/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@
"selectAccount": "Seleccionar cuenta",
"selectAllNfts": "Seleccionar todos los {{count}} NFTs",
"selectChain": "Seleccionar cadena",
"selectNft": "Seleccionar NFT",
"selectNfts": "Seleccionar NFT(s)",
"selectToken": "Seleccionar token",
"selectValidator": "Seleccionar validador",
"selectWidget": "Seleccionar widget",
Expand Down Expand Up @@ -1031,7 +1031,7 @@
"blocksBehind": "Bloques detrás",
"bulkImportActionExplanation": "Elige un archivo JSON o CSV a continuación que coincida con el formato descrito en <2>esta guía</2>.",
"bulkImportActionsDescription": "Importa muchas acciones a la vez.",
"burnNftDescription": "Quemar un NFT.",
"burnNftsDescription": "Quemar NFT(s).",
"bypassSimulationExplanation": "La simulación de la propuesta falló. Si no sabes lo que esto significa, probablemente una de las acciones anteriores esté mal configurada. Si estás seguro de que deseas publicarlo de todos modos, haz clic en el botón de nuevo.",
"cannotRemoveNoneOption": "No puedes eliminar esta opción. Se agregará al final de la lista.",
"catchingUp": "Poniéndose al día...",
Expand Down Expand Up @@ -1496,7 +1496,6 @@
"to": "a",
"today": "hoy",
"token": "token",
"tokenBurned": "El NFT con ID {{tokenId}} ha sido quemado.",
"tokenCreationNoTokenFactory": "La creación de tokens no es compatible con esta cadena porque no tiene el módulo de fábrica de tokens.",
"tokenCreationUnderDevelopment": "La creación de tokens está en desarrollo y estará disponible pronto.",
"tokenDaoNotMemberInfo_dao": "No eres miembro de {{daoName}}. Para convertirte en miembro, debes obtener y apostar ${{tokenSymbol}}.",
Expand All @@ -1509,6 +1508,7 @@
"token_one": "token",
"token_other": "tokens",
"tokens": "tokens",
"tokensBurned": "Los NFTs han sido quemados: {{tokenIds}}.",
"tokensStaked": "{{amount}} ${{tokenSymbol}} depositados",
"tokensWillBeSentToTreasury": "Estos tokens serán enviados al tesoro del DAO.",
"tokensWillBeSplitAmongContributors": "{{tokens}} se dividirán entre los contribuyentes.",
Expand Down Expand Up @@ -1754,7 +1754,7 @@
"blockCompleted": "Bloque completado",
"browserWallets": "Billeteras del navegador",
"bulkImportActions": "Importar acciones en masa",
"burnNft": "Quemar NFT",
"burnNfts": "Quemar NFT(s)",
"cancelVesting": "Cancelar adquisición",
"canceled": "Cancelado",
"casting": "Casting",
Expand Down Expand Up @@ -2036,8 +2036,8 @@
"saved": "Guardado",
"scanQrCode": "Escanear código QR",
"search": "Buscar",
"selectNftToBurn": "Seleccionar NFT para quemar",
"selectNftToTransfer": "Seleccionar NFT para transferir",
"selectNftsToBurn": "Seleccionar NFT(s) para quemar",
"selectNftsToTransfer": "Seleccionar NFT(s) para transferir",
"send": "Enviar",
"setAdminToParent": "Establecer admin a {{parent}}",
"setItem": "Establecer ítem",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ Default.args = {
isCreating: true,
errors: {},
options: {
nftInfo: {
nftInfos: {
loading: false,
errored: false,
data: selected,
data: [selected],
},
options: {
loading: false,
Expand Down
122 changes: 63 additions & 59 deletions packages/stateful/actions/core/actions/BurnNft/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,84 +19,71 @@ import {
import { getNftKey } from '@dao-dao/utils'

export type BurnNftData = {
chainId: string
collection: string
tokenId: string
nfts: {
chainId: string
collection: string
tokenId: string
}[]
}

export interface BurnNftOptions {
// The set of NFTs that may be burned as part of this action.
options: LoadingDataWithError<LazyNftCardInfo[]>
// Information about the NFT currently selected. If errored, it may be burnt.
// If undefined, no NFT is selected.
nftInfo: LoadingDataWithError<NftCardInfo> | undefined
// Information about the NFTs currently selected. If errored, it may be burnt.
// If undefined, no NFTs are selected.
nftInfos: LoadingDataWithError<NftCardInfo[]> | undefined
NftSelectionModal: ComponentType<NftSelectionModalProps>
}

export const BurnNft: ActionComponent<BurnNftOptions> = ({
fieldNamePrefix,
isCreating,
errors,
options: { options, nftInfo, NftSelectionModal },
options: { options, nftInfos, NftSelectionModal },
}) => {
const { t } = useTranslation()
const { watch, setValue, setError, clearErrors } =
const { watch, setValue, getValues, setError, clearErrors } =
useFormContext<BurnNftData>()

const chainId = watch((fieldNamePrefix + 'chainId') as 'chainId')
const tokenId = watch((fieldNamePrefix + 'tokenId') as 'tokenId')
const collection = watch((fieldNamePrefix + 'collection') as 'collection')

const selectedKey = getNftKey(chainId, collection, tokenId)
const nfts = watch((fieldNamePrefix + 'nfts') as 'nfts')
const selectedKeys = nfts.map((nft) =>
getNftKey(nft.chainId, nft.collection, nft.tokenId)
)

useEffect(() => {
if (
!selectedKey ||
// If selected, make sure it exists in options.
(!options.loading &&
!options.errored &&
!options.data.some((nft) => nft.key === selectedKey))
) {
if (!errors?.collection) {
setError((fieldNamePrefix + 'collection') as 'collection', {
type: 'required',
message: t('error.noNftSelected'),
})
}
if (!nfts.length) {
setError((fieldNamePrefix + 'nfts') as 'nfts', {
type: 'required',
message: t('error.noNftSelected'),
})
} else {
if (errors?.collection) {
clearErrors((fieldNamePrefix + 'collection') as 'collection')
}
clearErrors((fieldNamePrefix + 'nfts') as 'nfts')
}
}, [
selectedKey,
setError,
clearErrors,
t,
fieldNamePrefix,
options,
errors?.collection,
])
}, [nfts.length, setError, clearErrors, t, fieldNamePrefix])

// Show modal initially if creating and no NFT already selected.
const [showModal, setShowModal] = useState<boolean>(
isCreating && !selectedKey
isCreating && !nfts.length
)

return (
<>
<div className="flex flex-col gap-2">
{nftInfo &&
(nftInfo.loading ? (
{nftInfos &&
(nftInfos.loading ? (
<HorizontalNftCardLoader />
) : !nftInfo.errored ? (
<HorizontalNftCard {...nftInfo.data} />
) : !nftInfos.errored ? (
<div className="flex flex-col gap-1">
{nftInfos.data.map(({ key, ...nftInfo }) => (
<HorizontalNftCard key={key} {...nftInfo} />
))}
</div>
) : (
// If errored loading NFT and not creating, token likely burned.
nftInfo.errored &&
nftInfos.errored &&
!isCreating && (
<p className="primary-text">
{t('info.tokenBurned', { tokenId })}
{t('info.tokensBurned', { tokenIds: selectedKeys.join(', ') })}
</p>
)
))}
Expand All @@ -105,14 +92,14 @@ export const BurnNft: ActionComponent<BurnNftOptions> = ({
<Button
className={clsx(
'text-text-tertiary',
nftInfo && !nftInfo.loading && !nftInfo.errored
nftInfos && !nftInfos.loading && !nftInfos.errored
? 'self-end'
: 'self-start'
)}
onClick={() => setShowModal(true)}
variant="secondary"
variant={nfts.length ? 'secondary' : 'primary'}
>
{t('button.selectNft')}
{t('button.selectNfts')}
</Button>
)}

Expand All @@ -127,25 +114,42 @@ export const BurnNft: ActionComponent<BurnNftOptions> = ({
onClick: () => setShowModal(false),
}}
header={{
title: t('title.selectNftToBurn'),
title: t('title.selectNftsToBurn'),
}}
nfts={options}
onClose={() => setShowModal(false)}
onNftClick={(nft) => {
if (nft.key === selectedKey) {
setValue((fieldNamePrefix + 'chainId') as 'chainId', '')
setValue((fieldNamePrefix + 'tokenId') as 'tokenId', '')
setValue((fieldNamePrefix + 'collection') as 'collection', '')
} else {
setValue((fieldNamePrefix + 'chainId') as 'chainId', nft.chainId)
setValue((fieldNamePrefix + 'tokenId') as 'tokenId', nft.tokenId)
const selected = getValues((fieldNamePrefix + 'nfts') as 'nfts')

// If the NFT is already selected, remove it.
if (
selected.some(
(n) =>
n.chainId === nft.chainId &&
n.collection === nft.collectionAddress &&
n.tokenId === nft.tokenId
)
) {
setValue(
(fieldNamePrefix + 'collection') as 'collection',
nft.collectionAddress
(fieldNamePrefix + 'nfts') as 'nfts',
nfts.filter(
(n) =>
getNftKey(n.chainId, n.collection, n.tokenId) !== nft.key
)
)
} else {
// Otherwise, add the NFT.
setValue((fieldNamePrefix + 'nfts') as 'nfts', [
...selected,
{
chainId: nft.chainId,
collection: nft.collectionAddress,
tokenId: nft.tokenId,
},
])
}
}}
selectedKeys={selectedKey ? [selectedKey] : []}
selectedKeys={selectedKeys}
visible={showModal}
/>
)}
Expand Down
Loading

0 comments on commit bcf2960

Please sign in to comment.