From e0504e589a7e80b00b913fc88fefbfa826ddcf79 Mon Sep 17 00:00:00 2001 From: soaresa <10797037+soaresa@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:43:22 -0300 Subject: [PATCH] Adds tooltip to the vouch chip icons (#65) --- web-app/package-lock.json | 21 +++++ web-app/package.json | 1 + .../core/routes/Validators/Validators.tsx | 2 +- .../Validators/components/ValidatorRow.tsx | 2 +- .../Validators/components/ValidatorsStats.tsx | 8 -- .../components/Vouch/FamilyIcon.tsx | 9 +++ .../Validators/components/Vouch/VouchChip.tsx | 76 +++++++++++++++++++ .../components/Vouch/VouchLegend.tsx | 22 ++++++ .../components/{ => Vouch}/Vouches.tsx | 2 +- .../components/{ => Vouch}/VouchesRow.tsx | 68 ++--------------- .../{ => Vouch}/VouchesRowSkeleton.tsx | 0 .../components/{ => Vouch}/VouchesTable.tsx | 8 +- .../components/Vouch/legendItems.tsx | 59 ++++++++++++++ .../Validators/components/VouchesLegend.tsx | 72 ------------------ web-app/src/utils.ts | 4 + 15 files changed, 205 insertions(+), 149 deletions(-) create mode 100644 web-app/src/modules/core/routes/Validators/components/Vouch/FamilyIcon.tsx create mode 100644 web-app/src/modules/core/routes/Validators/components/Vouch/VouchChip.tsx create mode 100644 web-app/src/modules/core/routes/Validators/components/Vouch/VouchLegend.tsx rename web-app/src/modules/core/routes/Validators/components/{ => Vouch}/Vouches.tsx (98%) rename web-app/src/modules/core/routes/Validators/components/{ => Vouch}/VouchesRow.tsx (53%) rename web-app/src/modules/core/routes/Validators/components/{ => Vouch}/VouchesRowSkeleton.tsx (100%) rename web-app/src/modules/core/routes/Validators/components/{ => Vouch}/VouchesTable.tsx (96%) create mode 100644 web-app/src/modules/core/routes/Validators/components/Vouch/legendItems.tsx delete mode 100644 web-app/src/modules/core/routes/Validators/components/VouchesLegend.tsx diff --git a/web-app/package-lock.json b/web-app/package-lock.json index 1b4b6a4..55e10cd 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -34,6 +34,7 @@ "react-dom": "^18.3.1", "react-qr-code": "^2.0.14", "react-router-dom": "^6.23.1", + "react-tooltip": "^5.28.0", "styled-components": "^6.1.11" }, "devDependencies": { @@ -1837,6 +1838,12 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clone-response": { "version": "1.0.3", "license": "MIT", @@ -3733,6 +3740,20 @@ "react-dom": ">=16.8" } }, + "node_modules/react-tooltip": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz", + "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1", + "classnames": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "dev": true, diff --git a/web-app/package.json b/web-app/package.json index a102c08..7daaad1 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -41,6 +41,7 @@ "react-dom": "^18.3.1", "react-qr-code": "^2.0.14", "react-router-dom": "^6.23.1", + "react-tooltip": "^5.28.0", "styled-components": "^6.1.11" }, "devDependencies": { diff --git a/web-app/src/modules/core/routes/Validators/Validators.tsx b/web-app/src/modules/core/routes/Validators/Validators.tsx index 6b90def..ebe8aa3 100644 --- a/web-app/src/modules/core/routes/Validators/Validators.tsx +++ b/web-app/src/modules/core/routes/Validators/Validators.tsx @@ -3,7 +3,7 @@ import { gql, useQuery } from '@apollo/client'; import Page from '../../../ui/Page'; import ValidatorsTable from './components/ValidatorsTable'; import ValidatorsStats from './components/ValidatorsStats'; -import VouchesTable from './components/VouchesTable'; +import VouchesTable from './components/Vouch/VouchesTable'; import ToggleButton from '../../../ui/ToggleButton'; const GET_VALIDATORS = gql` diff --git a/web-app/src/modules/core/routes/Validators/components/ValidatorRow.tsx b/web-app/src/modules/core/routes/Validators/components/ValidatorRow.tsx index 483efc2..5982a6f 100644 --- a/web-app/src/modules/core/routes/Validators/components/ValidatorRow.tsx +++ b/web-app/src/modules/core/routes/Validators/components/ValidatorRow.tsx @@ -11,7 +11,7 @@ import { XCircleIcon, } from '@heroicons/react/20/solid'; // import ProgressBar from './ProgressBar'; -import Vouches from './Vouches'; +import Vouches from './Vouch/Vouches'; // Define icons for each status const statusIcons = { diff --git a/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx b/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx index 6352e21..155802f 100644 --- a/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx +++ b/web-app/src/modules/core/routes/Validators/components/ValidatorsStats.tsx @@ -67,12 +67,4 @@ function formatPercentage(value: number) { return `${(value / 10).toFixed(1)}%`; } -// print percentage 0-100 -/*function formatPercentage(value: number, didChange: boolean, didIncrease: boolean) { - if (didChange) { - return didIncrease ? `+${value}%` : `-${value}%`; - } - return `${value}%`; -}*/ - export default ValidatorsStats; diff --git a/web-app/src/modules/core/routes/Validators/components/Vouch/FamilyIcon.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/FamilyIcon.tsx new file mode 100644 index 0000000..8f42f9b --- /dev/null +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/FamilyIcon.tsx @@ -0,0 +1,9 @@ +export const FamilyIcon: React.FC<{ family: string }> = ({ family }) => { + const bgColor = family && family.length > 8 ? `#${family.slice(2, 8)}` : `#f87171`; + return ( + + ); +}; diff --git a/web-app/src/modules/core/routes/Validators/components/Vouch/VouchChip.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchChip.tsx new file mode 100644 index 0000000..da225e5 --- /dev/null +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchChip.tsx @@ -0,0 +1,76 @@ +// VouchChip.tsx +import React from 'react'; +import clsx from 'clsx'; +import { legendItems } from './legendItems'; +import { formatAddress } from '../../../../../../utils'; +import { FamilyIcon } from './FamilyIcon'; + +interface VouchDetails { + inSet: boolean; + compliant: boolean; + epochsToExpire: number; + handle?: string; + address: string; + family: string; +} + +export const VouchChip: React.FC<{ vouch: VouchDetails; index: number }> = ({ vouch, index }) => { + // Determine which icons to display based on vouch properties + const activeIcons = legendItems.filter((item) => { + switch (item.title) { + case 'In Set': + return vouch.inSet; + case 'Compliant': + return vouch.compliant; + case 'Non-Compliant': + return !vouch.compliant; + case 'Expiring Soon': + return vouch.epochsToExpire <= 7 && vouch.epochsToExpire > 0; + case 'Expired': + return vouch.epochsToExpire <= 0; + default: + return false; + } + }); + + return ( + + {activeIcons.map((item, idx) => ( + + + + {item.title} + + + ))} + + {/* Display Family Icon */} + {vouch.family && ( + + + + Family Color + + + )} + + {/* Display Handle or Address */} + {vouch.handle || formatAddress(vouch.address)} + + ({vouch.epochsToExpire}) + + + ); +}; + +export default VouchChip; diff --git a/web-app/src/modules/core/routes/Validators/components/Vouch/VouchLegend.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchLegend.tsx new file mode 100644 index 0000000..5beca35 --- /dev/null +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchLegend.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import clsx from 'clsx'; +import { legendItems } from './legendItems'; + +const VouchLegend: React.FC = () => { + return ( +
+

Vouch Legend

+ +
+ ); +}; + +export default VouchLegend; diff --git a/web-app/src/modules/core/routes/Validators/components/Vouches.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/Vouches.tsx similarity index 98% rename from web-app/src/modules/core/routes/Validators/components/Vouches.tsx rename to web-app/src/modules/core/routes/Validators/components/Vouch/Vouches.tsx index 4d97b8d..cf4f2ef 100644 --- a/web-app/src/modules/core/routes/Validators/components/Vouches.tsx +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/Vouches.tsx @@ -2,7 +2,7 @@ import { FC, useState } from 'react'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'; import { CheckIcon, XMarkIcon } from '@heroicons/react/24/outline'; -import AccountAddress from '../../../../ui/AccountAddress'; +import AccountAddress from '../../../../../ui/AccountAddress'; interface Vouches { compliant: boolean; diff --git a/web-app/src/modules/core/routes/Validators/components/VouchesRow.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchesRow.tsx similarity index 53% rename from web-app/src/modules/core/routes/Validators/components/VouchesRow.tsx rename to web-app/src/modules/core/routes/Validators/components/Vouch/VouchesRow.tsx index 6a54617..e30fafe 100644 --- a/web-app/src/modules/core/routes/Validators/components/VouchesRow.tsx +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchesRow.tsx @@ -1,24 +1,17 @@ import React from 'react'; -import { - CheckIcon, - XMarkIcon, - ExclamationTriangleIcon, - ClockIcon, - GlobeAltIcon, -} from '@heroicons/react/20/solid'; +import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; -import AccountAddress from '../../../../ui/AccountAddress'; -import { ValidatorVouches, VouchDetails } from '../../../../interface/Validator.interface'; +import AccountAddress from '../../../../../ui/AccountAddress'; +import { ValidatorVouches, VouchDetails } from '../../../../../interface/Validator.interface'; +import { VouchChip } from './VouchChip'; +import { FamilyIcon } from './FamilyIcon'; +import { formatAddress } from '../../../../../../utils'; type VouchesRowProps = { validator: ValidatorVouches; showExpired: boolean; }; -function formatAddress(address: string): string { - return address.slice(0, 4) + '...' + address.slice(-4); -} - const VouchesRow: React.FC = ({ validator, showExpired }) => { return ( = ({ validator, showExpired }) => { ); }; -const FamilyIcon: React.FC<{ family: string }> = ({ family }) => { - const bgColor = family && family.length > 8 ? `#${family.slice(2, 8)}` : `#f87171`; - return ( - - ); -}; - -const VouchChip: React.FC<{ vouch: VouchDetails; index: number }> = ({ vouch, index }) => { - // Generate background color for the chip based on the family (hexadecimal) - return ( - - {vouch.inSet && } - {vouch.compliant ? ( - - ) : ( - - )} - - {vouch.epochsToExpire <= 7 && vouch.epochsToExpire > 0 && ( - - )} - - {vouch.epochsToExpire <= 0 && } - - {/* handle or address 1234...5678*/} - {vouch.handle || formatAddress(vouch.address)} - - {' '} - ({vouch.epochsToExpire}) - - - - - ); -}; - export default VouchesRow; diff --git a/web-app/src/modules/core/routes/Validators/components/VouchesRowSkeleton.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchesRowSkeleton.tsx similarity index 100% rename from web-app/src/modules/core/routes/Validators/components/VouchesRowSkeleton.tsx rename to web-app/src/modules/core/routes/Validators/components/Vouch/VouchesRowSkeleton.tsx diff --git a/web-app/src/modules/core/routes/Validators/components/VouchesTable.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchesTable.tsx similarity index 96% rename from web-app/src/modules/core/routes/Validators/components/VouchesTable.tsx rename to web-app/src/modules/core/routes/Validators/components/Vouch/VouchesTable.tsx index ff288cf..12742f4 100644 --- a/web-app/src/modules/core/routes/Validators/components/VouchesTable.tsx +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/VouchesTable.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { gql, useQuery } from '@apollo/client'; -import SortableTh from './SortableTh'; +import SortableTh from '../SortableTh'; import VouchesRow from './VouchesRow'; -import { ValidatorVouches } from '../../../../interface/Validator.interface'; +import { ValidatorVouches } from '../../../../../interface/Validator.interface'; import VouchesRowSkeleton from './VouchesRowSkeleton'; -import VouchesLegend from './VouchesLegend'; +import VouchLegend from './VouchLegend'; const GET_VALIDATORS = gql` query GetValidatorsVouches { @@ -179,7 +179,7 @@ const VouchesTable: React.FC = () => { ))} - +

