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): style selected visual Assets. #4085

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4074f2b
feat(wallet-dashboard): style selected visual Assets.
panteleymonchuk Nov 15, 2024
9bb5dd0
refactor(core): destructure metaKeys and metaValues from attributes
panteleymonchuk Nov 25, 2024
1f2eb82
refactor(wallet): move Collapsible component to core.
panteleymonchuk Nov 25, 2024
f9530b1
feat(dashboard): integrate useAssetsDialog for asset details view
panteleymonchuk Nov 25, 2024
934cd96
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Nov 26, 2024
d055db0
fix(assets): update import path and enhance text styling in DetailsView
panteleymonchuk Nov 26, 2024
2a2daeb
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Nov 27, 2024
6304492
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Nov 27, 2024
c22a756
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Nov 27, 2024
124ddec
Merge branch 'develop' into tooling-dashboard/style-selected-asset
panteleymonchuk Nov 28, 2024
fc404d8
refactor(wallet-dashboard): move state to page
panteleymonchuk Nov 28, 2024
7bd0c26
Merge branch 'develop' into tooling-dashboard/style-selected-asset
panteleymonchuk Nov 28, 2024
61a13f8
refactor(wallet-dashboard): rename handler functions for consistency …
panteleymonchuk Nov 28, 2024
ed3e381
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Nov 29, 2024
336468e
fix(dashboard): PR conflicts
panteleymonchuk Nov 29, 2024
4ce390b
refactor(dashboard): update state for asset view, improve code
panteleymonchuk Nov 29, 2024
16110bb
Merge branch 'develop' into tooling-dashboard/style-selected-asset
brancoder Nov 29, 2024
0bd5c50
Merge remote-tracking branch 'origin/develop' into tooling-dashboard/…
panteleymonchuk Dec 2, 2024
84b2dba
fix(wallet-dashboard): unify asset transfer success and error handlin…
panteleymonchuk Dec 2, 2024
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
4 changes: 4 additions & 0 deletions apps/core/src/components/collapsible/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 './Collapsible';
2 changes: 1 addition & 1 deletion apps/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export * from './coin';
export * from './icon';
export * from './Inputs';
export * from './QR';

export * from './collapsible';
export * from './providers';
3 changes: 3 additions & 0 deletions apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ export * from './useTransactionData';
export * from './useGetStakingValidatorDetails';
export * from './useCursorPagination';
export * from './useTheme';
export * from './useNFTBasicData';
export * from './useOwnedNFT';
export * from './useNftDetails';

export * from './stake';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { IotaObjectData } from '@iota/iota-sdk/client';
import useFileExtensionType from './useFileExtensionType';
import useMediaUrl from './useMediaUrl';

