diff --git a/ui/src/adapters/api/credentials.ts b/ui/src/adapters/api/credentials.ts index 1082dc0b..d68483a2 100644 --- a/ui/src/adapters/api/credentials.ts +++ b/ui/src/adapters/api/credentials.ts @@ -301,6 +301,7 @@ export async function getAuthCredentialsByIDs({ export type CreateCredential = { credentialSchema: string; + credentialStatusType: CredentialStatusType | null; credentialSubject: Json; displayMethod: CredentialDisplayMethod | null; expiration: number | null; @@ -556,6 +557,7 @@ export async function deleteLink({ export type CreateLink = { credentialExpiration: string | null; + credentialStatusType: CredentialStatusType | null; credentialSubject: Json; displayMethod: CredentialDisplayMethod | null; expiration: string | null; diff --git a/ui/src/adapters/parsers/view.ts b/ui/src/adapters/parsers/view.ts index 2edea876..e6d816bd 100644 --- a/ui/src/adapters/parsers/view.ts +++ b/ui/src/adapters/parsers/view.ts @@ -31,6 +31,7 @@ type CredentialIssuance = { credentialDisplayMethod: { type: DisplayMethodType; url: string } | undefined; credentialExpiration: Date | undefined; credentialRefreshService: string | undefined; + credentialStatusType: CredentialStatusType | undefined; credentialSubject: Record | undefined; mtProof: boolean; signatureProof: boolean; @@ -197,6 +198,7 @@ export const issuanceMethodFormDataParser = getStrictParser; displayMethod: { enabled: boolean; type: DisplayMethodType | ""; url: string }; proofTypes: ProofType[]; @@ -207,6 +209,7 @@ export type IssueCredentialFormData = { const issueCredentialFormDataParser = getStrictParser()( z.object({ credentialExpiration: dayjsInstanceParser.nullable().optional(), + credentialStatusType: z.nativeEnum(CredentialStatusType).nullable(), credentialSubject: z.record(z.unknown()).optional(), displayMethod: z.object({ enabled: z.boolean(), @@ -237,8 +240,14 @@ export const credentialFormParser = getStrictParser< issueCredential: issueCredentialFormDataParser, }) .transform(({ issuanceMethod, issueCredential }, context) => { - const { credentialExpiration, credentialSubject, displayMethod, proofTypes, refreshService } = - issueCredential; + const { + credentialExpiration, + credentialStatusType, + credentialSubject, + displayMethod, + proofTypes, + refreshService, + } = issueCredential; const { type } = issuanceMethod; const baseIssuance = { @@ -248,6 +257,7 @@ export const credentialFormParser = getStrictParser< : undefined, credentialExpiration: credentialExpiration ? credentialExpiration.toDate() : undefined, credentialRefreshService: refreshService.enabled ? refreshService.url : undefined, + credentialStatusType: credentialStatusType || undefined, credentialSubject, mtProof: proofTypes.includes(ProofType.Iden3SparseMerkleTreeProof), signatureProof: proofTypes.includes(ProofType.BJJSignature2021), @@ -400,6 +410,7 @@ export function serializeCredentialLinkIssuance({ credentialDisplayMethod, credentialExpiration, credentialRefreshService, + credentialStatusType, credentialSubject, linkAccessibleUntil, linkMaximumIssuance, @@ -422,6 +433,7 @@ export function serializeCredentialLinkIssuance({ credentialExpiration: credentialExpiration ? serializeDate(credentialExpiration, "date-time") : null, + credentialStatusType: credentialStatusType ?? null, credentialSubject: serializedSchemaForm.data === undefined ? {} : serializedSchemaForm.data, displayMethod: credentialDisplayMethod ? { @@ -455,6 +467,7 @@ export function serializeCredentialIssuance({ credentialDisplayMethod, credentialExpiration, credentialRefreshService, + credentialStatusType, credentialSubject, did, mtProof, @@ -478,6 +491,7 @@ export function serializeCredentialIssuance({ return { data: { credentialSchema, + credentialStatusType: credentialStatusType ?? null, credentialSubject: serializedSchemaForm.data === undefined ? {} : serializedSchemaForm.data, displayMethod: credentialDisplayMethod ? { diff --git a/ui/src/components/credentials/IssueCredential.tsx b/ui/src/components/credentials/IssueCredential.tsx index 83a28897..4a76f442 100644 --- a/ui/src/components/credentials/IssueCredential.tsx +++ b/ui/src/components/credentials/IssueCredential.tsx @@ -36,6 +36,7 @@ const defaultCredentialFormInput: CredentialFormInput = { type: "directIssue", }, issueCredential: { + credentialStatusType: null, displayMethod: { enabled: false, type: "", url: "" }, proofTypes: [ProofType.BJJSignature2021], refreshService: { enabled: false, url: "" }, diff --git a/ui/src/components/credentials/IssueCredentialForm.tsx b/ui/src/components/credentials/IssueCredentialForm.tsx index 3ff4e1cf..50207b4c 100644 --- a/ui/src/components/credentials/IssueCredentialForm.tsx +++ b/ui/src/components/credentials/IssueCredentialForm.tsx @@ -24,6 +24,7 @@ import { generatePath } from "react-router-dom"; import { z } from "zod"; import { getDisplayMethods } from "src/adapters/api/display-method"; +import { getSupportedBlockchains } from "src/adapters/api/identities"; import { getApiSchemas } from "src/adapters/api/schemas"; import { getJsonSchemaFromUrl } from "src/adapters/jsonSchemas"; import { buildAppError, jsonSchemaErrorToString, notifyError } from "src/adapters/parsers"; @@ -45,6 +46,7 @@ import { ApiSchema, AppError, Attribute, + CredentialStatusType, DisplayMethod, JsonSchema, ObjectAttribute, @@ -124,6 +126,12 @@ export function IssueCredentialForm({ status: "pending", }); + const [credentialStatusTypes, setCredentialStatusTypes] = useState< + AsyncTask + >({ + status: "pending", + }); + const [inputErrors, setInputErrors] = useState(); const [refreshServiceChecked, setRefreshServiceChecked] = useState( @@ -405,6 +413,40 @@ export function IssueCredentialForm({ [env, identifier] ); + const fetchBlockChains = useCallback( + async (signal: AbortSignal) => { + setCredentialStatusTypes((previousState) => + isAsyncTaskDataAvailable(previousState) + ? { data: previousState.data, status: "reloading" } + : { status: "loading" } + ); + + const response = await getSupportedBlockchains({ + env, + signal, + }); + + if (response.success) { + const [, , blockchain = "", network = ""] = identifier.split(":"); + const identityBlockchainNetworks = + response.data.successful.find(({ name }) => name === blockchain)?.networks || []; + const identityNetworkCredentialStatusTypes = + identityBlockchainNetworks.find(({ name }) => name === network)?.credentialStatus || []; + + setCredentialStatusTypes({ + data: identityNetworkCredentialStatusTypes, + status: "successful", + }); + } else { + if (!isAbortedError(response.error)) { + setCredentialStatusTypes({ error: response.error, status: "failed" }); + void message.error(response.error.message); + } + } + }, + [env, message, identifier] + ); + useEffect(() => { const { aborter } = makeRequestAbortable(fetchSchemas); @@ -417,6 +459,12 @@ export function IssueCredentialForm({ return aborter; }, [fetchDisplayMethods]); + useEffect(() => { + const { aborter } = makeRequestAbortable(fetchBlockChains); + + return aborter; + }, [fetchBlockChains]); + return (
+ + + + + + +