Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: eductx presentations display #636

Merged
merged 3 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { formatAddress } from '@/utils/format';
import { copyToClipboard } from '@/utils/string';
import { DocumentDuplicateIcon } from '@heroicons/react/24/outline';
import { Tooltip } from '@nextui-org/react';
import { useTranslations } from 'next-intl';

export const AddressDisplay = ({ address }: { address: string }) => {
const t = useTranslations('AddressDisplay');
return (
<div className="flex flex-col space-y-0.5">
<h2 className="dark:text-navy-blue-200 pr-2 font-bold text-gray-800">
{t('title')}:
</h2>
<div className="flex">
<Tooltip
className="border-navy-blue-300 bg-navy-blue-100 text-navy-blue-700"
content={t('tooltip')}
>
<a
href={`https://etherscan.io/address/${address}`}
target="_blank"
rel="noopener noreferrer"
className="text-md animated-transition dark:text-navy-blue-300 cursor-pointer font-normal text-gray-700 underline underline-offset-2"
>
{formatAddress(address)}
</a>
</Tooltip>
<button
type="button"
className="pl-1"
onClick={() => copyToClipboard(address)}
>
<DocumentDuplicateIcon className="animated-transition dark:text-navy-blue-300 ml-1 h-5 w-5 text-gray-700 hover:text-gray-700" />
</button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,135 +2,28 @@

import {
CheckCircleIcon,
DocumentDuplicateIcon,
ExclamationCircleIcon,
} from '@heroicons/react/24/outline';
import { Tooltip } from '@nextui-org/react';
import type { VerifiableCredential } from '@veramo/core';
import clsx from 'clsx';
import { useTranslations } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
import { Fragment, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';

import { DIDDisplay } from '@/components/DIDDisplay';
import JsonModal from '@/components/JsonModal';
import { formatAddress, getFirstWord } from '@/utils/format';
import { convertTypes, copyToClipboard } from '@/utils/string';
import { ImageLink } from '@/components/ImageLink';
import { getFirstWord } from '@/utils/format';
import { convertTypes } from '@/utils/string';
import { Normal } from './templates/Normal';
import { EduCTX } from './templates/EduCTX';

interface FormattedPanelProps {
credential: VerifiableCredential;
}

const AddressDisplay = ({ address }: { address: string }) => {
const t = useTranslations('AddressDisplay');
return (
<div className="flex flex-col space-y-0.5">
<h2 className="dark:text-navy-blue-200 pr-2 font-bold text-gray-800">
{t('title')}:
</h2>
<div className="flex">
<Tooltip
className="border-navy-blue-300 bg-navy-blue-100 text-navy-blue-700"
content={t('tooltip')}
>
<a
href={`https://etherscan.io/address/${address}`}
target="_blank"
rel="noopener noreferrer"
className="text-md animated-transition dark:text-navy-blue-300 cursor-pointer font-normal text-gray-700 underline underline-offset-2"
>
{formatAddress(address)}
</a>
</Tooltip>
<button
type="button"
className="pl-1"
onClick={() => copyToClipboard(address)}
>
<DocumentDuplicateIcon className="animated-transition dark:text-navy-blue-300 ml-1 h-5 w-5 text-gray-700 hover:text-gray-700" />
</button>
</div>
</div>
);
};

const DisplayDate = ({ text, date }: { text: string; date: string }) => (
<div className="flex flex-col items-start space-y-0.5">
<h2 className="dark:text-navy-blue-200 pr-2 font-bold text-gray-800">
{text}:
</h2>
<h3 className="text-md dark:text-navy-blue-200 text-gray-700">
{new Date(Date.parse(date)).toDateString()}
</h3>
</div>
);

const CredentialSubject = ({
data,
viewJsonText,
selectJsonData,
}: {
data: Record<string, any>;
viewJsonText: string;
selectJsonData: React.Dispatch<React.SetStateAction<any>>;
}) => (
<>
{Object.entries(data).map(([key, value]: [string, any]) => {
if (value === null || value === '') return null;
return (
<Fragment key={key}>
{(() => {
if (key === 'id') {
return (
<>
<div className="flex flex-col space-y-0.5">
<div className="flex">
<DIDDisplay did={value} />
</div>
</div>
</>
);
}

if (key === 'address') return <AddressDisplay address={value} />;
if (key === 'image') return <ImageLink value={value} />;

const isObject = !(
typeof value === 'string' || typeof value === 'number'
);
key = key.replace(/([A-Z])/g, ' $1').trim();
return (
<div
className={clsx(
'flex w-full overflow-clip',
isObject ? 'items-center' : 'flex-col items-start space-y-0.5'
)}
>
<h2 className="dark:text-navy-blue-200 pr-2 font-bold capitalize text-gray-800">
{key}:
</h2>
<div className="text-md dark:text-navy-blue-300 w-full truncate font-normal text-gray-700">
{isObject ? (
<button
type="button"
className="dark:border-navy-blue-300 dark:hover:border-navy-blue-400 dark:focus:ring-navy-blue-500 rounded-md border border-gray-300 px-2 py-0.5 text-sm hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
onClick={() => selectJsonData(value)}
>
{viewJsonText}
</button>
) : (
value
)}
</div>
</div>
);
})()}
</Fragment>
);
})}
</>
);
enum Templates {
Normal = 0,
EduCTX = 1,
}

const CredentialPanel = ({ credential }: FormattedPanelProps) => {
const t = useTranslations('CredentialPanel');
Expand All @@ -153,6 +46,47 @@ const CredentialPanel = ({ credential }: FormattedPanelProps) => {
setJsonModalOpen(true);
};

const template = useMemo(() => {
const credentialTypes = Array.isArray(credential.type)
? credential.type
: [credential.type];

if (credentialTypes.includes('EducationCredential')) {
return Templates.EduCTX;
}

return Templates.Normal;
}, [credential]);

const renderTemplate = useMemo(() => {
switch (template) {
case Templates.EduCTX:
return (
<EduCTX
credential={credential}
title={{
subject: t('subject'),
issuer: t('issuer'),
dates: t('dates'),
}}
/>
);
default:
return (
<Normal
credential={credential}
title={{
subject: t('subject'),
issuer: t('issuer'),
dates: t('dates'),
}}
viewJsonText={t('view-json')}
selectJsonData={selectJsonData}
/>
);
}
}, [credential, template]);

return (
<>
<div className="flex flex-col space-y-8">
Expand Down Expand Up @@ -193,53 +127,7 @@ const CredentialPanel = ({ credential }: FormattedPanelProps) => {
</div>
</div>
</div>
<div className="flex flex-col space-y-8 px-6 md:flex-row md:space-x-16 md:space-y-0">
<div className="flex w-full flex-col items-start space-y-2 md:max-w-[50%]">
<h1 className="text-md dark:text-orange-accent-dark font-medium text-pink-500">
{t('subject')}
</h1>
<CredentialSubject
data={credential.credentialSubject}
viewJsonText={t('view-json')}
selectJsonData={selectJsonData}
/>
</div>
<div className="flex flex-1">
<div className="flex flex-col space-y-8">
<div className="flex flex-col items-start justify-center space-y-2 ">
<h1 className="text-md dark:text-orange-accent-dark font-medium text-pink-500">
{t('issuer')}
</h1>
<div className="flex flex-col space-y-0.5">
<div className="flex">
<DIDDisplay
did={
typeof credential.issuer === 'string'
? credential.issuer
: credential.issuer.id
}
/>
</div>
</div>
</div>
<div className="flex flex-col items-start space-y-2">
<h1 className="text-md dark:text-orange-accent-dark font-medium text-pink-500">
{t('dates')}
</h1>
<DisplayDate
text="Issuance date"
date={credential.issuanceDate}
/>
{credential.expirationDate && (
<DisplayDate
text="Expiration date"
date={credential.expirationDate}
/>
)}
</div>
</div>
</div>
</div>
{renderTemplate}
<div
className="text-md dark:text-navy-blue-200 cursor-pointer px-6 font-medium text-gray-700"
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const DisplayDate = ({ text, date }: { text: string; date: string }) => {
const parsed = Date.parse(date);
return (
<div className="flex flex-col items-start space-y-0.5">
<h2 className="dark:text-navy-blue-200 pr-2 font-bold text-gray-800">
{text}:
</h2>
<h3 className="text-md dark:text-navy-blue-200 text-gray-700">
{!Number.isNaN(parsed) ? new Date(parsed).toLocaleDateString() : date}
</h3>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Tooltip } from '@nextui-org/react';

export const DisplayText = ({
text,
value,
tooltip,
}: { text: string; value: string; tooltip?: string }) => (
<div className="flex flex-col items-start space-y-0.5">
<h2 className="dark:text-navy-blue-200 pr-2 font-bold text-gray-800">
{text}:
</h2>
{tooltip ? (
<Tooltip
content={tooltip}
className="border-navy-blue-300 bg-navy-blue-100 text-navy-blue-700"
>
<h3 className="text-md dark:text-navy-blue-200 text-gray-700">
{value}
</h3>
</Tooltip>
) : (
<h3 className="text-md dark:text-navy-blue-200 text-gray-700">{value}</h3>
)}
</div>
);
Loading