Skip to content

Commit

Permalink
feat(wallet-dashboard): show kiosk NFTs (#4610)
Browse files Browse the repository at this point in the history
* refactor(wallet): move notification to separate component.

* feat(wallet): enhance hidden asset functionality with undo option and notifications

* feat(wallet): add undo functionality for showing hidden assets with notifications

* feat(core): move HiddenAssetsProvider.

* feat(core): move useGetNFTs.

* feat(core): update useGetNFTs to accept filter and improve asset fetching

* refactor(dashboard): remove 'Hidden' asset category and related logic

* refactor(wallet-dashboard): remove 'Hidden' asset category from layout

* refactor(dashboard): remove HiddenAssets context, update default select asset logic.

* feat(core): add refetch capability to useGetNFTs hook

* refactor(core): improve hide/show logic

* refactor(core): remove undo functionality and streamline asset visibility management

* refactor(core): simplify asset visibility management and improve error handling

* refactor(wallet): rename MoveAssetNotification to MovedAssetNotification

* refactor(dashboard): adapt logic to useGetNFTs hook.

* feat(wallet-dashboard): add KioskTile component and integrate with AssetTileLink

* feat(dashboard): move KioskTile component to core and update references

* feat(dashboard): add KioskDetailsView and integrate with asset dialog flow

* feat(core, wallet): move NftImage component to core and update references in KioskDetailsView

* feat(dashboard): enhance AssetDialog and KioskDetailsView with selected asset handling

* fix(wallet-dashboard): improve asset loading logic and conditional rendering

* feat(wallet-dashboard): move logic for page to the hook

* refactor(dashboard): add back button for kiosk item details, fix send kiosk item.

* refactor(core): cleanup

* feat(wallet-dashboard): enhance KioskDetailsView layout and add item count badge
  • Loading branch information
panteleymonchuk authored Jan 20, 2025
1 parent f1c1906 commit 4e8d968
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 71 deletions.
2 changes: 2 additions & 0 deletions apps/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export * from './buttons';
export * from './collapsible';
export * from './providers';
export * from './stake';
export * from './kiosk';
export * from './nft';
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import { getKioskIdFromOwnerCap, hasDisplayData, useGetKioskContents } from '@iota/core';
import { type IotaObjectResponse } from '@iota/iota-sdk/client';
import { useActiveAddress } from '_hooks';
import { ButtonUnstyled, CardImage, ImageType, truncate } from '@iota/apps-ui-kit';
import { getKioskIdFromOwnerCap, hasDisplayData, useGetKioskContents } from '../..';
import { type IotaObjectData, type IotaObjectResponse } from '@iota/iota-sdk/client';
import {
ButtonUnstyled,
CardImage,
ImageType,
truncate,
LoadingIndicator,
} from '@iota/apps-ui-kit';
import { PlaceholderReplace } from '@iota/apps-ui-icons';

interface KioskProps {
object: IotaObjectResponse;
interface KioskTileProps {
object: IotaObjectResponse | IotaObjectData;
address?: string | null;
onClick?: () => void;
}

export function Kiosk({ object }: KioskProps) {
const address = useActiveAddress();
export function KioskTile({ object, address, onClick }: KioskTileProps) {
const { data: kioskData, isPending } = useGetKioskContents(address);

const kioskId = getKioskIdFromOwnerCap(object);
Expand All @@ -25,10 +31,18 @@ export function Kiosk({ object }: KioskProps) {
? null
: itemsWithDisplay[0].data?.display?.data?.image_url || null;

if (isPending) return null;
if (isPending)
return (
<div className="group relative aspect-square w-full cursor-pointer overflow-hidden rounded-xl flex items-center justify-center">
<LoadingIndicator />
</div>
);

return (
<div className="group relative aspect-square w-full cursor-pointer overflow-hidden rounded-xl">
<div
onClick={onClick}
className="group relative aspect-square w-full cursor-pointer overflow-hidden rounded-xl"
>
<div
className={
'group relative aspect-square w-full cursor-pointer overflow-hidden rounded-xl'
Expand Down
4 changes: 4 additions & 0 deletions apps/core/src/components/kiosk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export * from './KioskTile';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { CardImage, ImageType, VisualAssetCard, VisualAssetType } from '@iota/apps-ui-kit';
import { PlaceholderReplace } from '@iota/apps-ui-icons';
import cx from 'clsx';

export interface NftImageProps {
src: string | null;
Expand Down Expand Up @@ -33,7 +34,14 @@ export function NftImage({ src, title, isHoverable, video, icon, onIconClick }:
}
if (!imgSrc) {
return (
<div className="relative flex aspect-square h-full w-full items-center justify-center overflow-hidden rounded-xl">
<div
className={cx(
'relative flex aspect-square h-full w-full items-center justify-center overflow-hidden rounded-xl',
{
'group cursor-pointer': isHoverable,
},
)}
>
{isHoverable && (
<div className="absolute left-0 top-0 h-full w-full bg-cover bg-center bg-no-repeat group-hover:bg-shader-neutral-light-48 group-hover:transition group-hover:duration-300 group-hover:ease-in-out group-hover:dark:bg-shader-primary-dark-48" />
)}
Expand Down
4 changes: 4 additions & 0 deletions apps/core/src/components/nft/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export * from './NftImage';
64 changes: 55 additions & 9 deletions apps/wallet-dashboard/components/dialogs/assets/AssetDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import { useState } from 'react';
import { Dialog } from '@iota/apps-ui-kit';
import { FormikProvider, useFormik } from 'formik';
import { useIotaClient, useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit';
import { createNftSendValidationSchema, useTransferAsset } from '@iota/core';
import { DetailsView, SendView } from './views';
import {
createNftSendValidationSchema,
useTransferAsset,
isKioskOwnerToken,
useKioskClient,
useNftDetails,
} from '@iota/core';
import { DetailsView, SendView, KioskDetailsView } from './views';
import { IotaObjectData, IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import { AssetsDialogView } from './constants';
import { TransactionDetailsView } from '../send-token';
Expand All @@ -28,17 +34,29 @@ const INITIAL_VALUES: FormValues = {
};

export function AssetDialog({ onClose, asset, refetchAssets }: AssetsDialogProps): JSX.Element {
const [view, setView] = useState<AssetsDialogView>(AssetsDialogView.Details);
const kioskClient = useKioskClient();
const account = useCurrentAccount();
const [digest, setDigest] = useState<string>('');
const activeAddress = account?.address ?? '';
const objectId = asset?.objectId ?? '';
const iotaClient = useIotaClient();
const validationSchema = createNftSendValidationSchema(activeAddress, objectId);
const { mutateAsync: signAndExecuteTransaction } =
useSignAndExecuteTransaction<IotaTransactionBlockResponse>();

const isTokenOwnedByKiosk = isKioskOwnerToken(kioskClient.network, asset);
const activeAddress = account?.address ?? '';

const initView = isTokenOwnedByKiosk ? AssetsDialogView.KioskDetails : AssetsDialogView.Details;

const [view, setView] = useState<AssetsDialogView>(initView);
const [chosenKioskAsset, setChoosenKioskAsset] = useState<IotaObjectData | null>(null);
const [digest, setDigest] = useState<string>('');

const activeAsset = chosenKioskAsset || asset;
const objectId = chosenKioskAsset ? chosenKioskAsset.objectId : asset ? asset.objectId : '';
const validationSchema = createNftSendValidationSchema(activeAddress, objectId);
const { objectData } = useNftDetails(objectId, activeAddress);

const { mutateAsync: sendAsset } = useTransferAsset({
objectId,
objectType: objectData?.type,
activeAddress: activeAddress,
executeFn: signAndExecuteTransaction,
});
Expand Down Expand Up @@ -76,19 +94,47 @@ export function AssetDialog({ onClose, asset, refetchAssets }: AssetsDialogProps
}
function onOpenChange() {
setView(AssetsDialogView.Details);
setChoosenKioskAsset(null);
onClose();
}

function onKioskItemClick(item: IotaObjectData) {
setChoosenKioskAsset(item);
setView(AssetsDialogView.Details);
}

function onBack() {
if (!chosenKioskAsset) {
return;
}
setChoosenKioskAsset(null);
setView(AssetsDialogView.KioskDetails);
}

return (
<Dialog open onOpenChange={onOpenChange}>
<DialogLayout>
<>
{view === AssetsDialogView.KioskDetails && (
<KioskDetailsView
asset={activeAsset}
onClose={onOpenChange}
onItemClick={onKioskItemClick}
/>
)}
{view === AssetsDialogView.Details && (
<DetailsView asset={asset} onClose={onOpenChange} onSend={onDetailsSend} />
<DetailsView
asset={activeAsset}
onClose={onOpenChange}
onSend={onDetailsSend}
onBack={onBack}
/>
)}
{view === AssetsDialogView.Send && (
<FormikProvider value={formik}>
<SendView
asset={asset}
objectId={objectId}
senderAddress={activeAddress}
onClose={onOpenChange}
onBack={onSendViewBack}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export enum AssetsDialogView {
Details = 'Details',
Send = 'Send',
TransactionDetails = 'TransactionDetails',
KioskDetails = 'KioskDetails',
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { ExplorerLinkType, useNftDetails, Collapsible, useNFTBasicData } from '@iota/core';
import {
Button,
ButtonType,
Header,
KeyValueInfo,
VisualAssetCard,
VisualAssetType,
} from '@iota/apps-ui-kit';
ExplorerLinkType,
useNftDetails,
Collapsible,
useNFTBasicData,
NftImage,
} from '@iota/core';
import { Button, ButtonType, Header, KeyValueInfo } from '@iota/apps-ui-kit';
import { formatAddress } from '@iota/iota-sdk/utils';
import { DialogLayoutBody, DialogLayoutFooter } from '../../layout';
import { IotaObjectData } from '@iota/iota-sdk/client';
Expand All @@ -20,9 +19,10 @@ interface DetailsViewProps {
asset: IotaObjectData;
onClose: () => void;
onSend: () => void;
onBack?: () => void;
}

export function DetailsView({ onClose, asset, onSend }: DetailsViewProps) {
export function DetailsView({ onClose, asset, onSend, onBack }: DetailsViewProps) {
const account = useCurrentAccount();

const senderAddress = account?.address ?? '';
Expand Down Expand Up @@ -53,17 +53,11 @@ export function DetailsView({ onClose, asset, onSend }: DetailsViewProps) {

return (
<>
<Header title="Asset" onClose={onClose} titleCentered />
<Header title="Asset" onClose={onClose} titleCentered onBack={onBack} />
<DialogLayoutBody>
<div className="flex w-full flex-col items-center justify-center gap-xs">
<div className="w-[172px]">
<VisualAssetCard
assetSrc={nftImageUrl}
assetTitle={nftName}
assetType={VisualAssetType.Image}
altText={nftName || 'NFT'}
isHoverable={false}
/>
<NftImage src={nftImageUrl} title={nftName || 'NFT'} isHoverable={false} />
</div>
<ExplorerLink type={ExplorerLinkType.Object} objectID={objectId}>
<Button type={ButtonType.Ghost} text="View on Explorer" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
useGetKioskContents,
getKioskIdFromOwnerCap,
useNftDetails,
NftImage,
ExplorerLinkType,
ViewTxnOnExplorerButton,
} from '@iota/core';
import { Badge, BadgeType, Header, LoadingIndicator } from '@iota/apps-ui-kit';
import { DialogLayoutBody, DialogLayoutFooter } from '../../layout';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { useCurrentAccount } from '@iota/dapp-kit';
import { ExplorerLink } from '@/components/ExplorerLink';

interface DetailsViewProps {
asset: IotaObjectData;
onClose: () => void;
onItemClick: (asset: IotaObjectData) => void;
}

export function KioskDetailsView({ onClose, asset, onItemClick }: DetailsViewProps) {
const account = useCurrentAccount();
const senderAddress = account?.address ?? '';
const objectId = getKioskIdFromOwnerCap(asset);
const { data: kioskData, isPending } = useGetKioskContents(account?.address);
const kiosk = kioskData?.kiosks.get(objectId);
const items = kiosk?.items;

if (isPending) {
return (
<div className="flex h-full items-center justify-center">
<LoadingIndicator />
</div>
);
}

return (
<>
<Header title="Kiosk" onClose={onClose} titleCentered />
<DialogLayoutBody>
<div className="flex flex-col gap-md">
<div className="flex flex-row gap-x-sm">
<span className="text-title-lg text-neutral-10 dark:text-neutral-92">
Kiosk items
</span>
<Badge type={BadgeType.Neutral} label={items?.length.toString() ?? '0'} />
</div>
<div className="grid grid-cols-3 items-center justify-center gap-sm">
{items?.map((item) => {
return item.data?.objectId ? (
<div
onClick={() => {
item.data && onItemClick(item.data);
}}
key={item.data?.objectId}
>
<KioskItem object={item.data} address={senderAddress} />
</div>
) : null;
})}
</div>
</div>
</DialogLayoutBody>
<DialogLayoutFooter>
<ExplorerLink objectID={objectId} type={ExplorerLinkType.Object}>
<ViewTxnOnExplorerButton digest={objectId} />
</ExplorerLink>
</DialogLayoutFooter>
</>
);
}

interface KioskItemProps {
object: IotaObjectData;
address: string;
}

function KioskItem({ object, address }: KioskItemProps) {
const { nftName, nftImageUrl } = useNftDetails(object.objectId, address);

return <NftImage title={nftName} src={nftImageUrl} isHoverable />;
}
33 changes: 7 additions & 26 deletions apps/wallet-dashboard/components/dialogs/assets/views/SendView.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,30 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { AddressInput, useNftDetails } from '@iota/core';
import { AddressInput, NftImage, useNftDetails } from '@iota/core';
import { useFormikContext } from 'formik';
import { DialogLayoutFooter, DialogLayoutBody } from '../../layout';
import {
Button,
ButtonHtmlType,
Header,
VisualAssetCard,
VisualAssetType,
Title,
} from '@iota/apps-ui-kit';
import { Button, ButtonHtmlType, Header, Title } from '@iota/apps-ui-kit';
import { Loader } from '@iota/apps-ui-icons';
import { useCurrentAccount } from '@iota/dapp-kit';
import { IotaObjectData } from '@iota/iota-sdk/client';

interface SendViewProps {
asset: IotaObjectData;
objectId: string;
senderAddress: string;
onClose: () => void;
onBack: () => void;
}

export function SendView({ asset, onClose, onBack }: SendViewProps) {
export function SendView({ objectId, senderAddress, onClose, onBack }: SendViewProps) {
const { isValid, dirty, isSubmitting, submitForm } = useFormikContext();

const account = useCurrentAccount();

const senderAddress = account?.address ?? '';
const objectId = asset?.objectId || '';

const { nftName, nftImageUrl } = useNftDetails(objectId, senderAddress);

return (
<>
<Header title="Send asset" onClose={onClose} titleCentered onBack={onBack} />
<DialogLayoutBody>
<div className="flex w-full flex-col items-center justify-center gap-xs">
<div className="w-[172px]">
<VisualAssetCard
assetSrc={nftImageUrl}
assetTitle={nftName}
assetType={VisualAssetType.Image}
altText={nftName || 'NFT'}
isHoverable={false}
/>
<NftImage src={nftImageUrl} title={nftName || 'NFT'} isHoverable={false} />
</div>
<div className="flex w-full flex-col gap-md">
<div className="flex flex-col items-center gap-xxxs">
Expand Down
Loading

0 comments on commit 4e8d968

Please sign in to comment.