From 77b99b99d13c4581f0e8079f0094880ab235e23d Mon Sep 17 00:00:00 2001 From: andyv09 Date: Mon, 6 Nov 2023 18:44:56 +0100 Subject: [PATCH 1/7] feat: create FilterPopover component --- packages/dapp/package.json | 2 +- .../src/components/Controlbar/Controlbar.tsx | 6 +- .../VCTable/FilterPopover/index.tsx | 84 +++++++++++++++++++ packages/snap/snap.manifest.json | 2 +- 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 packages/dapp/src/components/VCTable/FilterPopover/index.tsx diff --git a/packages/dapp/package.json b/packages/dapp/package.json index a11732dd7..48cce9d48 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -8,7 +8,7 @@ "build": "rimraf .next && next build", "postbuild": "next-sitemap --config=next-sitemap.config.js", "build:docker": "pnpm build", - "dev": "cross-env USE_LOCAL='true' next dev", + "dev": "cross-env USE_LOCAL='false' next dev", "docker:build": "docker build . -t blockchain-lab-um/dapp:latest", "postinstall": "pnpm prisma generate --schema=./prisma/schema.prisma", "lint": "pnpm lint:next && pnpm lint:tsc && pnpm lint:prettier && pnpm lint:stylelint", diff --git a/packages/dapp/src/components/Controlbar/Controlbar.tsx b/packages/dapp/src/components/Controlbar/Controlbar.tsx index 62f397a2d..563c00932 100644 --- a/packages/dapp/src/components/Controlbar/Controlbar.tsx +++ b/packages/dapp/src/components/Controlbar/Controlbar.tsx @@ -12,11 +12,12 @@ import clsx from 'clsx'; import { useTranslations } from 'next-intl'; import ImportModal from '@/components/ImportModal'; -import DataStoreCombobox from '@/components/VCTable/DataStoreCombobox'; +// import DataStoreCombobox from '@/components/VCTable/DataStoreCombobox'; import GlobalFilter from '@/components/VCTable/GlobalFilter'; import ViewTabs from '@/components/VCTable/ViewTabs'; import { stringifyCredentialSubject } from '@/utils/format'; import { useGeneralStore, useMascaStore, useToastStore } from '@/stores'; +import FilterPopover from '../VCTable/FilterPopover'; // import PlaygroundModal from '../PlaygroundModal'; @@ -162,7 +163,8 @@ const Controlbar = () => {
{vcs.length > 0 && (
- + {/* */} +
)} diff --git a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx new file mode 100644 index 000000000..d8b460a97 --- /dev/null +++ b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { Fragment, useState } from 'react'; +import { Popover, Transition } from '@headlessui/react'; +import { ChevronDownIcon } from '@heroicons/react/20/solid'; +import clsx from 'clsx'; +import { useTranslations } from 'next-intl'; + +import { QueryCredentialsRequestResult } from '@blockchain-lab-um/masca-connector'; +import { ChevronRightIcon } from '@heroicons/react/24/solid'; +import { useGeneralStore } from '@/stores'; + +interface FilterPopoverProps { + vcs: QueryCredentialsRequestResult[]; +} + + +function FilterPopover({ vcs }: FilterPopoverProps) { + const t = useTranslations('AppNavbar'); + const [storesOpen, setStoresOpen] = useState(false); + + const isConnected = useGeneralStore((state) => state.isConnected); + + return ( + + {({ open, close }) => ( + <> + + Filters + + + +
+
+
Filters
+ +
+
+ + {storesOpen && ( +
+ Content +
+ )} +
+
+
+
+ + )} +
+ ); +} + +export default FilterPopover; diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 78b760c14..b2cdee422 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -26,7 +26,7 @@ "./files/circuits/credentialAtomicQuerySigV2/circuit_final.zkey", "./files/circuits/credentialAtomicQuerySigV2/verification_key.json" ], - "shasum": "wspaS/8UJT4A8/gqD9bzbMdem7dTVVHoBw27ZPgd0NA=" + "shasum": "WpJFHZRVxpZbp/XiJ2lU+H/rvJ9QrGUs7mMInPfmHP8=" }, "initialPermissions": { "endowment:ethereum-provider": {}, From 4132eefae5053855a343621896c4ae39aff5f523 Mon Sep 17 00:00:00 2001 From: andyv09 Date: Fri, 10 Nov 2023 16:57:15 +0100 Subject: [PATCH 2/7] feat: add datastore & type filters --- .../VCTable/FilterPopover/CheckBox.tsx | 25 ++++ .../VCTable/FilterPopover/CredentialTypes.tsx | 58 ++++++++ .../VCTable/FilterPopover/DataStores.tsx | 58 ++++++++ .../VCTable/FilterPopover/index.tsx | 133 ++++++++++++++---- .../dapp/src/components/VCTable/index.tsx | 10 +- .../dapp/src/components/VCTable/tableUtils.ts | 22 +++ packages/dapp/src/stores/tableStore.ts | 42 +++++- 7 files changed, 320 insertions(+), 28 deletions(-) create mode 100644 packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx create mode 100644 packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx create mode 100644 packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx new file mode 100644 index 000000000..bbc5aa4d5 --- /dev/null +++ b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +interface CheckBoxProps { + selected: boolean; + setSelected: (selected: boolean) => void; + children: React.ReactNode; +} + +export const CheckBox = ({ + selected, + setSelected, + children, +}: CheckBoxProps) => ( + // Create checkbox +
+ { + setSelected(!selected); + }} + /> + {children} +
+); diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx new file mode 100644 index 000000000..ae56b47e2 --- /dev/null +++ b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { ChevronRightIcon } from '@heroicons/react/24/solid'; +import clsx from 'clsx'; + +import { useTableStore } from '@/stores'; +import { CheckBox } from './CheckBox'; + +export const CredentialTypes = () => { + const [open, setOpen] = useState(false); + const { credentialTypes, setCredentialTypes } = useTableStore((state) => ({ + credentialTypes: state.credentialTypes, + setCredentialTypes: state.setCredentialTypes, + })); + + return ( +
+ + {open && ( +
+ {credentialTypes.map((type) => ( + { + const newCredentialTypes = credentialTypes.map((tp) => { + if (tp.type === type.type) { + return { + ...type, + selected, + }; + } + return tp; + }); + setCredentialTypes(newCredentialTypes); + }} + > + {type.type} + + ))} +
+ )} +
+ ); +}; diff --git a/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx new file mode 100644 index 000000000..fecf16574 --- /dev/null +++ b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { ChevronRightIcon } from '@heroicons/react/24/solid'; +import clsx from 'clsx'; + +import { useTableStore } from '@/stores'; +import { CheckBox } from './CheckBox'; + +export const DataStores = () => { + const [open, setOpen] = useState(false); + const { dataStores, setDataStores } = useTableStore((state) => ({ + dataStores: state.dataStores, + setDataStores: state.setDataStores, + })); + + return ( +
+ + {open && ( +
+ {dataStores.map((dataStore) => ( + { + const newDataStores = dataStores.map((ds) => { + if (ds.dataStore === dataStore.dataStore) { + return { + ...dataStore, + selected, + }; + } + return ds; + }); + setDataStores(newDataStores); + }} + > + {dataStore.dataStore} + + ))} +
+ )} +
+ ); +}; diff --git a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx index d8b460a97..df2aec72d 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx @@ -1,26 +1,120 @@ 'use client'; -import { Fragment, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; +import { + AvailableCredentialStores, + QueryCredentialsRequestResult, +} from '@blockchain-lab-um/masca-connector'; import { Popover, Transition } from '@headlessui/react'; import { ChevronDownIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import { useTranslations } from 'next-intl'; -import { QueryCredentialsRequestResult } from '@blockchain-lab-um/masca-connector'; -import { ChevronRightIcon } from '@heroicons/react/24/solid'; -import { useGeneralStore } from '@/stores'; +import { useGeneralStore, useTableStore } from '@/stores'; +import { CredentialTypes } from './CredentialTypes'; +import { DataStores } from './DataStores'; interface FilterPopoverProps { - vcs: QueryCredentialsRequestResult[]; + vcs: QueryCredentialsRequestResult[]; } - function FilterPopover({ vcs }: FilterPopoverProps) { const t = useTranslations('AppNavbar'); const [storesOpen, setStoresOpen] = useState(false); + const { + dataStores, + availableEcosystems, + selectedEcosystems, + credentialTypes, + columnFilters, + setColumnFilters, + setDataStores, + setAvailableEcosystems, + setSelectedEcosystems, + setCredentialTypes, + } = useTableStore((state) => ({ + dataStores: state.dataStores, + availableEcosystems: state.availableEcosystems, + selectedEcosystems: state.selectedEcosystems, + credentialTypes: state.credentialTypes, + columnFilters: state.columnFilters, + setDataStores: state.setDataStores, + setAvailableEcosystems: state.setAvailableEcosystems, + setSelectedEcosystems: state.setSelectedEcosystems, + setCredentialTypes: state.setCredentialTypes, + setColumnFilters: state.setColumnFilters, + })); + const isConnected = useGeneralStore((state) => state.isConnected); + const updateColumnFiltersDataStore = () => { + const dsFilter = { + id: 'data_store', + value: [] as AvailableCredentialStores[], + }; + dsFilter.value = dataStores + .filter((ds) => ds.selected) + .map((ds) => ds.dataStore); + + const newColumnFilters = columnFilters.filter( + (cf) => cf.id !== 'data_store' + ); + newColumnFilters.push(dsFilter); + console.log('newColumnFilters', newColumnFilters); + setColumnFilters(newColumnFilters); + }; + + const updateColumnFiltersCredentialTypes = () => { + const typeFilter = { + id: 'type', + value: [] as string[], + }; + typeFilter.value = credentialTypes + .filter((type) => type.selected) + .map((type) => type.type); + + const newColumnFilters = columnFilters.filter((cf) => cf.id !== 'type'); + newColumnFilters.push(typeFilter); + console.log('newColumnFilters', newColumnFilters); + setColumnFilters(newColumnFilters); + }; + + const getAvailableCredentialTypes = () => { + const allCredentialTypes: string[] = []; + vcs.forEach((vc) => { + if (vc.data.type) { + if (typeof vc.data.type === 'string') { + allCredentialTypes.push(vc.data.type); + return; + } + vc.data.type.forEach((type: string) => { + if (type !== 'VerifiableCredential') allCredentialTypes.push(type); + }); + } + }); + const availableCredentialTypes = [...new Set(allCredentialTypes)]; + + setCredentialTypes( + availableCredentialTypes.map((type) => ({ + type, + selected: true, + })) + ); + }; + + useEffect(() => { + updateColumnFiltersDataStore(); + }, [dataStores]); + + useEffect(() => { + getAvailableCredentialTypes(); + }, [vcs]); + + useEffect(() => { + updateColumnFiltersCredentialTypes(); + }, [credentialTypes]); + return ( {({ open, close }) => ( @@ -54,24 +148,15 @@ function FilterPopover({ vcs }: FilterPopoverProps) { leaveTo="opacity-0 translate-y-1" > -
-
-
Filters
- -
-
- - {storesOpen && ( -
- Content -
- )} -
+
+
+
+ Filters +
+ +
+ +
diff --git a/packages/dapp/src/components/VCTable/index.tsx b/packages/dapp/src/components/VCTable/index.tsx index e23c3b83f..33ab1145d 100644 --- a/packages/dapp/src/components/VCTable/index.tsx +++ b/packages/dapp/src/components/VCTable/index.tsx @@ -46,7 +46,12 @@ import { stringifyCredentialSubject } from '@/utils/format'; import { convertTypes } from '@/utils/string'; import { useMascaStore, useTableStore, useToastStore } from '@/stores'; import TablePagination from './TablePagination'; -import { includesDataStore, recursiveIncludes, selectRows } from './tableUtils'; +import { + includesDataStore, + includesType, + recursiveIncludes, + selectRows, +} from './tableUtils'; import VCCard from './VCCard'; const Table = () => { @@ -89,6 +94,7 @@ const Table = () => { {info.getValue().toString()} ), header: () => {t('table.type')}, + filterFn: includesType, } ), columnHelper.accessor((row) => Date.parse(row.data.issuanceDate), { @@ -272,7 +278,7 @@ const Table = () => { const table = useReactTable({ data: vcs, columns, - filterFns: { includesDataStore, recursiveIncludes }, + filterFns: { includesDataStore, recursiveIncludes, includesType }, globalFilterFn: recursiveIncludes, state: { sorting, diff --git a/packages/dapp/src/components/VCTable/tableUtils.ts b/packages/dapp/src/components/VCTable/tableUtils.ts index 8df0a4f00..8c526575a 100644 --- a/packages/dapp/src/components/VCTable/tableUtils.ts +++ b/packages/dapp/src/components/VCTable/tableUtils.ts @@ -20,6 +20,27 @@ export const includesDataStore: FilterFn = ( return matching; }; +export const includesType: FilterFn = ( + row, + _: string, + value: string[] +) => { + const item = row.original.data.type; + let matching = false; + + for (const val of value) { + if (typeof item === 'string' && item === val) { + matching = true; + break; + } else if (Array.isArray(item) && item.indexOf(val) >= 0) { + matching = true; + break; + } + } + + return matching; +}; + const extractValues = (obj: any): string[] => { if (typeof obj === 'string') return [obj.toLowerCase()]; if (typeof obj === 'number') return [obj.toString()]; @@ -39,6 +60,7 @@ export const recursiveIncludes: FilterFn = ( columnId: string, value: string ) => { + console.log('here...'); if (!value) return false; const item = columnId === 'credential_subject' diff --git a/packages/dapp/src/stores/tableStore.ts b/packages/dapp/src/stores/tableStore.ts index a8f9e729d..5261bcd37 100644 --- a/packages/dapp/src/stores/tableStore.ts +++ b/packages/dapp/src/stores/tableStore.ts @@ -1,25 +1,56 @@ -import { type QueryCredentialsRequestResult } from '@blockchain-lab-um/masca-connector'; +import { + AvailableCredentialStores, + type QueryCredentialsRequestResult, +} from '@blockchain-lab-um/masca-connector'; import { ColumnFiltersState } from '@tanstack/react-table'; import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; +interface DataStore { + dataStore: AvailableCredentialStores; + selected: boolean; +} + +interface CredentialType { + type: string; + selected: boolean; +} + interface TableStore { globalFilter: string; columnFilters: ColumnFiltersState; selectedVCs: QueryCredentialsRequestResult[]; cardView: boolean; + dataStores: DataStore[]; + credentialTypes: CredentialType[]; + availableEcosystems: string[]; + selectedEcosystems: string[]; setGlobalFilter: (globalFilter: string) => void; setColumnFilters: (columnFilters: ColumnFiltersState) => void; setSelectedVCs: (selectedVCs: QueryCredentialsRequestResult[]) => void; setCardView: (view: boolean) => void; + setDataStores: (dataStores: DataStore[]) => void; + setCredentialTypes: (credentialTypes: CredentialType[]) => void; + setAvailableEcosystems: (availableEcosystems: string[]) => void; + setSelectedEcosystems: (selectedEcosystems: string[]) => void; } export const tableStoreInitialState = { globalFilter: '', - columnFilters: [{ id: 'data_store', value: ['snap'] }], + columnFilters: [ + { id: 'data_store', value: ['snap'] }, + // { id: 'type', value: [] }, + ], selectedVCs: [], cardView: true, + dataStores: [ + { dataStore: 'snap', selected: true } as DataStore, + { dataStore: 'ceramic', selected: true } as DataStore, + ], + credentialTypes: [], + availableEcosystems: ['ebsi', 'polygonid', 'other'], + selectedEcosystems: [], }; export const useTableStore = createWithEqualityFn()( @@ -32,6 +63,13 @@ export const useTableStore = createWithEqualityFn()( setCardView: (cardView: boolean) => set({ cardView }), setSelectedVCs: (selectedVCs: QueryCredentialsRequestResult[]) => set({ selectedVCs }), + setDataStores: (dataStores: DataStore[]) => set({ dataStores }), + setCredentialTypes: (credentialTypes: CredentialType[]) => + set({ credentialTypes }), + setAvailableEcosystems: (availableEcosystems: string[]) => + set({ availableEcosystems }), + setSelectedEcosystems: (selectedEcosystems: string[]) => + set({ selectedEcosystems }), }), shallow ); From 98c401b112931167b2d6515ac78d327c5d8e2e30 Mon Sep 17 00:00:00 2001 From: andyv09 Date: Thu, 23 Nov 2023 19:44:32 +0100 Subject: [PATCH 3/7] feat: finish filters --- .../VCTable/FilterPopover/CheckBox.tsx | 5 +- .../VCTable/FilterPopover/CredentialTypes.tsx | 118 +++++++++++++----- .../VCTable/FilterPopover/DataStores.tsx | 13 +- .../VCTable/FilterPopover/Ecosystems.tsx | 64 ++++++++++ .../VCTable/FilterPopover/index.tsx | 39 ++++-- .../dapp/src/components/VCTable/index.tsx | 9 +- .../dapp/src/components/VCTable/tableUtils.ts | 31 +++++ packages/dapp/src/stores/tableStore.ts | 24 ++-- packages/dapp/tailwind.config.cjs | 3 +- 9 files changed, 243 insertions(+), 63 deletions(-) create mode 100644 packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx index bbc5aa4d5..a6b1d8745 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx @@ -12,14 +12,15 @@ export const CheckBox = ({ children, }: CheckBoxProps) => ( // Create checkbox -
+
{ setSelected(!selected); }} /> - {children} + {children}
); diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx index ae56b47e2..9fb5f6b6f 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx @@ -11,46 +11,96 @@ export const CredentialTypes = () => { credentialTypes: state.credentialTypes, setCredentialTypes: state.setCredentialTypes, })); + const [filter, setFilter] = useState(''); return (
- - {open && ( -
- {credentialTypes.map((type) => ( - { - const newCredentialTypes = credentialTypes.map((tp) => { - if (tp.type === type.type) { - return { - ...type, - selected, - }; - } - return tp; - }); - setCredentialTypes(newCredentialTypes); +
+ + {open && + (credentialTypes.filter((type) => type.selected).length > 0 ? ( + + ) : ( + ))} +
+ {open && ( +
+ { + setFilter(e.target.value); + }} + value={filter} + /> +
+ {credentialTypes.map((type) => { + if (!type.type.toLowerCase().includes(filter.toLowerCase())) { + return null; + } + + return ( +
+ { + const newCredentialTypes = credentialTypes.map((tp) => { + if (tp.type === type.type) { + return { + ...type, + selected, + }; + } + return tp; + }); + setCredentialTypes(newCredentialTypes); + }} + > + {type.type} + +
+ ); + })} +
)}
diff --git a/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx index fecf16574..74bed05db 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx @@ -5,6 +5,11 @@ import clsx from 'clsx'; import { useTableStore } from '@/stores'; import { CheckBox } from './CheckBox'; +const DSNames = { + snap: 'Snap', + ceramic: 'Ceramic', +}; + export const DataStores = () => { const [open, setOpen] = useState(false); const { dataStores, setDataStores } = useTableStore((state) => ({ @@ -19,10 +24,10 @@ export const DataStores = () => { setOpen(!open); }} > -
+
@@ -30,7 +35,7 @@ export const DataStores = () => {
{open && ( -
+
{dataStores.map((dataStore) => ( { setDataStores(newDataStores); }} > - {dataStore.dataStore} + {DSNames[dataStore.dataStore]} ))}
diff --git a/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx b/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx new file mode 100644 index 000000000..55b89902e --- /dev/null +++ b/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import { ChevronRightIcon } from '@heroicons/react/24/solid'; +import clsx from 'clsx'; + +import { useTableStore } from '@/stores'; +import { CheckBox } from './CheckBox'; + +const ESNames = { + ebsi: 'EBSI', + polygonid: 'Polygon', + other: 'Other', +}; + +export const Ecosystems = () => { + const [open, setOpen] = useState(false); + const { ecosystems, setEcosystems } = useTableStore((state) => ({ + ecosystems: state.ecosystems, + setEcosystems: state.setEcosystems, + })); + + return ( +
+ + {open && ( +
+ {ecosystems.map((ecosystem) => ( + { + const newDataStores = ecosystems.map((ds) => { + if (ds.ecosystem === ecosystem.ecosystem) { + return { + ...ecosystem, + selected, + }; + } + return ds; + }); + setEcosystems(newDataStores); + }} + > + {ESNames[ecosystem.ecosystem]} + + ))} +
+ )} +
+ ); +}; diff --git a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx index df2aec72d..536c11d47 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx @@ -13,6 +13,7 @@ import { useTranslations } from 'next-intl'; import { useGeneralStore, useTableStore } from '@/stores'; import { CredentialTypes } from './CredentialTypes'; import { DataStores } from './DataStores'; +import { Ecosystems } from './Ecosystems'; interface FilterPopoverProps { vcs: QueryCredentialsRequestResult[]; @@ -24,24 +25,20 @@ function FilterPopover({ vcs }: FilterPopoverProps) { const { dataStores, - availableEcosystems, - selectedEcosystems, + ecosystems, + setEcosystems, credentialTypes, columnFilters, setColumnFilters, setDataStores, - setAvailableEcosystems, - setSelectedEcosystems, setCredentialTypes, } = useTableStore((state) => ({ dataStores: state.dataStores, - availableEcosystems: state.availableEcosystems, - selectedEcosystems: state.selectedEcosystems, credentialTypes: state.credentialTypes, columnFilters: state.columnFilters, setDataStores: state.setDataStores, - setAvailableEcosystems: state.setAvailableEcosystems, - setSelectedEcosystems: state.setSelectedEcosystems, + ecosystems: state.ecosystems, + setEcosystems: state.setEcosystems, setCredentialTypes: state.setCredentialTypes, setColumnFilters: state.setColumnFilters, })); @@ -103,6 +100,21 @@ function FilterPopover({ vcs }: FilterPopoverProps) { ); }; + const updateColumnFiltersEcosystems = () => { + const esFilter = { + id: 'issuer', + value: [] as string[], + }; + esFilter.value = ecosystems + .filter((es) => es.selected) + .map((es) => es.ecosystem); + + const newColumnFilters = columnFilters.filter((cf) => cf.id !== 'issuer'); + newColumnFilters.push(esFilter); + console.log('newColumnFilters ECOSYSTEM', newColumnFilters); + setColumnFilters(newColumnFilters); + }; + useEffect(() => { updateColumnFiltersDataStore(); }, [dataStores]); @@ -111,6 +123,10 @@ function FilterPopover({ vcs }: FilterPopoverProps) { getAvailableCredentialTypes(); }, [vcs]); + useEffect(() => { + updateColumnFiltersEcosystems(); + }, [ecosystems]); + useEffect(() => { updateColumnFiltersCredentialTypes(); }, [credentialTypes]); @@ -148,15 +164,16 @@ function FilterPopover({ vcs }: FilterPopoverProps) { leaveTo="opacity-0 translate-y-1" > -
+
-
+
Filters
- +
+
diff --git a/packages/dapp/src/components/VCTable/index.tsx b/packages/dapp/src/components/VCTable/index.tsx index 33ab1145d..debff3eca 100644 --- a/packages/dapp/src/components/VCTable/index.tsx +++ b/packages/dapp/src/components/VCTable/index.tsx @@ -48,6 +48,7 @@ import { useMascaStore, useTableStore, useToastStore } from '@/stores'; import TablePagination from './TablePagination'; import { includesDataStore, + includesEcosystem, includesType, recursiveIncludes, selectRows, @@ -151,6 +152,7 @@ const Table = () => { ), header: () => {t('table.issuer')}, + filterFn: includesEcosystem, } ), columnHelper.accessor((row) => row.data.expirationDate, { @@ -278,7 +280,12 @@ const Table = () => { const table = useReactTable({ data: vcs, columns, - filterFns: { includesDataStore, recursiveIncludes, includesType }, + filterFns: { + includesDataStore, + recursiveIncludes, + includesType, + includesEcosystem, + }, globalFilterFn: recursiveIncludes, state: { sorting, diff --git a/packages/dapp/src/components/VCTable/tableUtils.ts b/packages/dapp/src/components/VCTable/tableUtils.ts index 8c526575a..97e0e8fb2 100644 --- a/packages/dapp/src/components/VCTable/tableUtils.ts +++ b/packages/dapp/src/components/VCTable/tableUtils.ts @@ -41,6 +41,37 @@ export const includesType: FilterFn = ( return matching; }; +export const includesEcosystem: FilterFn = ( + row, + _: string, + value: string[] +) => { + let item = row.original.data.issuer; + if (!item) return false; + if (typeof item !== 'string') { + item = item.id; + } + let matching = false; + + for (const val of value) { + if (typeof item !== 'string') break; + if ( + val === 'other' && + item.split(':')[1] !== 'ebsi' && + item.split(':')[1] !== 'polygonid' + ) { + matching = true; + break; + } + if (item.split(':')[1] === val) { + matching = true; + break; + } + } + + return matching; +}; + const extractValues = (obj: any): string[] => { if (typeof obj === 'string') return [obj.toLowerCase()]; if (typeof obj === 'number') return [obj.toString()]; diff --git a/packages/dapp/src/stores/tableStore.ts b/packages/dapp/src/stores/tableStore.ts index 5261bcd37..8cefacb3c 100644 --- a/packages/dapp/src/stores/tableStore.ts +++ b/packages/dapp/src/stores/tableStore.ts @@ -16,15 +16,19 @@ interface CredentialType { selected: boolean; } +interface Ecosystem { + ecosystem: 'ebsi' | 'polygonid' | 'other'; + selected: boolean; +} + interface TableStore { globalFilter: string; columnFilters: ColumnFiltersState; selectedVCs: QueryCredentialsRequestResult[]; cardView: boolean; dataStores: DataStore[]; + ecosystems: Ecosystem[]; credentialTypes: CredentialType[]; - availableEcosystems: string[]; - selectedEcosystems: string[]; setGlobalFilter: (globalFilter: string) => void; setColumnFilters: (columnFilters: ColumnFiltersState) => void; @@ -32,8 +36,7 @@ interface TableStore { setCardView: (view: boolean) => void; setDataStores: (dataStores: DataStore[]) => void; setCredentialTypes: (credentialTypes: CredentialType[]) => void; - setAvailableEcosystems: (availableEcosystems: string[]) => void; - setSelectedEcosystems: (selectedEcosystems: string[]) => void; + setEcosystems: (ecosystems: Ecosystem[]) => void; } export const tableStoreInitialState = { @@ -48,9 +51,12 @@ export const tableStoreInitialState = { { dataStore: 'snap', selected: true } as DataStore, { dataStore: 'ceramic', selected: true } as DataStore, ], + ecosystems: [ + { ecosystem: 'ebsi', selected: true } as Ecosystem, + { ecosystem: 'polygonid', selected: true } as Ecosystem, + { ecosystem: 'other', selected: true } as Ecosystem, + ], credentialTypes: [], - availableEcosystems: ['ebsi', 'polygonid', 'other'], - selectedEcosystems: [], }; export const useTableStore = createWithEqualityFn()( @@ -66,10 +72,8 @@ export const useTableStore = createWithEqualityFn()( setDataStores: (dataStores: DataStore[]) => set({ dataStores }), setCredentialTypes: (credentialTypes: CredentialType[]) => set({ credentialTypes }), - setAvailableEcosystems: (availableEcosystems: string[]) => - set({ availableEcosystems }), - setSelectedEcosystems: (selectedEcosystems: string[]) => - set({ selectedEcosystems }), + + setEcosystems: (ecosystems: Ecosystem[]) => set({ ecosystems }), }), shallow ); diff --git a/packages/dapp/tailwind.config.cjs b/packages/dapp/tailwind.config.cjs index e087a8ebc..9b416115e 100644 --- a/packages/dapp/tailwind.config.cjs +++ b/packages/dapp/tailwind.config.cjs @@ -153,5 +153,6 @@ module.exports = { }, }, }, - plugins: [require('tailwind-scrollbar')], + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-var-requires + plugins: [require('tailwind-scrollbar')({ nocompatible: true })], }; From aa90aa65aebc32510768e90b4744998dec049789 Mon Sep 17 00:00:00 2001 From: andyv09 Date: Thu, 23 Nov 2023 19:53:47 +0100 Subject: [PATCH 4/7] fix: filters button --- .../VCTable/FilterPopover/index.tsx | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx index 536c11d47..7c847e2a6 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx @@ -10,7 +10,7 @@ import { ChevronDownIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import { useTranslations } from 'next-intl'; -import { useGeneralStore, useTableStore } from '@/stores'; +import { useTableStore } from '@/stores'; import { CredentialTypes } from './CredentialTypes'; import { DataStores } from './DataStores'; import { Ecosystems } from './Ecosystems'; @@ -43,8 +43,6 @@ function FilterPopover({ vcs }: FilterPopoverProps) { setColumnFilters: state.setColumnFilters, })); - const isConnected = useGeneralStore((state) => state.isConnected); - const updateColumnFiltersDataStore = () => { const dsFilter = { id: 'data_store', @@ -58,7 +56,6 @@ function FilterPopover({ vcs }: FilterPopoverProps) { (cf) => cf.id !== 'data_store' ); newColumnFilters.push(dsFilter); - console.log('newColumnFilters', newColumnFilters); setColumnFilters(newColumnFilters); }; @@ -73,7 +70,6 @@ function FilterPopover({ vcs }: FilterPopoverProps) { const newColumnFilters = columnFilters.filter((cf) => cf.id !== 'type'); newColumnFilters.push(typeFilter); - console.log('newColumnFilters', newColumnFilters); setColumnFilters(newColumnFilters); }; @@ -111,7 +107,6 @@ function FilterPopover({ vcs }: FilterPopoverProps) { const newColumnFilters = columnFilters.filter((cf) => cf.id !== 'issuer'); newColumnFilters.push(esFilter); - console.log('newColumnFilters ECOSYSTEM', newColumnFilters); setColumnFilters(newColumnFilters); }; @@ -132,28 +127,23 @@ function FilterPopover({ vcs }: FilterPopoverProps) { }, [credentialTypes]); return ( - + {({ open, close }) => ( <> - - Filters - +
+ + Filters + +
Date: Fri, 24 Nov 2023 13:10:53 +0100 Subject: [PATCH 5/7] fix: revert USE_LOCAL to true --- packages/dapp/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dapp/package.json b/packages/dapp/package.json index 48cce9d48..a11732dd7 100644 --- a/packages/dapp/package.json +++ b/packages/dapp/package.json @@ -8,7 +8,7 @@ "build": "rimraf .next && next build", "postbuild": "next-sitemap --config=next-sitemap.config.js", "build:docker": "pnpm build", - "dev": "cross-env USE_LOCAL='false' next dev", + "dev": "cross-env USE_LOCAL='true' next dev", "docker:build": "docker build . -t blockchain-lab-um/dapp:latest", "postinstall": "pnpm prisma generate --schema=./prisma/schema.prisma", "lint": "pnpm lint:next && pnpm lint:tsc && pnpm lint:prettier && pnpm lint:stylelint", From e925af863a20870dbb4c91a8ab5015bb46f71aad Mon Sep 17 00:00:00 2001 From: Urban Date: Fri, 24 Nov 2023 13:13:40 +0100 Subject: [PATCH 6/7] chore: remove comment --- packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx index a6b1d8745..68289c89c 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/CheckBox.tsx @@ -11,7 +11,6 @@ export const CheckBox = ({ setSelected, children, }: CheckBoxProps) => ( - // Create checkbox
Date: Mon, 27 Nov 2023 10:18:43 +0100 Subject: [PATCH 7/7] fix: pr feedback --- .changeset/chatty-plants-notice.md | 5 ++++ .../src/components/Controlbar/Controlbar.tsx | 2 -- .../VCTable/FilterPopover/CredentialTypes.tsx | 5 +++- .../VCTable/FilterPopover/DataStores.tsx | 8 +++--- .../VCTable/FilterPopover/Ecosystems.tsx | 8 +++--- .../VCTable/FilterPopover/index.tsx | 26 ++++++++----------- .../dapp/src/components/VCTable/tableUtils.ts | 20 ++++++-------- packages/dapp/src/messages/en.json | 8 ++++++ packages/dapp/src/stores/tableStore.ts | 5 +--- packages/dapp/tailwind.config.cjs | 1 - 10 files changed, 47 insertions(+), 41 deletions(-) create mode 100644 .changeset/chatty-plants-notice.md diff --git a/.changeset/chatty-plants-notice.md b/.changeset/chatty-plants-notice.md new file mode 100644 index 000000000..7d867f599 --- /dev/null +++ b/.changeset/chatty-plants-notice.md @@ -0,0 +1,5 @@ +--- +'@blockchain-lab-um/dapp': minor +--- + +Add better popover for filtering VCs on dashboard diff --git a/packages/dapp/src/components/Controlbar/Controlbar.tsx b/packages/dapp/src/components/Controlbar/Controlbar.tsx index 563c00932..88beef232 100644 --- a/packages/dapp/src/components/Controlbar/Controlbar.tsx +++ b/packages/dapp/src/components/Controlbar/Controlbar.tsx @@ -12,7 +12,6 @@ import clsx from 'clsx'; import { useTranslations } from 'next-intl'; import ImportModal from '@/components/ImportModal'; -// import DataStoreCombobox from '@/components/VCTable/DataStoreCombobox'; import GlobalFilter from '@/components/VCTable/GlobalFilter'; import ViewTabs from '@/components/VCTable/ViewTabs'; import { stringifyCredentialSubject } from '@/utils/format'; @@ -163,7 +162,6 @@ const Controlbar = () => {
{vcs.length > 0 && (
- {/* */}
diff --git a/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx index 9fb5f6b6f..20afcb19d 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/CredentialTypes.tsx @@ -1,11 +1,14 @@ import React, { useState } from 'react'; import { ChevronRightIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; +import { useTranslations } from 'next-intl'; import { useTableStore } from '@/stores'; import { CheckBox } from './CheckBox'; export const CredentialTypes = () => { + const t = useTranslations('FilterPopover'); + const [open, setOpen] = useState(false); const { credentialTypes, setCredentialTypes } = useTableStore((state) => ({ credentialTypes: state.credentialTypes, @@ -28,7 +31,7 @@ export const CredentialTypes = () => { `${open ? 'rotate-90' : ''}` )} /> - Types + {t('type')}
{open && diff --git a/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx index 74bed05db..975535442 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/DataStores.tsx @@ -1,16 +1,18 @@ import React, { useState } from 'react'; import { ChevronRightIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; +import { useTranslations } from 'next-intl'; import { useTableStore } from '@/stores'; import { CheckBox } from './CheckBox'; -const DSNames = { +const DataStoreNames = { snap: 'Snap', ceramic: 'Ceramic', }; export const DataStores = () => { + const t = useTranslations('FilterPopover'); const [open, setOpen] = useState(false); const { dataStores, setDataStores } = useTableStore((state) => ({ dataStores: state.dataStores, @@ -31,7 +33,7 @@ export const DataStores = () => { `${open ? 'rotate-90' : ''}` )} /> - Stores + {t('datastore')}
{open && ( @@ -53,7 +55,7 @@ export const DataStores = () => { setDataStores(newDataStores); }} > - {DSNames[dataStore.dataStore]} + {DataStoreNames[dataStore.dataStore]} ))}
diff --git a/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx b/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx index 55b89902e..b990c76b1 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/Ecosystems.tsx @@ -1,17 +1,19 @@ import React, { useState } from 'react'; import { ChevronRightIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; +import { useTranslations } from 'next-intl'; import { useTableStore } from '@/stores'; import { CheckBox } from './CheckBox'; -const ESNames = { +const EcosystemSNames = { ebsi: 'EBSI', polygonid: 'Polygon', other: 'Other', }; export const Ecosystems = () => { + const t = useTranslations('FilterPopover'); const [open, setOpen] = useState(false); const { ecosystems, setEcosystems } = useTableStore((state) => ({ ecosystems: state.ecosystems, @@ -32,7 +34,7 @@ export const Ecosystems = () => { `${open ? 'rotate-90' : ''}` )} /> - Ecosystems + {t('ecosystem')}
{open && ( @@ -54,7 +56,7 @@ export const Ecosystems = () => { setEcosystems(newDataStores); }} > - {ESNames[ecosystem.ecosystem]} + {EcosystemSNames[ecosystem.ecosystem]} ))}
diff --git a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx index 7c847e2a6..4db9cecb5 100644 --- a/packages/dapp/src/components/VCTable/FilterPopover/index.tsx +++ b/packages/dapp/src/components/VCTable/FilterPopover/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect } from 'react'; import { AvailableCredentialStores, QueryCredentialsRequestResult, @@ -20,17 +20,14 @@ interface FilterPopoverProps { } function FilterPopover({ vcs }: FilterPopoverProps) { - const t = useTranslations('AppNavbar'); - const [storesOpen, setStoresOpen] = useState(false); + const t = useTranslations('FilterPopover'); const { dataStores, ecosystems, - setEcosystems, credentialTypes, columnFilters, setColumnFilters, - setDataStores, setCredentialTypes, } = useTableStore((state) => ({ dataStores: state.dataStores, @@ -76,15 +73,14 @@ function FilterPopover({ vcs }: FilterPopoverProps) { const getAvailableCredentialTypes = () => { const allCredentialTypes: string[] = []; vcs.forEach((vc) => { - if (vc.data.type) { - if (typeof vc.data.type === 'string') { - allCredentialTypes.push(vc.data.type); - return; - } - vc.data.type.forEach((type: string) => { - if (type !== 'VerifiableCredential') allCredentialTypes.push(type); - }); + if (!vc.data.type) return; + if (typeof vc.data.type === 'string') { + allCredentialTypes.push(vc.data.type); + return; } + vc.data.type.forEach((type: string) => { + if (type !== 'VerifiableCredential') allCredentialTypes.push(type); + }); }); const availableCredentialTypes = [...new Set(allCredentialTypes)]; @@ -157,9 +153,9 @@ function FilterPopover({ vcs }: FilterPopoverProps) {
- Filters + {t('filter')}
- +
diff --git a/packages/dapp/src/components/VCTable/tableUtils.ts b/packages/dapp/src/components/VCTable/tableUtils.ts index 97e0e8fb2..96ac27cb8 100644 --- a/packages/dapp/src/components/VCTable/tableUtils.ts +++ b/packages/dapp/src/components/VCTable/tableUtils.ts @@ -29,10 +29,10 @@ export const includesType: FilterFn = ( let matching = false; for (const val of value) { - if (typeof item === 'string' && item === val) { - matching = true; - break; - } else if (Array.isArray(item) && item.indexOf(val) >= 0) { + if ( + (typeof item === 'string' && item === val) || + (Array.isArray(item) && item.indexOf(val) >= 0) + ) { matching = true; break; } @@ -56,17 +56,14 @@ export const includesEcosystem: FilterFn = ( for (const val of value) { if (typeof item !== 'string') break; if ( - val === 'other' && - item.split(':')[1] !== 'ebsi' && - item.split(':')[1] !== 'polygonid' + (val === 'other' && + item.split(':')[1] !== 'ebsi' && + item.split(':')[1] !== 'polygonid') || + item.split(':')[1] === val ) { matching = true; break; } - if (item.split(':')[1] === val) { - matching = true; - break; - } } return matching; @@ -91,7 +88,6 @@ export const recursiveIncludes: FilterFn = ( columnId: string, value: string ) => { - console.log('here...'); if (!value) return false; const item = columnId === 'credential_subject' diff --git a/packages/dapp/src/messages/en.json b/packages/dapp/src/messages/en.json index 3d7c06b57..867590c12 100644 --- a/packages/dapp/src/messages/en.json +++ b/packages/dapp/src/messages/en.json @@ -251,6 +251,14 @@ "subject": "SUBJECT", "view-json": "View" }, + "FilterPopover": { + "clear": "clear", + "filter": "Filter", + "title": "Filter Credentials", + "ecosystem": "Ecosystem", + "datastore": "Data Store", + "type": "Type" + }, "FriendlydAppTable": { "add-failed": "Failed to add Friendly dapp", "add-masca": "Add Masca.io to Friendly dapps", diff --git a/packages/dapp/src/stores/tableStore.ts b/packages/dapp/src/stores/tableStore.ts index 8cefacb3c..b1546e681 100644 --- a/packages/dapp/src/stores/tableStore.ts +++ b/packages/dapp/src/stores/tableStore.ts @@ -41,10 +41,7 @@ interface TableStore { export const tableStoreInitialState = { globalFilter: '', - columnFilters: [ - { id: 'data_store', value: ['snap'] }, - // { id: 'type', value: [] }, - ], + columnFilters: [{ id: 'data_store', value: ['snap'] }], selectedVCs: [], cardView: true, dataStores: [ diff --git a/packages/dapp/tailwind.config.cjs b/packages/dapp/tailwind.config.cjs index 9b416115e..21679d17a 100644 --- a/packages/dapp/tailwind.config.cjs +++ b/packages/dapp/tailwind.config.cjs @@ -153,6 +153,5 @@ module.exports = { }, }, }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-var-requires plugins: [require('tailwind-scrollbar')({ nocompatible: true })], };