diff --git a/packages/web-wallet/package.json b/packages/web-wallet/package.json index e1d9f34..d8afd0d 100644 --- a/packages/web-wallet/package.json +++ b/packages/web-wallet/package.json @@ -61,9 +61,9 @@ "@sphereon/ssi-sdk.siopv2-oid4vp-op-auth": "0.29.1-next.82", "@sphereon/ssi-sdk.w3c-vc-api-issuer-rest-client": "0.29.1-next.82", "@sphereon/ssi-types": "0.29.1-next.82", - "@sphereon/ui-components.core": "0.3.1-next.6", - "@sphereon/ui-components.credential-branding": "0.3.1-next.6", - "@sphereon/ui-components.ssi-react": "0.3.1-next.6", + "@sphereon/ui-components.core": "0.3.1-next.16", + "@sphereon/ui-components.credential-branding": "0.3.1-next.16", + "@sphereon/ui-components.ssi-react": "0.3.1-next.16", "web-did-resolver": "^2.0.27", "@supabase/postgrest-js": "1.15.2", "@supabase/storage-js": "2.6.0", @@ -74,6 +74,7 @@ "@veramo/did-provider-key": "4.2.0", "@veramo/did-resolver": "4.2.0", "@veramo/remote-client": "4.2.0", + "@veramo/utils": "4.2.0", "@xstate/react": "3.2.2", "axios": "^1.7.2", "babel-plugin-module-resolver": "^5.0.2", diff --git a/packages/web-wallet/pages/credentials/create/index.tsx b/packages/web-wallet/pages/credentials/create/index.tsx index bfa39d1..c6d143c 100644 --- a/packages/web-wallet/pages/credentials/create/index.tsx +++ b/packages/web-wallet/pages/credentials/create/index.tsx @@ -6,8 +6,8 @@ import PageHeaderBar from '@components/bars/PageHeaderBar' import {Outlet} from 'react-router-dom' import {useCredentialsCreateMachine} from '@machines/credentials/credentialsCreateStateNavigation' import QRCodeModal, {QRValueResult} from 'src/components/modals/QRCodeModal' -import {createCredentialPayloadWithSchema, qrValueGenerator} from '../../../src/services/credentials/CredentialService' -import {staticPropsWithSST} from '../../../src/i18n/server' +import {createCredentialPayloadWithSchema, qrValueGenerator} from '@/src/services/credentials/CredentialService' +import {staticPropsWithSST} from '@/src/i18n/server' const CredentialsCreatePage: FC = () => { const translate = useTranslate() diff --git a/packages/web-wallet/public/locales/en/common.json b/packages/web-wallet/public/locales/en/common.json index bba0023..23059e2 100644 --- a/packages/web-wallet/public/locales/en/common.json +++ b/packages/web-wallet/public/locales/en/common.json @@ -217,6 +217,7 @@ "issue_credential_enter_issue_method_description": "You can choose how you want to issue the credential to the person or subject.", "issue_credential_path_label": "credentials / issue credential", "credentials_overview_action_add_credential": "Add new credential", + "credentials_overview_action_import_credential": "Import credential", "unknown_label": "Unknown", "credential_issuance_method_qr_code_label": "QR-code", "credential_issuance_column_card_label": "Card", @@ -394,5 +395,10 @@ "credential_catalog_column_credential_description_label": "Description", "credential_catalog_column_actions_label": "Actions", "credential_catalog_card_view_display_mode": "Card view", - "credential_catalog_list_view_display_mode": "List view" + "credential_catalog_list_view_display_mode": "List view", + "action_import_label": "Import", + "import_credential_modal_header_title": "Import credential", + "import_credential_modal_header_subtitle": "Please click or drag a file onto the box to import it as a credential.", + "import_credential_modal_dragbox_caption": "Click or drag to import a credential", + "import_credential_modal_dragbox_description": "This credential will be imported and will be available in the overview." } diff --git a/packages/web-wallet/public/locales/nl/common.json b/packages/web-wallet/public/locales/nl/common.json index 3eaf417..f5084d5 100644 --- a/packages/web-wallet/public/locales/nl/common.json +++ b/packages/web-wallet/public/locales/nl/common.json @@ -219,6 +219,7 @@ "issue_credential_enter_issue_method_description": "U kunt kiezen hoe u de credential aan de persoon of het onderwerp wilt verstrekken.", "issue_credential_path_label": "credentials / uitgeven credential", "credentials_overview_action_add_credential": "Credential toevoegen", + "credentials_overview_action_import_credential": "Importeer credential", "unknown_label": "Onbekend", "credential_issuance_method_qr_code_label": "QR-code", "credential_issuance_column_card_label": "Kaart", @@ -392,5 +393,10 @@ "credential_catalog_column_credential_description_label": "Beschrijving", "credential_catalog_column_actions_label": "Acties", "credential_catalog_card_view_display_mode": "Kaartweergave", - "credential_catalog_list_view_display_mode": "Lijstweergave" + "credential_catalog_list_view_display_mode": "Lijstweergave", + "action_import_label": "Import", + "import_credential_modal_header_title": "Credential importeren", + "import_credential_modal_header_subtitle": "Klik of sleep een bestand naar het vak om het als een credential te importeren.", + "import_credential_modal_dragbox_caption": "Klik of sleep om een credential te importeren", + "import_credential_modal_dragbox_description": "Deze credential wordt geïmporteerd en zal beschikbaar zijn in het overzicht." } diff --git a/packages/web-wallet/src/components/modals/ImportFileModal/index.module.css b/packages/web-wallet/src/components/modals/ImportFileModal/index.module.css new file mode 100644 index 0000000..1b27041 --- /dev/null +++ b/packages/web-wallet/src/components/modals/ImportFileModal/index.module.css @@ -0,0 +1,71 @@ +.overlay { + position: absolute; + z-index: 1; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(217,217,217, 0.6); + align-items: center; + justify-content: center; + display: flex; +} + +.container { + border-radius: 24px; + background-color: #FBFBFB; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.headerContainer { + display: flex; + flex-direction: row; + padding-left: 49px; + padding-right: 15px; +} + +.headerCaptionContainer { + flex-grow: 1; + padding-top: 50px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.headerCloseContainer { + margin-left: auto; + padding-top: 16px; +} + +.closeButton { + display: flex; + aspect-ratio: 1; + cursor: pointer; + align-items: center; + justify-content: center; + width: 52px; +} + +.titleCaption { + font-size: 24px; + font-style: normal; + font-weight: 500; + line-height: normal; +} + +.subTitleCaption { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 0.14px; +} + +.contentContainer { + padding: 24px 49px; + display: flex; + flex-direction: column; + gap: 24px; +} diff --git a/packages/web-wallet/src/components/modals/ImportFileModal/index.tsx b/packages/web-wallet/src/components/modals/ImportFileModal/index.tsx new file mode 100644 index 0000000..3597fe0 --- /dev/null +++ b/packages/web-wallet/src/components/modals/ImportFileModal/index.tsx @@ -0,0 +1,94 @@ +import React, {FC, ReactElement, useState} from 'react'; +import {useTranslate} from '@refinedev/core'; +import {PrimaryButton} from '@sphereon/ui-components.ssi-react'; +import CrossIcon from '@components/assets/icons/CrossIcon'; +import DragAndDropBox from '@components/fields/DragAndDropBox'; +import FileSelectionField from '@components/fields/FileSelectionField'; +import style from './index.module.css' + +type Props = { + headerTitle?: string + headerSubTitle?: string + dragBoxCaption: string + dragBoxDescription?: string + onImportFile: (file: File) => Promise + onValidateFile?: (file: File) => Promise + onClose: () => Promise + fileMask?: RegExp +} + +const ImportFileModal: FC = (props: Props): ReactElement => { + const { + headerTitle, + headerSubTitle, + dragBoxCaption, + dragBoxDescription, + onImportFile, + onValidateFile, + onClose + } = props + const translate = useTranslate() + const [file, setFile] = useState() + + const onChangeFile = async (file: File): Promise => { + if (onValidateFile) { + const validationResult = await onValidateFile(file).then( + (result) => result, + () => false + ); + + if (!validationResult) { + return + } + } + + setFile(file) + } + + const onImport = async (): Promise => { + if (!file) { + return + } + + void onImportFile(file) + } + + return ( +
+
) => event.stopPropagation()}> +
+ {(headerTitle || headerSubTitle) && +
+ {headerTitle &&
{headerTitle}
} + {headerSubTitle &&
{headerSubTitle}
} +
+ } +
+
+ +
+
+
+
+ + {file && } + +
+
+
+ ) +} + +export default ImportFileModal diff --git a/packages/web-wallet/src/components/views/ContactsList/index.tsx b/packages/web-wallet/src/components/views/ContactsList/index.tsx index 04f4706..b942cfe 100644 --- a/packages/web-wallet/src/components/views/ContactsList/index.tsx +++ b/packages/web-wallet/src/components/views/ContactsList/index.tsx @@ -144,7 +144,7 @@ const ContactsList: FC = (props: Props): ReactElement => { )), ) const columns = generateHeader({ - type: orderAndOmitContactProperties(partiesOfType[0]?.contact || createEmptyContactOfType(key), ['createdAt', 'lastUpdatedAt']), + type: orderAndOmitContactProperties(partiesOfType[0]?.contact || createEmptyContactOfType(key), ['createdAt', 'lastUpdatedAt', 'metadata', 'ownerId', 'tenantId']), truncationLength, labelPrefix: `${key}_fields`, }) diff --git a/packages/web-wallet/src/components/views/CredentialsList/index.tsx b/packages/web-wallet/src/components/views/CredentialsList/index.tsx index 19654c1..2435537 100644 --- a/packages/web-wallet/src/components/views/CredentialsList/index.tsx +++ b/packages/web-wallet/src/components/views/CredentialsList/index.tsx @@ -1,15 +1,27 @@ import React, {FC, ReactElement, useEffect, useState} from 'react' -import {HttpError, useDelete, useList, useNavigation, useTranslate} from '@refinedev/core' +import {HttpError, useCreate, useDelete, useList, useNavigation, useTranslate} from '@refinedev/core' import {ColumnHeader, Row, SSITableView, TableCellType} from '@sphereon/ui-components.ssi-react' import {ButtonIcon} from '@sphereon/ui-components.core' -import {Credential, CredentialReference, CredentialTableItem, DataResource} from '@typings' -import {toCredentialSummary} from '@sphereon/ui-components.credential-branding' +import {Button, Credential, CredentialReference, CredentialTableItem, DataResource} from '@typings' +import {getCredentialIssuerNameAndAlias, toCredentialSummary} from '@sphereon/ui-components.credential-branding' import agent from '@agent' -import {Party} from '@sphereon/ssi-sdk.data-store' +import { + CorrelationIdentifierType, + CredentialCorrelationType, + FindPartyArgs, + Party, + PartyOrigin, + PartyTypeType +} from '@sphereon/ssi-sdk.data-store' import {CredentialRole, DigitalCredential} from '@sphereon/ssi-sdk.credential-store' import {getMatchingIdentity} from '@helpers/IdentityFilters' import {CredentialMapper, OriginalVerifiableCredential} from '@sphereon/ssi-types' -import {VerifiableCredential} from '@veramo/core' +import {VerifiableCredential, W3CVerifiableCredential} from '@veramo/core' +import ImportFileModal from "@components/modals/ImportFileModal"; +import {computeEntryHash} from '@veramo/utils'; +import {AddContactArgs} from "@sphereon/ssi-sdk.contact-manager"; +import {IdentityOrigin} from "@sphereon/ssi-sdk.data-store/dist/types/contact/contact"; +import {addContact} from "@/src/services/contactService"; type Props = { credentialRole: CredentialRole @@ -21,8 +33,10 @@ const CredentialsList: FC = (props: Props): ReactElement => { const translate = useTranslate() const {mutateAsync: deleteCredential} = useDelete() const {mutateAsync: deleteCredentialReference} = useDelete() + const {mutate: mutateOne} = useCreate() const {create, show} = useNavigation() const [credentialTableItems, setCredentialTableItems] = useState([]) + const [showImportCredentialModal, setShowImportCredentialModal] = useState(false) const { data: credentialData, @@ -58,7 +72,12 @@ const CredentialsList: FC = (props: Props): ReactElement => { ], }) - const {data: partyData, isLoading: partiesLoading, isError: partiesError} = useList({resource: 'parties'}) + const { + data: partyData, + isLoading: partiesLoading, + isError: partiesError, + refetch: refetchParties + } = useList({resource: 'parties'}) useEffect(() => { const fetchCredentialTableItems = async () => { @@ -75,8 +94,9 @@ const CredentialsList: FC = (props: Props): ReactElement => { const issuerPartyIdentity = credential.issuerCorrelationId !== undefined ? getMatchingIdentity(partyData.data, credential.issuerCorrelationId) : undefined const subjectPartyIdentity = - credential.subjectCorrelationId !== undefined ? getMatchingIdentity(partyData.data, credential.subjectCorrelationId) : undefined + (credential.subjectCorrelationId !== undefined && credential.subjectCorrelationId !== null) ? getMatchingIdentity(partyData.data, credential.subjectCorrelationId) : undefined // FIXME null check https://sphereon.atlassian.net/browse/SDK-31 const originalVerifiableCredential = JSON.parse(credential.uniformDocument ?? credential.rawDocument) as OriginalVerifiableCredential + const credentialSummary = await toCredentialSummary({ verifiableCredential: originalVerifiableCredential as VerifiableCredential, hash: credential.hash, @@ -96,7 +116,7 @@ const CredentialsList: FC = (props: Props): ReactElement => { } } - fetchCredentialTableItems() + void fetchCredentialTableItems() }, [credentialData, partyData]) const onCredentialItemDelete = async (opts: Row): Promise => { @@ -227,7 +247,7 @@ const CredentialsList: FC = (props: Props): ReactElement => { ) if (refetchCredentials) { - refetchCredentials() + void refetchCredentials() } } @@ -239,8 +259,8 @@ const CredentialsList: FC = (props: Props): ReactElement => { show(DataResource.CREDENTIALS, row.original.hash, undefined, {variables: {credentialRole: credentialRole}}) } - const buildActionList = () => { - const actions = [] + const buildActionList = (): Array