Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet-dashboard): show kiosk NFTs #4610

Open
wants to merge 5 commits into
base: tooling-dashboard/improve-asset-filter
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/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 @@ -2,8 +2,10 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

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

export interface NftImageProps {
src: string | null;
Expand Down Expand Up @@ -33,7 +35,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';
1 change: 1 addition & 0 deletions apps/wallet-dashboard/app/(protected)/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export default function AssetsDashboardPage(): React.JSX.Element {
<AssetDialog
onClose={() => setSelectedAsset(null)}
asset={selectedAsset}
setSelectedAsset={setSelectedAsset}
refetchAssets={refetch}
/>
)}
Expand Down
32 changes: 28 additions & 4 deletions apps/wallet-dashboard/components/Dialogs/Assets/AssetDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import React, { useState } from 'react';
import { Dialog } from '@iota/apps-ui-kit';
import { FormikProvider, useFormik } from 'formik';
import { useIotaClient, useCurrentAccount } from '@iota/dapp-kit';
import { createNftSendValidationSchema } from '@iota/core';
import { DetailsView, SendView } from './views';
import { createNftSendValidationSchema, isKioskOwnerToken, useKioskClient } from '@iota/core';
import { DetailsView, SendView, KioskDetailsView } from './views';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { AssetsDialogView } from './constants';
import { useCreateSendAssetTransaction, useNotifications } from '@/hooks';
Expand All @@ -17,6 +17,7 @@ import { DialogLayout } from '../layout';
interface AssetsDialogProps {
onClose: () => void;
asset: IotaObjectData;
setSelectedAsset: (asset: IotaObjectData) => void;
refetchAssets: () => void;
}

Expand All @@ -28,8 +29,18 @@ const INITIAL_VALUES: FormValues = {
to: '',
};

