Skip to content

Commit

Permalink
Merge branch 'develop' into feat/home-page
Browse files Browse the repository at this point in the history
  • Loading branch information
andyv09 committed Feb 13, 2024
2 parents ef460f4 + ad80cd1 commit a83091d
Show file tree
Hide file tree
Showing 14 changed files with 722 additions and 212 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-jeans-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@blockchain-lab-um/dapp': patch
---

Add Social Media Sharing & update OG
1 change: 1 addition & 0 deletions packages/dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"qs": "^6.11.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-share": "^5.0.3",
"sharp": "^0.32.6",
"siwe": "^2.1.4",
"swr": "^2.2.4",
Expand Down
Binary file added packages/dapp/public/images/hey_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/dapp/public/images/warpcast_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import { EyeIcon, TrashIcon } from '@heroicons/react/24/solid';
import { EyeIcon, ShareIcon, TrashIcon } from '@heroicons/react/24/solid';
import {
Pagination,
Spinner,
Expand All @@ -18,9 +18,11 @@ import clsx from 'clsx';
import { useTranslations } from 'next-intl';

import { DeleteSharedPresentationModal } from '@/components/DeleteSharedPresentationModal';
import { ShareCredentialModal } from '@/components/ShareCredentialModal';
import { createClient } from '@/utils/supabase/client';
import { Tables } from '@/utils/supabase/helper.types';
import { useAuthStore } from '@/stores/authStore';
import { useShareModalStore } from '@/stores/shareModalStore';

const ITEMS_PER_PAGE = 10;

Expand Down Expand Up @@ -62,6 +64,22 @@ export const SharedPresentations = () => {

const router = useRouter();

// Global state
const { isSignedIn, token, changeIsSignInModalOpen } = useAuthStore(
(state) => ({
isSignedIn: state.isSignedIn,
changeIsSignInModalOpen: state.changeIsSignInModalOpen,
token: state.token,
})
);

const { setShareLink, setShareModalMode, setIsShareModalOpen } =
useShareModalStore((state) => ({
setShareLink: state.setShareLink,
setShareModalMode: state.setMode,
setIsShareModalOpen: state.setIsOpen,
}));

// Local state
const [presentations, setPresentations] = useState<Tables<'presentations'>[]>(
[]
Expand All @@ -79,9 +97,6 @@ export const SharedPresentations = () => {
return Math.ceil(total / ITEMS_PER_PAGE);
}, [total]);

// Global state
const token = useAuthStore((state) => state.token);

const columns = [
{
key: 'title',
Expand Down Expand Up @@ -118,6 +133,27 @@ export const SharedPresentations = () => {
case 'actions':
return (
<div className="flex w-full items-center justify-end space-x-4">
<Tooltip content="Share">
<button
className={clsx(
' dark:text-navy-blue-50 group flex',
'items-center justify-center rounded-full text-gray-700 outline-none focus:outline-none'
)}
onClick={() => {
if (!isSignedIn) {
changeIsSignInModalOpen(true);
return;
}
setShareModalMode('multiple');
setShareLink(
`${window.location.origin}/app/share-presentation/${presentation.id}`
);
setIsShareModalOpen(true);
}}
>
<ShareIcon className="h-4 w-4" />
</button>
</Tooltip>
<Tooltip content="View">
<div
className="text-md dark:text-navy-blue-50 cursor-pointer text-gray-600"
Expand Down Expand Up @@ -261,6 +297,7 @@ export const SharedPresentations = () => {
setModalOpen={setDeleteModalOpen}
setPresentations={setPresentations}
/>
<ShareCredentialModal />
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useMemo } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import {
CheckCircleIcon,
DocumentDuplicateIcon,
ExclamationCircleIcon,
} from '@heroicons/react/24/outline';
import { Pagination, Tooltip } from '@nextui-org/react';
import { VerifiableCredential } from '@veramo/core';
import { useTranslations } from 'next-intl';

import { copyToClipboard } from '@/utils/string';
import CredentialPanel from './credentialPanel';

export const FormatedView = ({
Expand Down Expand Up @@ -40,22 +42,44 @@ export const FormatedView = ({
return (
<>
<div className="dark:bg-navy-blue-800 h-full w-full rounded-3xl bg-white shadow-lg">
<div className="dark:from-navy-blue-700 dark:to-navy-blue-700 flex max-w-full flex-col-reverse items-center space-x-4 rounded-t-2xl bg-gradient-to-br from-pink-100 to-orange-100 px-10 pt-6 sm:flex-row">
<div className="dark:from-navy-blue-700 dark:to-navy-blue-700 flex max-w-full flex-col-reverse items-center space-x-4 rounded-t-2xl bg-gradient-to-br from-pink-100 to-orange-100 px-10 pb-2 pt-6 sm:flex-row">
<div className="flex w-full">
<div className="flex flex-col space-y-4">
<div className="flex flex-col">
<h2 className="dark:text-navy-blue-200 font-bold text-gray-800">
{t('holder')}
</h2>
<h1 className="font-ubuntu dark:text-orange-accent-dark text-left text-lg font-medium text-pink-500 sm:text-xl md:text-2xl lg:truncate">
{holder.length <= 32 ? (
holder
) : (
<>
{holder.substring(0, 20)}...
{holder.substring(holder.length - 10)}
</>
)}
<div className="mt-2 flex items-center">
<Tooltip
content={holder}
className="border-navy-blue-300 bg-navy-blue-100 text-navy-blue-700"
>
<a
href={`https://dev.uniresolver.io/#${holder}`}
target="_blank"
rel="noreferrer"
className="font-ubuntu dark:text-orange-accent-dark text-left text-lg font-medium text-pink-500 underline sm:text-xl md:text-2xl lg:truncate"
>
{`${holder.substring(
0,
holder.lastIndexOf(':')
)}:${holder
.split(':')
[holder.split(':').length - 1].slice(
0,
6
)}...${holder.slice(-4)}`}
</a>
</Tooltip>
<button
onClick={() => {
copyToClipboard(holder);
}}
>
<DocumentDuplicateIcon className="animated-transition dark:text-orange-accent-dark ml-1 h-5 w-5 text-pink-500 hover:opacity-80" />
</button>
</div>
</h1>
</div>
{issuanceDate && (
Expand Down Expand Up @@ -97,7 +121,7 @@ export const FormatedView = ({
</div>
</div>
<div className="px-4 pb-6">
<div className="flex justify-start px-2">
<div className="ml-[14px] flex justify-start px-2">
<Pagination
loop
color="success"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { createClient } from '@supabase/supabase-js';
import { VerifiablePresentation } from '@veramo/core';
import { decodeCredentialToObject } from '@veramo/utils';
import { normalizeCredential } from 'did-jwt-vc';

import JsonPanel from '@/components/CredentialDisplay/JsonPanel';
import { convertTypes } from '@/utils/string';
import { Database } from '@/utils/supabase/database.types';
import { FormatedView } from './formatedView';

export const metadata: Metadata = {
title: 'Share presentation',
description: 'Page for displaying shared presentations',
};

export const revalidate = 0;

const getPresentation = async (id: string): Promise<VerifiablePresentation> => {
interface ReturnPresentation {
presentation: VerifiablePresentation;
title: string;
}

const getPresentation = async (id: string): Promise<ReturnPresentation> => {
const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!
Expand Down Expand Up @@ -43,7 +44,8 @@ const getPresentation = async (id: string): Promise<VerifiablePresentation> => {
.eq('id', id);

const presentation = data[0].presentation as VerifiablePresentation;
return presentation;
const { title } = data[0];
return { presentation, title };
};

export default async function Page({
Expand All @@ -56,7 +58,7 @@ export default async function Page({
page: string | undefined;
};
}) {
const presentation = await getPresentation(id);
const { presentation } = await getPresentation(id);
const credentials = presentation.verifiableCredential
? presentation.verifiableCredential.map(decodeCredentialToObject)
: [];
Expand Down Expand Up @@ -85,3 +87,100 @@ export default async function Page({
</div>
);
}

export async function generateMetadata({
params: { id },
searchParams,
}: {
params: { id: string };
searchParams: {
view: 'Normal' | 'Json';
page: string | undefined;
};
}) {
const { presentation, title } = await getPresentation(id);
if (!presentation) return {};
const url = process.env.NEXT_PUBLIC_APP_URL || 'https://masca.io';
const ogUrl = new URL(`${url}/api/og`);
ogUrl.searchParams.set('type', 'share-presentation');
ogUrl.searchParams.set('holder', presentation.holder);
ogUrl.searchParams.set(
'numberOfCredentials',
(presentation.verifiableCredential?.length ?? 0).toString()
);
ogUrl.searchParams.set('title', title);

if (presentation.verifiableCredential?.length === 1) {
let credential = presentation.verifiableCredential[0];
if (typeof presentation.verifiableCredential[0] === 'string') {
try {
credential = JSON.parse(presentation.verifiableCredential[0]);
} catch (e) {
try {
credential = normalizeCredential(
presentation.verifiableCredential[0]
);
} catch (ex) {
console.error(ex);
}
}
}

const types = convertTypes((credential as any).type);

if (typeof (credential as any).issuer === 'string') {
ogUrl.searchParams.set(
'credentialIssuer',
(credential as any).issuer ?? 'Unknown'
);
} else {
ogUrl.searchParams.set(
'credentialIssuer',
(credential as any).issuer.id ?? 'Unknown'
);
}

if (types.split(', ')[0] === 'Education Credential') {
ogUrl.searchParams.set(
'credentialIssuer',
(credential as any).credentialSubject.achieved.wasAwardedBy
.awardingBody ?? 'Unknown'
);

ogUrl.searchParams.set(
'credentialTitle',
(credential as any).credentialSubject.achieved.title ?? 'missing'
);
}
ogUrl.searchParams.set('credentialType', types);
ogUrl.searchParams.set(
'credentialSubject',
(credential as any).credentialSubject.id
);
ogUrl.searchParams.set(
'credentialIssuanceDate',
(credential as any).issuanceDate
);
}
return {
title: 'Check out my shared credentials',
description: 'Page for displaying shared presentations',
openGraph: {
type: 'article',
images: [
{
url: ogUrl.toString(),
width: 1200,
height: 630,
alt: 'Presentation Image',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Check out my shared credentials',
description: 'Page for displaying shared presentations',
images: [ogUrl.toString()],
},
};
}
Loading

0 comments on commit a83091d

Please sign in to comment.