diff --git a/packages/toolbox-core/src/utils/formatSchemaName.ts b/packages/toolbox-core/src/utils/formatSchemaName.ts new file mode 100644 index 0000000..61c4e93 --- /dev/null +++ b/packages/toolbox-core/src/utils/formatSchemaName.ts @@ -0,0 +1,9 @@ +export const formatSchemaName = (schemaId?: string) => + schemaId + ? schemaId + .split(':')[2] + .replace(/([a-z])([A-Z])/g, '$1 $2') + .split(/-|_| /g) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') + : 'Unknown credential' diff --git a/packages/toolbox-core/src/utils/index.ts b/packages/toolbox-core/src/utils/index.ts index c9b8343..791f744 100644 --- a/packages/toolbox-core/src/utils/index.ts +++ b/packages/toolbox-core/src/utils/index.ts @@ -1 +1,2 @@ export * from './uuid' +export * from './formatSchemaName' diff --git a/packages/toolbox-ui/src/components/credentials/CredentialsTable.tsx b/packages/toolbox-ui/src/components/credentials/CredentialsTable.tsx index b641df4..9859fb9 100644 --- a/packages/toolbox-ui/src/components/credentials/CredentialsTable.tsx +++ b/packages/toolbox-ui/src/components/credentials/CredentialsTable.tsx @@ -1,9 +1,11 @@ import type { ConnectionRecord, CredentialExchangeRecord } from '@aries-framework/core' +import { formatSchemaName } from '@animo/toolbox-core/src/utils' import { CredentialsUtil } from '@animo/toolbox-core/src/utils/records/CredentialsUtil' import { Badge, Group, ScrollArea, Table, Text, useMantineTheme } from '@mantine/core' import React from 'react' +import { useCredentialsFormatData } from '../../contexts/CredentialFormatDataProvider' import { RecordActions } from '../RecordActions' import { SmartAvatar } from '../SmartAvatar' @@ -17,13 +19,14 @@ interface CredentialsTableProps { export const CredentialsTable = ({ records, connections, onDelete, onAccept, onDecline }: CredentialsTableProps) => { const theme = useMantineTheme() + const { formattedData } = useCredentialsFormatData() return ( - + {records.map((record) => { const connection = connections.find((connection) => connection.id == record.connectionId) + const formattedCredential = formattedData.find((data) => data.id === record.id) const isLoading = CredentialsUtil.isCredentialWaitingForResponse(record) const isWaitingForAccept = CredentialsUtil.isCredentialWaitingForAcceptInput(record) const isWaitingForDecline = CredentialsUtil.isCredentialWaitingForDeclineInput(record) @@ -44,7 +48,7 @@ export const CredentialsTable = ({ records, connections, onDelete, onAccept, onD {connection?.theirLabel} - {connection?.theirLabel} + {formatSchemaName(formattedCredential?.offer?.indy?.schema_id)} diff --git a/packages/toolbox-ui/src/contexts/AgentContext.tsx b/packages/toolbox-ui/src/contexts/AgentContext.tsx index e9ab1fa..c35dcee 100644 --- a/packages/toolbox-ui/src/contexts/AgentContext.tsx +++ b/packages/toolbox-ui/src/contexts/AgentContext.tsx @@ -7,6 +7,8 @@ import { agentInitializer } from '@animo/toolbox-core/src/agent/AgentInitializer import AgentProvider from '@aries-framework/react-hooks' import React, { useEffect, useState } from 'react' +import CredentialFormatDataProvider from './CredentialFormatDataProvider' + interface AgentContextProps { agentRecord?: IAgentRecord agentDependenciesProvider: IAgentDependenciesProvider @@ -42,7 +44,7 @@ export const AgentContext: React.FunctionComponent - {children} + {children} ) } diff --git a/packages/toolbox-ui/src/contexts/CredentialFormatDataProvider.tsx b/packages/toolbox-ui/src/contexts/CredentialFormatDataProvider.tsx new file mode 100644 index 0000000..7752fe5 --- /dev/null +++ b/packages/toolbox-ui/src/contexts/CredentialFormatDataProvider.tsx @@ -0,0 +1,122 @@ +import type { Agent, GetFormatDataReturn, IndyCredentialFormat } from '@aries-framework/core' +import type { PropsWithChildren } from 'react' + +import { CredentialExchangeRecord } from '@aries-framework/core' +import { + recordsAddedByType, + recordsRemovedByType, + recordsUpdatedByType, +} from '@aries-framework/react-hooks/build/recordUtils' +import { useState, createContext, useContext, useEffect } from 'react' +import * as React from 'react' + +type FormattedData = GetFormatDataReturn<[IndyCredentialFormat]> & { + id: string +} + +type FormattedDataState = { + formattedData: Array + loading: boolean +} + +const addRecord = (record: FormattedData, state: FormattedDataState): FormattedDataState => { + const newRecordsState = [...state.formattedData] + newRecordsState.unshift(record) + return { + loading: state.loading, + formattedData: newRecordsState, + } +} + +const updateRecord = (record: FormattedData, state: FormattedDataState): FormattedDataState => { + const newRecordsState = [...state.formattedData] + const index = newRecordsState.findIndex((r) => r.id === record.id) + if (index > -1) { + newRecordsState[index] = record + } + return { + loading: state.loading, + formattedData: newRecordsState, + } +} + +const removeRecord = (record: FormattedData, state: FormattedDataState): FormattedDataState => { + const newRecordsState = state.formattedData.filter((r) => r.id !== record.id) + return { + loading: state.loading, + formattedData: newRecordsState, + } +} + +const CredentialFormatDataContext = createContext(undefined) + +export const useCredentialsFormatData = () => { + const credentialFormatDataContext = useContext(CredentialFormatDataContext) + if (!credentialFormatDataContext) { + throw new Error('useCredentialFormatData must be used within a CredentialFormatDataContextProvider') + } + return credentialFormatDataContext +} + +export const useCredentialFormatDataById = (id: string): FormattedData | undefined => { + const { formattedData } = useCredentialsFormatData() + return formattedData.find((c) => c.id === id) +} + +interface Props { + agent?: Agent +} + +const CredentialFormatDataProvider: React.FC> = ({ agent, children }) => { + const [state, setState] = useState<{ + formattedData: Array + loading: boolean + }>({ + formattedData: [], + loading: true, + }) + + const setInitialState = async () => { + if (agent) { + const records = await agent.credentials.getAll() + const formattedData: Array = [] + for (const record of records) { + const formatData = await agent.credentials.getFormatData(record.id) + formattedData.push({ ...formatData, id: record.id }) + } + setState({ formattedData, loading: false }) + } + } + + useEffect(() => { + setInitialState() + }, [agent]) + + useEffect(() => { + if (!state.loading) { + const credentialAdded$ = recordsAddedByType(agent, CredentialExchangeRecord).subscribe(async (record) => { + const formatData = await agent!.credentials.getFormatData(record.id) + setState(addRecord({ ...formatData, id: record.id }, state)) + }) + + const credentialUpdate$ = recordsUpdatedByType(agent, CredentialExchangeRecord).subscribe(async (record) => { + const formatData = await agent!.credentials.getFormatData(record.id) + setState(updateRecord({ ...formatData, id: record.id }, state)) + }) + + const credentialRemove$ = recordsRemovedByType(agent, CredentialExchangeRecord).subscribe((record) => + setState(removeRecord(record, state)) + ) + + return () => { + credentialAdded$.unsubscribe() + credentialUpdate$.unsubscribe() + credentialRemove$.unsubscribe() + } + } + }, [state, agent]) + + return {children} +} + +export default CredentialFormatDataProvider
IssuerCredential Credential Id State @@ -32,6 +35,7 @@ export const CredentialsTable = ({ records, connections, onDelete, onAccept, onD