export default function useNFTBasicData(nftObj: IotaObjectData | null) {
export function useNFTBasicData(nftObj: IotaObjectData | null) {
const nftObjectID = nftObj?.objectId || null;
const filePath = useMediaUrl(nftObj?.content || null);
let objType = null;
Expand Down
93 changes: 93 additions & 0 deletions apps/core/src/hooks/useNftDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
import {
useGetNFTMeta,
useOwnedNFT,
useNFTBasicData,
useGetKioskContents,
useIsAssetTransferable,
} from './';
import { formatAddress } from '@iota/iota-sdk/utils';
import { truncateString } from '../utils';

type NftField = { keys: string[]; values: string[] };

type NftFields = {
metadata?: { fields?: { attributes?: { fields?: NftField } } };
};

export function useNftDetails(nftId: string, accountAddress: string | null) {
const { data: objectData, isPending: isNftLoading } = useOwnedNFT(nftId || '', accountAddress);
const { data } = useGetKioskContents(accountAddress);

const isContainedInKiosk = data?.lookup.get(nftId!);
const kioskItem = data?.list.find((k) => k.data?.objectId === nftId);

const { data: isTransferable } = useIsAssetTransferable(objectData);

const { nftFields } = useNFTBasicData(objectData);

const { data: nftMeta, isPending: isPendingMeta } = useGetNFTMeta(nftId);

const nftName = nftMeta?.name || formatAddress(nftId);
const nftImageUrl = nftMeta?.imageUrl || '';

// Extract either the attributes, or use the top-level NFT fields:
const { keys: metaKeys, values: metaValues } =
(nftFields as NftFields)?.metadata?.fields?.attributes?.fields ||
Object.entries(nftFields ?? {})
.filter(([key]) => key !== 'id')
.reduce<NftField>(
(acc, [key, value]) => {
acc.keys.push(key);
acc.values.push(value as string);
return acc;
},
{ keys: [], values: [] },
);

const ownerAddress =
(objectData?.owner &&
typeof objectData?.owner === 'object' &&
'AddressOwner' in objectData.owner &&
objectData.owner.AddressOwner) ||
'';

function formatMetaValue(value: string | object) {
if (typeof value === 'object') {
return {
value: JSON.stringify(value),
valueLink: undefined,
};
} else {
if (value.includes('http')) {
return {
value: value.startsWith('http')
? truncateString(value, 20, 8)
: formatAddress(value),
valueLink: value,
};
}
return {
value: value,
valueLink: undefined,
};
}
}

return {
objectData,
isNftLoading,
nftName,
nftImageUrl,
ownerAddress,
isTransferable,
metaKeys,
metaValues,
formatMetaValue,
isContainedInKiosk,
kioskItem,
nftMeta,
isPendingMeta,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useGetKioskContents, useGetObject } from '@iota/core';
import { useGetKioskContents, useGetObject } from './';
import { useMemo } from 'react';

export function useOwnedNFT(nftObjectId: string | null, address: string | null) {
Expand Down
1 change: 1 addition & 0 deletions apps/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './getDelegationDataByStakeId';
export * from './api-env';
export * from './getExplorerPaths';
export * from './getExplorerLink';
export * from './truncateString';

export * from './stake';
export * from './transaction';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ArrowDown } from '@iota/ui-icons';
import { Button, ButtonType } from '@/lib';
import { ICON_STYLE } from './accordion.classes';

interface AccordionHeaderProps {
export interface AccordionHeaderProps {
/**
* Flag for show/hide content
*/
Expand Down
10 changes: 10 additions & 0 deletions apps/wallet-dashboard/app/(protected)/assets/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IotaObjectData } from '@iota/iota-sdk/client';
import { useState } from 'react';
import { AssetCategory } from '@/lib/enums';
import { AssetList } from '@/components/AssetsList';
import { AssetDialog } from '@/components/Dialogs/Assets';

const OBJECTS_PER_REQ = 50;

Expand All @@ -25,6 +26,7 @@ const ASSET_CATEGORIES: { label: string; value: AssetCategory }[] = [
];

export default function AssetsDashboardPage(): React.JSX.Element {
const [selectedAsset, setSelectedAsset] = useState<IotaObjectData | null>(null);
const [selectedCategory, setSelectedCategory] = useState<AssetCategory>(AssetCategory.Visual);
const account = useCurrentAccount();
const { data, isFetching, fetchNextPage, hasNextPage } = useGetOwnedObjects(
Expand All @@ -49,6 +51,10 @@ export default function AssetsDashboardPage(): React.JSX.Element {
}
}

function onClickAsset(asset: IotaObjectData) {
setSelectedAsset(asset);
}

return (
<Panel>
<Title title="Assets" size={TitleSize.Medium} />
Expand All @@ -67,10 +73,14 @@ export default function AssetsDashboardPage(): React.JSX.Element {
<AssetList
assets={assets}
selectedCategory={selectedCategory}
onClick={onClickAsset}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetching}
fetchNextPage={fetchNextPage}
/>
{selectedAsset && (
<AssetDialog onClose={() => setSelectedAsset(null)} asset={selectedAsset} />
)}
</div>
</Panel>
);
Expand Down
9 changes: 8 additions & 1 deletion apps/wallet-dashboard/components/AssetsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface AssetListProps {
hasNextPage: boolean;
isFetchingNextPage: boolean;
fetchNextPage: () => void;
onClick: (asset: IotaObjectData) => void;
}

const ASSET_LAYOUT: Record<AssetCategory, string> = {
Expand All @@ -29,6 +30,7 @@ export function AssetList({
hasNextPage,
isFetchingNextPage,
fetchNextPage,
onClick,
}: AssetListProps): React.JSX.Element {
const observerElem = useRef<HTMLDivElement | null>(null);
const { isIntersecting } = useOnScreen(observerElem);
Expand All @@ -43,7 +45,12 @@ export function AssetList({
return (
<div className={cl('max-h-[600px]', ASSET_LAYOUT[selectedCategory])}>
{assets.map((asset) => (
<AssetTileLink key={asset.digest} asset={asset} type={selectedCategory} />
<AssetTileLink
key={asset.digest}
asset={asset}
type={selectedCategory}
onClick={onClick}
/>
))}
<div ref={observerElem}>
{isSpinnerVisible ? (
Expand Down
89 changes: 89 additions & 0 deletions apps/wallet-dashboard/components/Dialogs/Assets/AssetDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React, { useState } from 'react';
import { Dialog } from '@iota/apps-ui-kit';
import { FormikProvider, useFormik } from 'formik';
import { useCurrentAccount } from '@iota/dapp-kit';
import { createNftSendValidationSchema } from '@iota/core';
import { DetailsView, SendView } from './views';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { AssetsDialogView } from './constants';
import { useCreateSendAssetTransaction, useNotifications } from '@/hooks';
import { NotificationType } from '@/stores/notificationStore';

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

interface FormValues {
to: string;
}

const INITIAL_VALUES: FormValues = {
to: '',
};

export function AssetDialog({ onClose: onCloseCb, asset }: AssetsDialogProps): JSX.Element {
const [view, setView] = useState<AssetsDialogView>(AssetsDialogView.Details);
const account = useCurrentAccount();
const activeAddress = account?.address ?? '';
const objectId = asset?.objectId ?? '';
const { addNotification } = useNotifications();
const validationSchema = createNftSendValidationSchema(activeAddress, objectId);

const { mutation: sendAsset } = useCreateSendAssetTransaction(
objectId,
onSendAsset,
onSendAsset,
);

const formik = useFormik<FormValues>({
initialValues: INITIAL_VALUES,
validationSchema: validationSchema,
onSubmit: onSubmit,
validateOnChange: true,
});

function onSendAsset() {
setView(AssetsDialogView.Details);
onCloseCb();
}

async function onSubmit(values: FormValues) {
try {
await sendAsset.mutateAsync(values.to);
addNotification('Transfer transaction successful', NotificationType.Success);
} catch (error) {
console.log(error);
addNotification('Transfer transaction failed', NotificationType.Error);
}
}

function onDetailsSend() {
setView(AssetsDialogView.Send);
}

function onSendViewBack() {
setView(AssetsDialogView.Details);
}
function onClose() {
setView(AssetsDialogView.Details);
onCloseCb();
}
return (
<Dialog open onOpenChange={onClose}>
<FormikProvider value={formik}>
<>
{view === AssetsDialogView.Details && (
<DetailsView asset={asset} onClose={onClose} onSend={onDetailsSend} />
)}
{view === AssetsDialogView.Send && (
<SendView asset={asset} onClose={onClose} onBack={onSendViewBack} />
)}
</>
</FormikProvider>
</Dialog>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export enum AssetsDialogView {
Details = 'Details',
Send = 'Send',
}
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 './AssetsDialogView';
5 changes: 5 additions & 0 deletions apps/wallet-dashboard/components/Dialogs/Assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export * from './AssetDialog';
export * from './constants';
Loading
Loading