diff --git a/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.stories.tsx b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.stories.tsx new file mode 100644 index 00000000..52eaf6c7 --- /dev/null +++ b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, Story } from '@storybook/react' +import React from 'react' + +import IconButtonWithTooltip from './IconButtonWithTooltip' + +export default { + component: IconButtonWithTooltip, + title: 'Components/IconButtonWithTooltip', +} as Meta + +const Template: Story = ({ icon, onClick, description }) => ( + onClick()}> + {description} + +) + +export const IconButtonWithTooltipStory = Template.bind({}) + +IconButtonWithTooltipStory.args = { + icon: ICON, + onClick: () => console.log('action'), + description: 'Tooltip content', +} diff --git a/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.tsx b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.tsx new file mode 100644 index 00000000..14da1017 --- /dev/null +++ b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.tsx @@ -0,0 +1,47 @@ +import React, { FC, ReactNode, useState } from 'react' + +import { TooltipType } from '../../../types' + +interface Props { + icon: JSX.Element + type?: TooltipType + children: ReactNode + disabled?: boolean + onClick: () => void +} + +const IconButtonWithTooltip: FC = ({ icon, children, disabled = false, onClick }) => { + const [hover, setHover] = useState(false) + + const handleMouseIn = () => !disabled && setHover(true) + + const handleMouseOut = () => !disabled && setHover(false) + + const styles = hover ? 'opacity-100 visible duration-100' : 'delay-300 opacity-0 invisible' + + return ( +
+ +
+ {children} +
+
+
+ ) +} + +export default IconButtonWithTooltip diff --git a/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.ui.test.tsx b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.ui.test.tsx new file mode 100644 index 00000000..e33c8fa4 --- /dev/null +++ b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/IconButtonWithTooltip.ui.test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from '@testing-library/react' +import React from 'react' + +import IconButtonWithTooltip from './IconButtonWithTooltip' + +describe('atoms/IconButtonWithTooltip', () => { + describe('render', () => { + it('should render IconButtonWithTooltip with content', () => { + const content = 'Tooltip content' + // when ... rendering component + render( + Button} onClick={() => {}}> + {content} + , + ) + const IconButtonWithTooltipElement = screen.getByText(content) + + // then ... should render as expected + expect(IconButtonWithTooltipElement).toBeInTheDocument() + }) + }) +}) diff --git a/apps/design-system/src/components/Atoms/IconButtonWithTooltip/index.ts b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/index.ts new file mode 100644 index 00000000..930d623a --- /dev/null +++ b/apps/design-system/src/components/Atoms/IconButtonWithTooltip/index.ts @@ -0,0 +1 @@ +export { default as IconButtonWithTooltip } from './IconButtonWithTooltip' diff --git a/apps/design-system/src/index.ts b/apps/design-system/src/index.ts index 41c31e25..09dc8163 100644 --- a/apps/design-system/src/index.ts +++ b/apps/design-system/src/index.ts @@ -18,6 +18,7 @@ export { export { Grid, GridRow } from './components/Atoms/Grid' export { Heading } from './components/Atoms/Heading' export { HeadingWithTooltip } from './components/Atoms/HeadingWithTooltip' +export { IconButtonWithTooltip } from './components/Atoms/IconButtonWithTooltip' export { LoadingIndicator } from './components/Atoms/LoadingIndicator' export { MemberProfileCard } from './components/Molecules/MemberProfileCard' export { Nav, NavItem } from './components/Atoms/Nav' diff --git a/apps/envited.ascs.digital/common/serverActions/assets/insert.ts b/apps/envited.ascs.digital/common/serverActions/assets/insert.ts index 2d58bde2..0e45b083 100644 --- a/apps/envited.ascs.digital/common/serverActions/assets/insert.ts +++ b/apps/envited.ascs.digital/common/serverActions/assets/insert.ts @@ -11,7 +11,7 @@ import { forbiddenError, formatError, internalServerErrorError, unauthorizedErro export const _insert = ({ db, getServerSession, log }: { db: Database; getServerSession: () => Promise; log: Log }) => - async ({ cid, name }: { cid: string, name: string }) => { + async ({ cid, name }: { cid: string; name: string }) => { try { const session = await getServerSession() if (isNil(session)) { diff --git a/apps/envited.ascs.digital/common/types/index.ts b/apps/envited.ascs.digital/common/types/index.ts index 606b1bce..0b0a5a31 100644 --- a/apps/envited.ascs.digital/common/types/index.ts +++ b/apps/envited.ascs.digital/common/types/index.ts @@ -1,4 +1,15 @@ -export { Language, AssetStatus, ButtonType, Columns, Size, ColorScheme, Role, CredentialType, FileType } from './types' +export { + Language, + AssetAction, + AssetStatus, + ButtonType, + Columns, + Size, + ColorScheme, + Role, + CredentialType, + FileType, +} from './types' export type { Action, Asset, diff --git a/apps/envited.ascs.digital/common/types/types.ts b/apps/envited.ascs.digital/common/types/types.ts index aae94181..1cbd0ad5 100644 --- a/apps/envited.ascs.digital/common/types/types.ts +++ b/apps/envited.ascs.digital/common/types/types.ts @@ -18,6 +18,12 @@ export enum AssetStatus { completed = 'completed', } +export enum AssetAction { + mint = 'mint', + view = 'view', + delete = 'delete', +} + export enum Columns { two = 'two', three = 'three', @@ -78,6 +84,7 @@ export interface Asset { metadata: AssetMetadata status: AssetStatus userId: string + createdAt: Date } export interface AssetMetadata { diff --git a/apps/envited.ascs.digital/common/utils/index.ts b/apps/envited.ascs.digital/common/utils/index.ts index d5566af3..a5f1f30a 100644 --- a/apps/envited.ascs.digital/common/utils/index.ts +++ b/apps/envited.ascs.digital/common/utils/index.ts @@ -18,6 +18,7 @@ export { extractIdFromCredential, extractIssuerIdFromCredential, extractTypeFromCredential, + formatDate, formatTokenAttributes, getImageUrl, slugify, @@ -27,5 +28,6 @@ export { slugToLabel, isTrustAnchor, truncateDID, + truncateCID, isServer, } from './utils' diff --git a/apps/envited.ascs.digital/common/utils/utils.ts b/apps/envited.ascs.digital/common/utils/utils.ts index d85af8fc..b6f00b01 100644 --- a/apps/envited.ascs.digital/common/utils/utils.ts +++ b/apps/envited.ascs.digital/common/utils/utils.ts @@ -76,6 +76,8 @@ export const truncate = (length: number) => export const truncateDID = truncate(20) +export const truncateCID = truncate(10) + export const isServer = () => typeof window === 'undefined' export const addUrn = (type: string) => (uuid: string) => `urn:${type}:${uuid}` @@ -106,3 +108,7 @@ export const formatTokenAttributes = (data: any) => { return result } + +export const formatDate = (date: Date) => { + return new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) +} diff --git a/apps/envited.ascs.digital/modules/AddAssets/AddAssets.actions.ts b/apps/envited.ascs.digital/modules/AddAssets/AddAssets.actions.ts index 135925cd..2418048d 100644 --- a/apps/envited.ascs.digital/modules/AddAssets/AddAssets.actions.ts +++ b/apps/envited.ascs.digital/modules/AddAssets/AddAssets.actions.ts @@ -3,13 +3,13 @@ import { revalidatePath } from 'next/cache' import { isNil } from 'ramda' +import { createFilename } from '../../common/asset/utils' import { getServerSession } from '../../common/auth' -import { getAssetUploadUrl, getUniqueFilename } from '../../common/aws' +import { getAssetUploadUrl } from '../../common/aws' import { ERRORS } from '../../common/constants' import { log } from '../../common/logger' import { insertAsset } from '../../common/serverActions' import { badRequestError, formatError, internalServerErrorError, slugify, unauthorizedError } from '../../common/utils' -import { createFilename } from 'apps/envited.ascs.digital/common/asset/utils' export async function addAssetsForm(formData: FormData) { const assets = formData.getAll('assets') as File[] @@ -44,7 +44,7 @@ export async function addAssetsForm(formData: FormData) { await insertAsset({ cid, - name: asset.name, + name: asset.name, }) return uploadResult diff --git a/apps/envited.ascs.digital/modules/Assets/Assets.tsx b/apps/envited.ascs.digital/modules/Assets/Assets.tsx index eae880c9..ce3b134c 100644 --- a/apps/envited.ascs.digital/modules/Assets/Assets.tsx +++ b/apps/envited.ascs.digital/modules/Assets/Assets.tsx @@ -169,46 +169,39 @@ export const Assets: FC = ({ items }) => {
- {map( - ({ - id, - displayUri, - name, - description, - }: Token) => ( -
-
- {name} -
-
-

{id}

-

- - -

-

{description}

- + {map(({ id, displayUri, name, description }: Token) => ( +
+
+ {name} +
+
+

{id}

+

+ + +

+

{description}

+
- ), - )(items)} +
+ ))(items)}
diff --git a/apps/envited.ascs.digital/modules/Mint/Mint.tsx b/apps/envited.ascs.digital/modules/Mint/Mint.tsx index c8aa5749..943d6efb 100644 --- a/apps/envited.ascs.digital/modules/Mint/Mint.tsx +++ b/apps/envited.ascs.digital/modules/Mint/Mint.tsx @@ -1,5 +1,7 @@ 'use client' +import { IconButtonWithTooltip } from '@envited-x-data-space/design-system' +import { RocketLaunchIcon } from '@heroicons/react/24/outline' import React, { FC } from 'react' import { useTranslation } from '../../common/i18n' @@ -10,9 +12,10 @@ import { ShowSpecificBeaconWallets } from './Mint.utils' interface MintProps { assetId: string + disabled: boolean } -export const Mint: FC = ({ assetId }) => { +export const Mint: FC = ({ assetId, disabled }) => { const { t } = useTranslation('Mint') const { error, success } = useNotification() @@ -40,12 +43,12 @@ export const Mint: FC = ({ assetId }) => { } } return ( - + ) } diff --git a/apps/envited.ascs.digital/modules/UploadedAsset/UploadAsset.constants.ts b/apps/envited.ascs.digital/modules/UploadedAsset/UploadAsset.constants.ts new file mode 100644 index 00000000..b3b3be8f --- /dev/null +++ b/apps/envited.ascs.digital/modules/UploadedAsset/UploadAsset.constants.ts @@ -0,0 +1,9 @@ +import { AssetAction, AssetStatus } from '../../common/types' + +export const enabledActionsMap = { + [AssetStatus.processing]: [], + [AssetStatus.rejected]: [AssetAction.delete], + [AssetStatus.minted]: [AssetAction.view], + [AssetStatus.completed]: [AssetAction.view], + [AssetStatus.pending]: [AssetAction.mint, AssetAction.delete], +} diff --git a/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.buttons.tsx b/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.buttons.tsx new file mode 100644 index 00000000..5506a0bb --- /dev/null +++ b/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.buttons.tsx @@ -0,0 +1,71 @@ +'use client' + +import { IconButtonWithTooltip } from '@envited-x-data-space/design-system' +import { EyeIcon, TrashIcon } from '@heroicons/react/24/outline' +import { useRouter } from 'next/navigation' +import { includes } from 'ramda' +import { FC } from 'react' + +import { useTranslation } from '../../common/i18n' +import { useNotification } from '../../common/notifications' +import { AssetAction, AssetStatus } from '../../common/types' +import { Mint } from '../Mint' +import { enabledActionsMap } from './UploadAsset.constants' +import { deleteAsset } from './UploadedAsset.actions' + +interface UploadedAssetProps { + id: string + status: AssetStatus +} + +export const View: FC<{ id: string; disabled: boolean }> = ({ id, disabled }) => { + const { t } = useTranslation('UploadedAsset') + const router = useRouter() + + return ( + + ) +} + +export const Delete: FC<{ id: string; disabled: boolean }> = ({ id, disabled }) => { + const { t } = useTranslation('UploadedAsset') + const { error, success } = useNotification() + + const cancel = async (id: string) => { + try { + await deleteAsset(id) + success(t('[Notification] asset deleted')) + } catch (e) { + error(t('[Notification] error deleting asset')) + } + } + + return ( + + ) +} + +export const UploadedAssetButtons: FC = ({ id, status }) => { + return ( + + + + + + + ) +} diff --git a/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.tsx b/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.tsx index 41ded052..ee49e556 100644 --- a/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.tsx +++ b/apps/envited.ascs.digital/modules/UploadedAsset/UploadedAsset.tsx @@ -1,27 +1,24 @@ 'use client' import { LoadingIndicator } from '@envited-x-data-space/design-system' -import { TrashIcon } from '@heroicons/react/24/outline' -import { useSession } from 'next-auth/react' -import { equals, last, propOr } from 'ramda' +import { CheckIcon, EllipsisHorizontalIcon, XMarkIcon } from '@heroicons/react/24/outline' +import { equals } from 'ramda' import { FC, useEffect, useState } from 'react' import { match } from 'ts-pattern' import { useTranslation } from '../../common/i18n' -import { useNotification } from '../../common/notifications' -import { Asset, AssetMetadata, AssetStatus } from '../../common/types' -import { Mint } from '../Mint' -import { deleteAsset, getAsset } from './UploadedAsset.actions' +import { Asset, AssetStatus } from '../../common/types' +import { formatDate, truncateCID } from '../../common/utils' +import { getAsset } from './UploadedAsset.actions' +import { UploadedAssetButtons } from './UploadedAsset.buttons' interface UploadedAssetProps { assetIdx: number asset: Asset - metadata: AssetMetadata } -export const UploadedAsset: FC = ({ assetIdx, asset, metadata }) => { +export const UploadedAsset: FC = ({ assetIdx, asset }) => { const { t } = useTranslation('UploadedAsset') - const { error, success } = useNotification() const [assetStatus, setAssetStatus] = useState(asset.status) useEffect(() => { @@ -50,29 +47,13 @@ export const UploadedAsset: FC = ({ assetIdx, asset, metadat } }, [asset.id, assetStatus]) - const cancel = async (id: string) => { - try { - await deleteAsset(id) - success(t('[Notification] asset deleted')) - } catch (e) { - error(t('[Notification] error deleting asset')) - } - } - return (
- {asset.name}
- {asset.cid} -
-
- -
-
- {propOr('', 'type')(metadata)} - ยท - {propOr('', 'size')(metadata)} + {asset.name} +
+ {truncateCID(asset.cid)}
{assetIdx !== 0 ?
: null} @@ -81,12 +62,12 @@ export const UploadedAsset: FC = ({ assetIdx, asset, metadat equals(assetIdx)(0) ? '' : 'border-t border-gray-200' } hidden px-3 py-3.5 text-sm text-gray-500 lg:table-cell`} > - {equals(assetStatus)(AssetStatus.processing) ? <>… : last(propOr('', 'tags')(metadata))} + {equals(asset.status)(AssetStatus.processing) ? <>… : formatDate(asset.createdAt)} {match(assetStatus) .with(AssetStatus.processing, () => ( @@ -95,31 +76,32 @@ export const UploadedAsset: FC = ({ assetIdx, asset, metadat

{t('[Status] processing')}

)) - .with(AssetStatus.minted, () => {t('[Status] minted')}) - .with(AssetStatus.rejected, () => ( -
- {t('[Status] rejected')} - + .with(AssetStatus.pending, () => ( +
+
)) - .otherwise(() => ( -
- - + .with(AssetStatus.minted, () => ( +
+
- ))} + )) + .with(AssetStatus.rejected, () => ( +
+
+ )) + .otherwise(() => '')} + + + {!equals(assetIdx)(0) ?
: null} diff --git a/apps/envited.ascs.digital/modules/UploadedAsset/locales/de_DE.json b/apps/envited.ascs.digital/modules/UploadedAsset/locales/de_DE.json index ff511f24..713b9f35 100644 --- a/apps/envited.ascs.digital/modules/UploadedAsset/locales/de_DE.json +++ b/apps/envited.ascs.digital/modules/UploadedAsset/locales/de_DE.json @@ -1,8 +1,10 @@ { - "[Status] minted": "Completed", + "[Status] minted": "Minted", "[Status] processing": "Processing...", + "[Status] pending": "Ready for minting", "[Status] rejected": "Rejected", "[Button] delete": "Cancel", - "[Notification] error deleting asset": "Something went wrong", + "[Button] view": "View", + "[Notification] error deleting asset": "Cannot delete asset", "[Notification] asset deleted": "Asset successfully cancelled" } diff --git a/apps/envited.ascs.digital/modules/UploadedAsset/locales/en_GB.json b/apps/envited.ascs.digital/modules/UploadedAsset/locales/en_GB.json index fbcf9346..0e6c2057 100644 --- a/apps/envited.ascs.digital/modules/UploadedAsset/locales/en_GB.json +++ b/apps/envited.ascs.digital/modules/UploadedAsset/locales/en_GB.json @@ -1,8 +1,11 @@ { - "[Status] minted": "Completed", + "[Status] minted": "Minted", "[Status] processing": "Processing...", + "[Status] pending": "Ready for minting", "[Status] rejected": "Rejected", - "[Button] delete": "Cancel", + "[Button] mint": "Mint", + "[Button] delete": "Delete", + "[Button] view": "View", "[Notification] error deleting asset": "Cannot delete asset", "[Notification] asset deleted": "Asset successfully cancelled" } diff --git a/apps/envited.ascs.digital/modules/UploadedAssets/UploadedAssets.tsx b/apps/envited.ascs.digital/modules/UploadedAssets/UploadedAssets.tsx index 1322bd8f..f3818952 100644 --- a/apps/envited.ascs.digital/modules/UploadedAssets/UploadedAssets.tsx +++ b/apps/envited.ascs.digital/modules/UploadedAssets/UploadedAssets.tsx @@ -24,32 +24,24 @@ export const UploadedAssets: FC = ({ assets }) => {
- - - - - - - + + + + + + - {assets.map((asset, assetIdx) => { - const metadata = !isEmpty(asset.metadata) - ? typeof asset.metadata === 'string' - ? JSON.parse(asset.metadata) - : asset.metadata - : {} - - return - })} + {assets.map((asset, assetIdx) => ( + + ))}
- {t('[Label] asset')} - - {t('[Label] type')} - - {t('[Label] select')} -
+ {t('[Label] name')} + + {t('[Label] created at')} + + {t('[Label] status')} + + {t('[Label] select')} +
diff --git a/apps/envited.ascs.digital/modules/UploadedAssets/locales/de_DE.json b/apps/envited.ascs.digital/modules/UploadedAssets/locales/de_DE.json index 6646362a..23dcafb0 100644 --- a/apps/envited.ascs.digital/modules/UploadedAssets/locales/de_DE.json +++ b/apps/envited.ascs.digital/modules/UploadedAssets/locales/de_DE.json @@ -1,12 +1,15 @@ { "[Heading] uploaded assets": "Assets that are uploaded and ready for minting.", "[Description] uploaded assets": "You be able to see a \"preview\" of the asset detail page before you \"mint\" the asset, if there is something incorrect \"delete\" the asset and start over again.", + "[Label] name": "Name", + "[Label] cid": "CID", + "[Label] created at": "Created on", + "[Label] status": "Status", "[Label] asset": "Asset", "[Label] type": "Type", - "[Label] status": "Status", "[Label] select": "Select", "[Button] mint": "Mint", "[Button] preview": "Preview", - "[Button] delete": "Delete", - "[Label] CID": "CID" + "[Button] view": "View", + "[Button] delete": "Delete" } diff --git a/apps/envited.ascs.digital/modules/UploadedAssets/locales/en_GB.json b/apps/envited.ascs.digital/modules/UploadedAssets/locales/en_GB.json index 6646362a..23dcafb0 100644 --- a/apps/envited.ascs.digital/modules/UploadedAssets/locales/en_GB.json +++ b/apps/envited.ascs.digital/modules/UploadedAssets/locales/en_GB.json @@ -1,12 +1,15 @@ { "[Heading] uploaded assets": "Assets that are uploaded and ready for minting.", "[Description] uploaded assets": "You be able to see a \"preview\" of the asset detail page before you \"mint\" the asset, if there is something incorrect \"delete\" the asset and start over again.", + "[Label] name": "Name", + "[Label] cid": "CID", + "[Label] created at": "Created on", + "[Label] status": "Status", "[Label] asset": "Asset", "[Label] type": "Type", - "[Label] status": "Status", "[Label] select": "Select", "[Button] mint": "Mint", "[Button] preview": "Preview", - "[Button] delete": "Delete", - "[Label] CID": "CID" + "[Button] view": "View", + "[Button] delete": "Delete" }