The data in this table is updated every 60 seconds.

diff --git a/web-app/src/modules/core/routes/Validators/components/Vouch/legendItems.tsx b/web-app/src/modules/core/routes/Validators/components/Vouch/legendItems.tsx new file mode 100644 index 0000000..a9a9116 --- /dev/null +++ b/web-app/src/modules/core/routes/Validators/components/Vouch/legendItems.tsx @@ -0,0 +1,59 @@ +// legendItems.tsx +import { + GlobeAltIcon, + CheckIcon, + XMarkIcon, + ExclamationTriangleIcon, + ClockIcon, +} from '@heroicons/react/20/solid'; + +export interface LegendItem { + Icon: React.ComponentType>; + color: string; + title: string; + description: string; +} + +export const legendItems: LegendItem[] = [ + { + Icon: GlobeAltIcon, + color: 'text-blue-500', // Color for "In Set" + title: 'In Set', + description: 'The validator is part of the current active set.', + }, + { + Icon: CheckIcon, + color: 'text-green-500', // Color for "Compliant" + title: 'Compliant', + description: 'The validator is compliant with audit qualifications.', + }, + { + Icon: XMarkIcon, + color: 'text-red-500', // Color for "Non-Compliant" + title: 'Non-Compliant', + description: 'The validator is not compliant with audit qualifications.', + }, + { + Icon: ExclamationTriangleIcon, + color: 'text-yellow-500', // Color for "Expiring Soon" + title: 'Expiring Soon', + description: 'Vouch will expire in less than 7 epochs.', + }, + { + Icon: ClockIcon, + color: 'text-red-500', // Color for "Expired" + title: 'Expired', + description: 'The vouch has expired.', + }, + { + Icon: () => ( + + ), + color: '', // No icon, so no color + title: 'Family Color', + description: 'Represents the vouch family grouping.', + }, +]; diff --git a/web-app/src/modules/core/routes/Validators/components/VouchesLegend.tsx b/web-app/src/modules/core/routes/Validators/components/VouchesLegend.tsx deleted file mode 100644 index bc4909e..0000000 --- a/web-app/src/modules/core/routes/Validators/components/VouchesLegend.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import { - GlobeAltIcon, - CheckIcon, - XMarkIcon, - ExclamationTriangleIcon, - ClockIcon, -} from '@heroicons/react/20/solid'; -import clsx from 'clsx'; - -const VouchLegend: React.FC = () => { - const legendItems = [ - { - Icon: GlobeAltIcon, - color: 'text-blue-500', // Color for "In Set" - title: 'In Set', - description: 'The validator is part of the current active set.', - }, - { - Icon: CheckIcon, - color: 'text-green-500', // Color for "Compliant" - title: 'Compliant', - description: 'The validator is compliant with audit qualifications.', - }, - { - Icon: XMarkIcon, - color: 'text-red-500', // Color for "Non-Compliant" - title: 'Non-Compliant', - description: 'The validator is not compliant with audit qualifications.', - }, - { - Icon: ExclamationTriangleIcon, - color: 'text-yellow-500', // Color for "Expiring Soon" - title: 'Expiring Soon', - description: 'Vouch will expire in less than 7 epochs.', - }, - { - Icon: ClockIcon, - color: 'text-red-500', // Color for "Expired" - title: 'Expired', - description: 'The vouch has expired.', - }, - { - Icon: () => ( - - ), - color: '', // No icon, so no color - title: 'Family Color', - description: 'Represents the vouch family grouping.', - }, - ]; - - return ( -
-

Vouch Legend

-
    - {legendItems.map((item, index) => ( -
  • - - {item.title}: - {item.description} -
  • - ))} -
-
- ); -}; - -export default VouchLegend; diff --git a/web-app/src/utils.ts b/web-app/src/utils.ts index 2bc6f25..03cf4ba 100644 --- a/web-app/src/utils.ts +++ b/web-app/src/utils.ts @@ -29,3 +29,7 @@ export const normalizeAddress = (address: string): string => { return addr; }; + +export function formatAddress(address: string): string { + return address.slice(0, 4) + '...' + address.slice(-4); +}