@@ -193,53 +127,7 @@ const CredentialPanel = ({ credential }: FormattedPanelProps) => {
- {
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayDate.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayDate.tsx
new file mode 100644
index 000000000..3ef25789d
--- /dev/null
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayDate.tsx
@@ -0,0 +1,13 @@
+export const DisplayDate = ({ text, date }: { text: string; date: string }) => {
+ const parsed = Date.parse(date);
+ return (
+
+
+ {text}:
+
+
+ {!Number.isNaN(parsed) ? new Date(parsed).toLocaleDateString() : date}
+
+
+ );
+};
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayText.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayText.tsx
new file mode 100644
index 000000000..0a9a7a4c3
--- /dev/null
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/DisplayText.tsx
@@ -0,0 +1,25 @@
+import { Tooltip } from '@nextui-org/react';
+
+export const DisplayText = ({
+ text,
+ value,
+ tooltip,
+}: { text: string; value: string; tooltip?: string }) => (
+
+
+ {text}:
+
+ {tooltip ? (
+
+
+ {value}
+
+
+ ) : (
+ {value}
+ )}
+
+);
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/FormattedView.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/FormattedView.tsx
index 354265443..296e99985 100644
--- a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/FormattedView.tsx
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/FormattedView.tsx
@@ -7,46 +7,91 @@ import {
InformationCircleIcon,
} from '@heroicons/react/24/outline';
import { Pagination, Tooltip } from '@nextui-org/react';
-import type { IVerifyResult, VerifiableCredential } from '@veramo/core';
+import type {
+ VerifiableCredential,
+ VerifiablePresentation,
+} from '@veramo/core';
import { useTranslations } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { VerificationInfoModal } from '@/components/VerificationInfoModal';
import { copyToClipboard } from '@/utils/string';
import CredentialPanel from './CredentialPanel';
import { formatDid } from '@/utils/format';
+import {
+ type VerificationResult,
+ VerificationService,
+} from '@blockchain-lab-um/extended-verification';
+import { isError } from '@blockchain-lab-um/masca-connector';
+import { useToastStore } from '@/stores';
+
+const verifyPresentation = async (presentation: VerifiablePresentation) => {
+ await VerificationService.init();
+
+ const verifiedResult = await VerificationService.verify(presentation);
+
+ if (isError(verifiedResult)) {
+ console.error('Failed to verify presentation');
+ setTimeout(() => {
+ useToastStore.setState({
+ open: true,
+ title: 'Failed to verify presentation',
+ type: 'error',
+ loading: false,
+ link: null,
+ });
+ }, 200);
+ return { verified: false } as VerificationResult;
+ }
-export const FormattedView = ({
+ return verifiedResult.data;
+};
+
+export const FormattedView = async ({
credential,
- holder,
- expirationDate,
- issuanceDate,
+ presentation,
page,
total,
- verificationResult,
}: {
credential: VerifiableCredential;
- holder: string;
- expirationDate: string | undefined;
- issuanceDate: string | undefined;
+ presentation: VerifiablePresentation;
page: string;
total: number;
- verificationResult: IVerifyResult;
}) => {
const t = useTranslations('FormattedView');
+ const router = useRouter();
+ const pathname = usePathname();
+
+ const { holder, expirationDate, issuanceDate } = presentation;
+
const [verificationInfoModalOpen, setVerificationInfoModalOpen] =
useState(false);
- const router = useRouter();
- const pathname = usePathname();
+ const [verificationResult, setVerificationResult] =
+ useState
(null);
const isValid = useMemo(() => {
if (!expirationDate) return true;
return Date.parse(expirationDate) > Date.now();
}, [expirationDate]);
+ useEffect(() => {
+ verifyPresentation(presentation)
+ .then((result) => setVerificationResult(result))
+ .catch((error) => {
+ console.error(error);
+ useToastStore.setState({
+ open: true,
+ title: t('verify-failed'),
+ type: 'error',
+ loading: false,
+ link: null,
+ });
+ });
+ }, [presentation]);
+
return (
<>
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx
index aec4abff8..8173fbafe 100644
--- a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/page.tsx
@@ -1,9 +1,7 @@
-import type { VerifiablePresentation } from '@veramo/core';
import { decodeCredentialToObject } from '@veramo/utils';
import { normalizeCredential } from 'did-jwt-vc';
import { notFound } from 'next/navigation';
-import { getAgent } from '@/app/api/veramoSetup';
import JsonPanel from '@/components/CredentialDisplay/JsonPanel';
import { convertTypes } from '@/utils/string';
import { FormattedView } from './FormattedView';
@@ -12,16 +10,6 @@ import { NormalViewButton } from './NormalViewButton';
export const revalidate = 0;
-const verifyPresentation = async (presentation: VerifiablePresentation) => {
- const agent = await getAgent();
-
- const result = await agent.verifyPresentation({
- presentation,
- });
-
- return result;
-};
-
export default async function Page({
params: { id },
searchParams,
@@ -48,20 +36,15 @@ export default async function Page({
const page = searchParams.page ?? '1';
const view = searchParams.view ?? 'Normal';
- const verificationResult = await verifyPresentation(presentation);
-
return (
{view === 'Normal' && (
)}
{view === 'Json' && (
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/EduCTX.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/EduCTX.tsx
new file mode 100644
index 000000000..6e2a83643
--- /dev/null
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/EduCTX.tsx
@@ -0,0 +1,97 @@
+import type { VerifiableCredential } from '@veramo/core';
+import { DisplayDate } from '../DisplayDate';
+import { DisplayText } from '../DisplayText';
+import { DIDDisplay } from '@/components/DIDDisplay';
+
+const CredentialSubject = ({
+ data,
+}: {
+ data: Record
;
+}) => (
+ <>
+
+
+
+
+
+ >
+);
+
+type EduCTXProps = {
+ credential: VerifiableCredential;
+ title: {
+ subject: string;
+ issuer: string;
+ dates: string;
+ };
+};
+
+export const EduCTX = ({ credential, title }: EduCTXProps) => {
+ return (
+
+
+
+ {title.subject}
+
+
+
+
+
+
+
+ {title.issuer}
+
+
+
+
+
+
+ {title.dates}
+
+
+
+ {credential.expirationDate && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/Normal.tsx b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/Normal.tsx
new file mode 100644
index 000000000..b245490e8
--- /dev/null
+++ b/packages/dapp/src/app/[locale]/app/(public)/share-presentation/[id]/templates/Normal.tsx
@@ -0,0 +1,138 @@
+import { DIDDisplay } from '@/components/DIDDisplay';
+import { Fragment } from 'react';
+import { AddressDisplay } from '../AddressDisplay';
+import { ImageLink } from '@/components/ImageLink';
+import clsx from 'clsx';
+import type { VerifiableCredential } from '@veramo/core';
+import { DisplayDate } from '../DisplayDate';
+
+const CredentialSubject = ({
+ data,
+ viewJsonText,
+ selectJsonData,
+}: {
+ data: Record;
+ viewJsonText: string;
+ selectJsonData: React.Dispatch>;
+}) => (
+ <>
+ {Object.entries(data).map(([key, value]: [string, any]) => {
+ if (value === null || value === '') return null;
+ return (
+
+ {(() => {
+ if (key === 'id') {
+ return (
+ <>
+
+ >
+ );
+ }
+
+ if (key === 'address') return ;
+ if (key === 'image') return ;
+
+ const isObject = !(
+ typeof value === 'string' || typeof value === 'number'
+ );
+ key = key.replace(/([A-Z])/g, ' $1').trim();
+ return (
+
+
+ {key}:
+
+
+ {isObject ? (
+
+ ) : (
+ value
+ )}
+
+
+ );
+ })()}
+
+ );
+ })}
+ >
+);
+
+type NormalProps = {
+ credential: VerifiableCredential;
+ title: {
+ subject: string;
+ issuer: string;
+ dates: string;
+ };
+ viewJsonText: string;
+ selectJsonData: (data: any) => void;
+};
+
+export const Normal = ({
+ credential,
+ title,
+ viewJsonText,
+ selectJsonData,
+}: NormalProps) => {
+ return (
+
+
+
+ {title.subject}
+
+
+
+
+
+
+
+
+ {title.dates}
+
+
+ {credential.expirationDate && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/packages/dapp/src/messages/en.json b/packages/dapp/src/messages/en.json
index 335a45b84..6d37707da 100644
--- a/packages/dapp/src/messages/en.json
+++ b/packages/dapp/src/messages/en.json
@@ -282,15 +282,15 @@
"invalid": "Invalid",
"presentation-status": "Status",
"presentation-valid": "Presentation is valid",
- "presentation-invalid": "Presentation is invalid"
+ "presentation-invalid": "Presentation is invalid",
+ "verify-failed": "Failed to verify presentation"
},
"CredentialPanel": {
"title": "Credential",
"status": "Status",
"dates": "DATES",
- "expiration-date": "Expiration Date",
"issuer": "ISSUER",
- "isuance-date": "Issuance Date",
+ "issuance-date": "Issuance Date",
"subject": "SUBJECT",
"view-json": "View JSON",
"credential-valid": "Credential is valid",