export function AssetDialog({ onClose, asset, refetchAssets }: AssetsDialogProps): JSX.Element {
const [view, setView] = useState<AssetsDialogView>(AssetsDialogView.Details);
export function AssetDialog({
onClose,
asset,
setSelectedAsset,
refetchAssets,
}: AssetsDialogProps): JSX.Element {
const kioskClient = useKioskClient();
const isOwnerToken = isKioskOwnerToken(kioskClient.network, asset);

const initView = isOwnerToken ? AssetsDialogView.KioskDetails : AssetsDialogView.Details;
const [view, setView] = useState<AssetsDialogView>(initView);

const account = useCurrentAccount();
const [digest, setDigest] = useState<string>('');
const activeAddress = account?.address ?? '';
Expand Down Expand Up @@ -75,10 +86,23 @@ export function AssetDialog({ onClose, asset, refetchAssets }: AssetsDialogProps
setView(AssetsDialogView.Details);
onClose();
}

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

return (
<Dialog open onOpenChange={onOpenChange}>
<DialogLayout>
<>
{view === AssetsDialogView.KioskDetails && (
<KioskDetailsView
asset={asset}
onClose={onOpenChange}
onItemClick={onKioskItemClick}
/>
)}
{view === AssetsDialogView.Details && (
<DetailsView asset={asset} onClose={onOpenChange} onSend={onDetailsSend} />
)}
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
Expand Up @@ -2,15 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
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 Link from 'next/link';
import { formatAddress } from '@iota/iota-sdk/utils';
import { DialogLayoutBody, DialogLayoutFooter } from '../../layout';
Expand Down Expand Up @@ -60,13 +59,7 @@ export function DetailsView({ onClose, asset, onSend }: DetailsViewProps) {
<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,94 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import {
useGetKioskContents,
getKioskIdFromOwnerCap,
useNftDetails,
NftImage,
Collapsible,
ExplorerLinkType,
} from '@iota/core';
import { Header, KeyValueInfo, 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';
import { formatAddress } from '@iota/iota-sdk/utils';

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="mb-auto grid grid-cols-3 items-center justify-center gap-3">
{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>
</DialogLayoutBody>
<DialogLayoutFooter>
<Collapsible defaultOpen title="Details">
<div className="flex flex-col gap-y-sm px-md py-xs">
<KeyValueInfo
keyText="Number of Items"
value={items?.length || '0'}
fullwidth
/>
<KeyValueInfo
keyText="Kiosk ID"
value={
<ExplorerLink objectID={objectId!} type={ExplorerLinkType.Object}>
{formatAddress(objectId!)}
</ExplorerLink>
}
fullwidth
/>
</div>
</Collapsible>
</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 />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from './DetailsView';
export * from './SendView';
export * from './KioskDetailsView';
9 changes: 8 additions & 1 deletion apps/wallet-dashboard/components/tiles/AssetTileLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

'use client';

import { isKioskOwnerToken, useKioskClient, KioskTile } from '@iota/core';
import { AssetCategory } from '@/lib/enums';
import { VisibilityOff } from '@iota/ui-icons';
import { VisualAssetTile } from '.';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { NonVisualAssetCard } from './NonVisualAssetTile';
import { useCurrentAccount } from '@iota/dapp-kit';

interface AssetTileLinkProps {
asset: IotaObjectData;
Expand All @@ -16,13 +18,18 @@ interface AssetTileLinkProps {
}

export function AssetTileLink({ asset, type, onClick }: AssetTileLinkProps): React.JSX.Element {
const account = useCurrentAccount();
const kioskClient = useKioskClient();
const isOwnerToken = isKioskOwnerToken(kioskClient.network, asset);
function handleClick() {
onClick(asset);
}

return (
<>
{type === AssetCategory.Visual ? (
{type === AssetCategory.Visual && isOwnerToken ? (
<KioskTile object={asset} address={account?.address} onClick={handleClick} />
) : type === AssetCategory.Visual ? (
<VisualAssetTile asset={asset} icon={<VisibilityOff />} onClick={handleClick} />
) : (
<NonVisualAssetCard asset={asset} />
Expand Down
1 change: 0 additions & 1 deletion apps/wallet/src/ui/app/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export * from './menu';
export * from './navigation';
export * from './network-selector';
export * from './nft-display';
export * from './nft-display/NftImage';
export * from './receipt-card';
export * from './receipt-card/TxnAmount';
export * from './transactions-card';
Expand Down
17 changes: 13 additions & 4 deletions apps/wallet/src/ui/app/components/nft-display/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Loading, NftImage } from '_components';
import { isKioskOwnerToken, useGetNFTDisplay, useGetObject, useKioskClient } from '@iota/core';
import { Loading } from '_components';
import {
NftImage,
isKioskOwnerToken,
useGetNFTDisplay,
useGetObject,
useKioskClient,
KioskTile,
} from '@iota/core';
import { formatAddress } from '@iota/iota-sdk/utils';
import { cva } from 'class-variance-authority';
import type { VariantProps } from 'class-variance-authority';
import { useResolveVideo } from '../../hooks/useResolveVideo';
import { Kiosk } from './Kiosk';

import { useActiveAddress } from '../../hooks';

const nftDisplayCardStyles = cva('flex flex-nowrap items-center h-full relative', {
variants: {
Expand Down Expand Up @@ -48,13 +56,14 @@ export function NFTDisplayCard({
const video = useResolveVideo(objectData);
const kioskClient = useKioskClient();
const isOwnerToken = isKioskOwnerToken(kioskClient.network, objectData);
const address = useActiveAddress();

return (
<div className={nftDisplayCardStyles({ isHoverable, wideView })}>
<Loading loading={isPending}>
<div className="flex w-full flex-col justify-center gap-sm text-center">
{objectData?.data && isOwnerToken ? (
<Kiosk object={objectData} />
<KioskTile object={objectData} address={address} />
) : (
<NftImage
title={nftName}
Expand Down
Loading