From 76b70651c4d8f47d25fb7f62f5d389177d5fff74 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Thu, 19 Oct 2023 11:08:29 -0400 Subject: [PATCH 1/6] refactor(StakingProductsCardGrid): improve type clarity and constraints --- .../Staking/StakingProductsCardGrid.tsx | 177 ++++++++++-------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/src/components/Staking/StakingProductsCardGrid.tsx b/src/components/Staking/StakingProductsCardGrid.tsx index c573d06469d..67958d61d7d 100644 --- a/src/components/Staking/StakingProductsCardGrid.tsx +++ b/src/components/Staking/StakingProductsCardGrid.tsx @@ -1,4 +1,4 @@ -import React, { ComponentType, SVGProps, useEffect, useState } from "react" +import React, { useEffect, useState } from "react" import { shuffle } from "lodash" import { Badge, @@ -8,7 +8,7 @@ import { Flex, Heading, HStack, - Icon, + Icon as ChakraIcon, List, ListIcon, ListItem, @@ -44,28 +44,29 @@ enum FlagType { UNKNOWN = "unknown", } -const getIconFromName = ( - imageName: string -): ComponentType> => { +const getIconFromName = (imageName: string): typeof ChakraIcon | undefined => { const { [imageName + "GlyphIcon"]: Icon } = require("../icons/staking") return Icon } -const Status: React.FC<{ status: FlagType }> = ({ status }) => { +const Status = ({ status }: { status: FlagType }) => { if (!status) return null - const styles = { fontSize: "2xl", m: 0 } - switch (status) { - case "green-check": - return - case "caution": - return - case "warning": - case "false": - return - default: - return + const getStatusIcon = () => { + switch (status) { + case "green-check": + return GreenCheckProductGlyphIcon + case "caution": + return CautionProductGlyphIcon + case "warning": + case "false": + return WarningProductGlyphIcon + default: + return UnknownProductGlyphIcon + } } + + return } const StakingBadge: React.FC<{ @@ -93,20 +94,20 @@ type Product = { url: string platforms: Array ui: Array - minEth: number + minEth?: number openSource: FlagType audited: FlagType bugBounty: FlagType battleTested: FlagType - trustless: FlagType - selfCustody: FlagType - liquidityToken: FlagType - permissionless: FlagType - permissionlessNodes: FlagType - multiClient: FlagType - consensusDiversity: FlagType - executionDiversity: FlagType - economical: FlagType + trustless?: FlagType + selfCustody?: FlagType + liquidityToken?: FlagType + permissionless?: FlagType + permissionlessNodes?: FlagType + multiClient?: FlagType + consensusDiversity?: FlagType + executionDiversity?: FlagType + economical?: FlagType matomo: MatomoEventOptions } interface ICardProps { @@ -139,7 +140,8 @@ const StakingProductCard: React.FC = ({ }, }) => { const Svg = getIconFromName(imageName) - const data = [ + type DataType = { label: JSX.Element; status?: FlagType } + const data: DataType[] = [ { label: , status: openSource, @@ -192,7 +194,11 @@ const StakingProductCard: React.FC = ({ label: , status: economical, }, - ].filter(({ status }) => !!status) + ] + + const filteredData = data.filter( + (item): item is Required => !!item.status + ) return ( = ({ borderRadius="base" maxH={24} > - {!!Svg && } + {!!Svg && } {name} @@ -250,8 +256,8 @@ const StakingProductCard: React.FC = ({ - {data && - data.map(({ label, status }, idx) => ( + {filteredData && + filteredData.map(({ label, status }, idx) => ( = ({ ) } +type StakingProductsType = typeof stakingProducts + +type NodeToolsType = StakingProductsType["nodeTools"] +type KeyGenType = StakingProductsType["keyGen"] +type PoolsType = StakingProductsType["pools"] +type SaasType = StakingProductsType["saas"] + +type StakingProductsCategoryKeys = keyof StakingProductsType + +type StakingCategoryType = + StakingProductsType[StakingProductsCategoryKeys][number] + export interface IProps { - category: string + category: StakingProductsCategoryKeys } const StakingProductCardGrid: React.FC = ({ category }) => { @@ -324,8 +342,12 @@ const StakingProductCardGrid: React.FC = ({ category }) => { return product.multiClient === FlagType.VALID ? 1 : 0 } - const scoreClientDiversity = (flag: FlagType): 2 | 1 | 0 => { - return flag === FlagType.VALID ? 2 : flag === FlagType.WARNING ? 1 : 0 + const scoreClientDiversity = (flag: FlagType | undefined): 2 | 1 | 0 => { + if (flag === FlagType.VALID) return 2 + + if (flag === FlagType.WARNING) return 1 + + return 0 } const scoreEconomical = (product: Product): 1 | 0 => { @@ -348,75 +370,83 @@ const StakingProductCardGrid: React.FC = ({ category }) => { return score } - const getBattleTestedFlag = (_launchDate: string): FlagType => { - let battleTested = FlagType.WARNING + const getBattleTestedFlag = ( + _launchDate: string + ): FlagType.CAUTION | FlagType.WARNING | FlagType.VALID => { const launchDate = new Date(_launchDate) const now = new Date() const halfYearAgo = new Date() const oneYearAgo = new Date() halfYearAgo.setDate(now.getDate() - 183) oneYearAgo.setDate(now.getDate() - 365) + if (halfYearAgo > launchDate) { - battleTested = FlagType.CAUTION + return FlagType.CAUTION } + if (oneYearAgo > launchDate) { - battleTested = FlagType.VALID + return FlagType.VALID } - return battleTested + + return FlagType.WARNING } const getDiversityOfClients = ( _pctMajorityClient: number | null - ): FlagType => { + ): FlagType.VALID | FlagType.UNKNOWN | FlagType.WARNING => { if (_pctMajorityClient === null) return FlagType.UNKNOWN if (_pctMajorityClient > 50) return FlagType.WARNING return FlagType.VALID } - const getFlagFromBoolean = (bool: boolean): FlagType => - !!bool ? FlagType.VALID : FlagType.FALSE + const getFlagFromBoolean = (bool: boolean) => + bool ? FlagType.VALID : FlagType.FALSE const getBrandProperties = ({ - name, - imageName, hue, - url, - socials, - matomo, - }) => ({ - name, - imageName, + ...rest + }: Pick< + StakingCategoryType, + "name" | "imageName" | "hue" | "url" | "matomo" + >): Pick => ({ color: `hsla(${hue}, ${SAT}, ${LUM}, 1)`, - url, - socials, - matomo, + ...rest, }) - const getTagProperties = ({ platforms, ui }) => ({ - platforms, - ui, - }) + const getTagProperties = ( + props: Pick + ): Pick => props - const getSharedSecurityProperties = ({ - isFoss, - audits, - hasBugBounty, - launchDate, - }) => ({ - openSource: getFlagFromBoolean(isFoss), - audited: getFlagFromBoolean(audits?.length), - bugBounty: getFlagFromBoolean(hasBugBounty), - battleTested: getBattleTestedFlag(launchDate), + const getSharedSecurityProperties = ( + props: Pick< + StakingCategoryType, + "isFoss" | "audits" | "hasBugBounty" | "launchDate" + > + ): Pick< + Product, + "openSource" | "audited" | "bugBounty" | "battleTested" + > => ({ + openSource: getFlagFromBoolean(props.isFoss), + audited: getFlagFromBoolean(!!props.audits?.length), + bugBounty: getFlagFromBoolean(props.hasBugBounty), + battleTested: getBattleTestedFlag(props.launchDate), }) useEffect(() => { const categoryProducts = stakingProducts[category] const products: Array = [] + function mapCatProducts( + products: T[], + cb: (listing: T) => Product + ) { + return products.map((item) => cb(item)) + } + // Pooled staking services if (category === "pools") { products.push( - ...categoryProducts.map((listing) => ({ + ...mapCatProducts(categoryProducts as PoolsType, (listing) => ({ ...getBrandProperties(listing), ...getTagProperties(listing), ...getSharedSecurityProperties(listing), @@ -430,7 +460,7 @@ const StakingProductCardGrid: React.FC = ({ category }) => { consensusDiversity: getDiversityOfClients( listing.pctMajorityConsensusClient ), - liquidityToken: getFlagFromBoolean(listing.tokens?.length), + liquidityToken: getFlagFromBoolean(!!listing.tokens?.length), minEth: listing.minEth, })) ) @@ -439,7 +469,7 @@ const StakingProductCardGrid: React.FC = ({ category }) => { // Solo staking products if (category === "nodeTools") { products.push( - ...categoryProducts.map((listing) => ({ + ...mapCatProducts(categoryProducts as NodeToolsType, (listing) => ({ ...getBrandProperties(listing), ...getTagProperties(listing), ...getSharedSecurityProperties(listing), @@ -455,7 +485,7 @@ const StakingProductCardGrid: React.FC = ({ category }) => { // Staking as a service if (category === "saas") { products.push( - ...categoryProducts.map((listing) => ({ + ...mapCatProducts(categoryProducts as SaasType, (listing) => ({ ...getBrandProperties(listing), ...getTagProperties(listing), ...getSharedSecurityProperties(listing), @@ -474,7 +504,7 @@ const StakingProductCardGrid: React.FC = ({ category }) => { // Key generators if (category === "keyGen") { products.push( - ...categoryProducts.map((listing) => ({ + ...mapCatProducts(categoryProducts as KeyGenType, (listing) => ({ ...getBrandProperties(listing), ...getTagProperties(listing), ...getSharedSecurityProperties(listing), @@ -494,7 +524,6 @@ const StakingProductCardGrid: React.FC = ({ category }) => { .sort((a, b) => b.rankingScore - a.rankingScore) ) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) if (!rankedProducts) return null From 46de55112de0e481bc4efe707f79280eba0b82ae Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Thu, 19 Oct 2023 11:40:06 -0400 Subject: [PATCH 2/6] refactor(StakingProductsCardGrid): code-split components and logic --- .../Staking/StakingProductsCardGrid.tsx | 545 ------------------ .../StakingProductCard.tsx | 247 ++++++++ .../Staking/StakingProductsCardGrid/index.tsx | 30 + .../Staking/StakingProductsCardGrid/types.ts | 46 ++ .../useStakingProductsCardGrid.ts | 128 ++++ .../Staking/StakingProductsCardGrid/utils.ts | 138 +++++ 6 files changed, 589 insertions(+), 545 deletions(-) delete mode 100644 src/components/Staking/StakingProductsCardGrid.tsx create mode 100644 src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx create mode 100644 src/components/Staking/StakingProductsCardGrid/index.tsx create mode 100644 src/components/Staking/StakingProductsCardGrid/types.ts create mode 100644 src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts create mode 100644 src/components/Staking/StakingProductsCardGrid/utils.ts diff --git a/src/components/Staking/StakingProductsCardGrid.tsx b/src/components/Staking/StakingProductsCardGrid.tsx deleted file mode 100644 index 67958d61d7d..00000000000 --- a/src/components/Staking/StakingProductsCardGrid.tsx +++ /dev/null @@ -1,545 +0,0 @@ -import React, { useEffect, useState } from "react" -import { shuffle } from "lodash" -import { - Badge, - Box, - BoxProps, - Center, - Flex, - Heading, - HStack, - Icon as ChakraIcon, - List, - ListIcon, - ListItem, - SimpleGrid, - useColorModeValue, -} from "@chakra-ui/react" -// Data imports -import stakingProducts from "../../data/staking-products.json" -// Component imports -import { ButtonLink } from "../Buttons" -import Translation from "../Translation" -// SVG imports -import { - CautionProductGlyphIcon, - GreenCheckProductGlyphIcon, - UnknownProductGlyphIcon, - WarningProductGlyphIcon, -} from "../icons/staking" - -import { MatomoEventOptions } from "../../utils/matomo" -// When adding a product svg, be sure to add to mapping below as well. - -const PADDED_DIV_STYLE: BoxProps = { - px: 8, - py: 6, -} - -enum FlagType { - VALID = "green-check", - CAUTION = "caution", - WARNING = "warning", - FALSE = "false", - UNKNOWN = "unknown", -} - -const getIconFromName = (imageName: string): typeof ChakraIcon | undefined => { - const { [imageName + "GlyphIcon"]: Icon } = require("../icons/staking") - return Icon -} - -const Status = ({ status }: { status: FlagType }) => { - if (!status) return null - - const getStatusIcon = () => { - switch (status) { - case "green-check": - return GreenCheckProductGlyphIcon - case "caution": - return CautionProductGlyphIcon - case "warning": - case "false": - return WarningProductGlyphIcon - default: - return UnknownProductGlyphIcon - } - } - - return -} - -const StakingBadge: React.FC<{ - type: "ui" | "platform" - children: React.ReactNode -}> = ({ type, children }) => { - const uiTypeColor = type === "ui" && "stakingPillUI" - const platformTypeColor = type === "platform" && "stakingPillPlatform" - - return ( - - {children} - - ) -} - -type Product = { - name: string - imageName: string - color: string - url: string - platforms: Array - ui: Array - minEth?: number - openSource: FlagType - audited: FlagType - bugBounty: FlagType - battleTested: FlagType - trustless?: FlagType - selfCustody?: FlagType - liquidityToken?: FlagType - permissionless?: FlagType - permissionlessNodes?: FlagType - multiClient?: FlagType - consensusDiversity?: FlagType - executionDiversity?: FlagType - economical?: FlagType - matomo: MatomoEventOptions -} -interface ICardProps { - product: Product -} - -const StakingProductCard: React.FC = ({ - product: { - name, - imageName, - color, - url, - platforms, - ui, - minEth, - openSource, - audited, - bugBounty, - battleTested, - trustless, - selfCustody, - liquidityToken, - permissionless, - permissionlessNodes, - multiClient, - consensusDiversity, - executionDiversity, - economical, - matomo, - }, -}) => { - const Svg = getIconFromName(imageName) - type DataType = { label: JSX.Element; status?: FlagType } - const data: DataType[] = [ - { - label: , - status: openSource, - }, - { - label: , - status: audited, - }, - { - label: , - status: bugBounty, - }, - { - label: , - status: battleTested, - }, - { - label: , - status: trustless, - }, - { - label: , - status: permissionless, - }, - { - label: , - status: permissionlessNodes, - }, - { - label: , - status: multiClient, - }, - { - label: , - status: executionDiversity, - }, - { - label: , - status: consensusDiversity, - }, - { - label: , - status: selfCustody, - }, - { - label: , - status: liquidityToken, - }, - { - label: , - status: economical, - }, - ] - - const filteredData = data.filter( - (item): item is Required => !!item.status - ) - - return ( - - - {!!Svg && } - - {name} - - - {typeof minEth !== "undefined" && ( -
- {minEth > 0 ? `From ${minEth} ETH` : "Any amount"} -
- )} - - {platforms && - platforms.map((platform, idx) => ( - - {platform} - - ))} - {ui && - ui.map((_ui, idx) => ( - - {_ui} - - ))} - - - - {filteredData && - filteredData.map(({ label, status }, idx) => ( - - - {label} - - ))} - - - - - - - -
- ) -} - -type StakingProductsType = typeof stakingProducts - -type NodeToolsType = StakingProductsType["nodeTools"] -type KeyGenType = StakingProductsType["keyGen"] -type PoolsType = StakingProductsType["pools"] -type SaasType = StakingProductsType["saas"] - -type StakingProductsCategoryKeys = keyof StakingProductsType - -type StakingCategoryType = - StakingProductsType[StakingProductsCategoryKeys][number] - -export interface IProps { - category: StakingProductsCategoryKeys -} - -const StakingProductCardGrid: React.FC = ({ category }) => { - const [rankedProducts, updateRankedProducts] = useState>([]) - const [SAT, LUM] = useColorModeValue(["75%", "60%"], ["50%", "35%"]) - - const scoreOpenSource = (product: Product): 1 | 0 => { - return product.openSource === FlagType.VALID ? 1 : 0 - } - - const scoreAudited = (product: Product): 1 | 0 => { - return product.audited === FlagType.VALID ? 1 : 0 - } - - const scoreBugBounty = (product: Product): 1 | 0 => { - return product.bugBounty === FlagType.VALID ? 1 : 0 - } - 1 - const scoreBattleTested = (product: Product): 2 | 1 | 0 => { - return product.battleTested === FlagType.VALID - ? 2 - : product.battleTested === FlagType.CAUTION - ? 1 - : 0 - } - - const scoreTrustless = (product: Product): 1 | 0 => { - return product.trustless === FlagType.VALID ? 1 : 0 - } - - const scorePermissionless = (product: Product): 1 | 0 => { - return product.permissionless === FlagType.VALID ? 1 : 0 - } - - const scorePermissionlessNodes = (product: Product): 1 | 0 => { - return product.permissionlessNodes === FlagType.VALID ? 1 : 0 - } - - const scoreMultiClient = (product: Product): 1 | 0 => { - return product.multiClient === FlagType.VALID ? 1 : 0 - } - - const scoreClientDiversity = (flag: FlagType | undefined): 2 | 1 | 0 => { - if (flag === FlagType.VALID) return 2 - - if (flag === FlagType.WARNING) return 1 - - return 0 - } - - const scoreEconomical = (product: Product): 1 | 0 => { - return product.economical === FlagType.VALID ? 1 : 0 - } - - const getRankingScore = (product: Product): number => { - let score = 0 - score += scoreOpenSource(product) - score += scoreAudited(product) - score += scoreBugBounty(product) - score += scoreBattleTested(product) - score += scoreTrustless(product) - score += scorePermissionless(product) - score += scorePermissionlessNodes(product) - score += scoreMultiClient(product) - score += scoreClientDiversity(product.executionDiversity) - score += scoreClientDiversity(product.consensusDiversity) - score += scoreEconomical(product) - return score - } - - const getBattleTestedFlag = ( - _launchDate: string - ): FlagType.CAUTION | FlagType.WARNING | FlagType.VALID => { - const launchDate = new Date(_launchDate) - const now = new Date() - const halfYearAgo = new Date() - const oneYearAgo = new Date() - halfYearAgo.setDate(now.getDate() - 183) - oneYearAgo.setDate(now.getDate() - 365) - - if (halfYearAgo > launchDate) { - return FlagType.CAUTION - } - - if (oneYearAgo > launchDate) { - return FlagType.VALID - } - - return FlagType.WARNING - } - - const getDiversityOfClients = ( - _pctMajorityClient: number | null - ): FlagType.VALID | FlagType.UNKNOWN | FlagType.WARNING => { - if (_pctMajorityClient === null) return FlagType.UNKNOWN - if (_pctMajorityClient > 50) return FlagType.WARNING - return FlagType.VALID - } - - const getFlagFromBoolean = (bool: boolean) => - bool ? FlagType.VALID : FlagType.FALSE - - const getBrandProperties = ({ - hue, - ...rest - }: Pick< - StakingCategoryType, - "name" | "imageName" | "hue" | "url" | "matomo" - >): Pick => ({ - color: `hsla(${hue}, ${SAT}, ${LUM}, 1)`, - ...rest, - }) - - const getTagProperties = ( - props: Pick - ): Pick => props - - const getSharedSecurityProperties = ( - props: Pick< - StakingCategoryType, - "isFoss" | "audits" | "hasBugBounty" | "launchDate" - > - ): Pick< - Product, - "openSource" | "audited" | "bugBounty" | "battleTested" - > => ({ - openSource: getFlagFromBoolean(props.isFoss), - audited: getFlagFromBoolean(!!props.audits?.length), - bugBounty: getFlagFromBoolean(props.hasBugBounty), - battleTested: getBattleTestedFlag(props.launchDate), - }) - - useEffect(() => { - const categoryProducts = stakingProducts[category] - const products: Array = [] - - function mapCatProducts( - products: T[], - cb: (listing: T) => Product - ) { - return products.map((item) => cb(item)) - } - - // Pooled staking services - if (category === "pools") { - products.push( - ...mapCatProducts(categoryProducts as PoolsType, (listing) => ({ - ...getBrandProperties(listing), - ...getTagProperties(listing), - ...getSharedSecurityProperties(listing), - trustless: getFlagFromBoolean(listing.isTrustless), - permissionlessNodes: getFlagFromBoolean( - listing.hasPermissionlessNodes - ), - executionDiversity: getDiversityOfClients( - listing.pctMajorityExecutionClient - ), - consensusDiversity: getDiversityOfClients( - listing.pctMajorityConsensusClient - ), - liquidityToken: getFlagFromBoolean(!!listing.tokens?.length), - minEth: listing.minEth, - })) - ) - } - - // Solo staking products - if (category === "nodeTools") { - products.push( - ...mapCatProducts(categoryProducts as NodeToolsType, (listing) => ({ - ...getBrandProperties(listing), - ...getTagProperties(listing), - ...getSharedSecurityProperties(listing), - trustless: getFlagFromBoolean(listing.isTrustless), - permissionless: getFlagFromBoolean(listing.isPermissionless), - multiClient: getFlagFromBoolean(listing.multiClient), - selfCustody: getFlagFromBoolean(true), - economical: getFlagFromBoolean(listing.minEth < 32), - minEth: listing.minEth, - })) - ) - } - // Staking as a service - if (category === "saas") { - products.push( - ...mapCatProducts(categoryProducts as SaasType, (listing) => ({ - ...getBrandProperties(listing), - ...getTagProperties(listing), - ...getSharedSecurityProperties(listing), - permissionless: getFlagFromBoolean(listing.isPermissionless), - executionDiversity: getDiversityOfClients( - listing.pctMajorityExecutionClient - ), - consensusDiversity: getDiversityOfClients( - listing.pctMajorityConsensusClient - ), - selfCustody: getFlagFromBoolean(listing.isSelfCustody), - minEth: listing.minEth, - })) - ) - } - // Key generators - if (category === "keyGen") { - products.push( - ...mapCatProducts(categoryProducts as KeyGenType, (listing) => ({ - ...getBrandProperties(listing), - ...getTagProperties(listing), - ...getSharedSecurityProperties(listing), - permissionless: getFlagFromBoolean(listing.isPermissionless), - selfCustody: getFlagFromBoolean(listing.isSelfCustody), - })) - ) - } - - if (products) { - updateRankedProducts( - shuffle(products) - .map((product) => ({ - ...product, - rankingScore: getRankingScore(product), - })) - .sort((a, b) => b.rankingScore - a.rankingScore) - ) - } - }, []) - - if (!rankedProducts) return null - - return ( - - {rankedProducts.map((product) => ( - - ))} - - ) -} - -export default StakingProductCardGrid diff --git a/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx new file mode 100644 index 00000000000..ef7620a6183 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx @@ -0,0 +1,247 @@ +import * as React from "react" +import { + Badge, + Box, + BoxProps, + Center, + Flex, + Heading, + HStack, + Icon as ChakraIcon, + List, + ListIcon, + ListItem, +} from "@chakra-ui/react" +import { ButtonLink } from "../../Buttons" +import Translation from "../../Translation" +import { + CautionProductGlyphIcon, + GreenCheckProductGlyphIcon, + UnknownProductGlyphIcon, + WarningProductGlyphIcon, +} from "../../icons/staking" +import { FlagType, Product } from "./types" + +const getIconFromName = (imageName: string): typeof ChakraIcon | undefined => { + const { [imageName + "GlyphIcon"]: Icon } = require("../icons/staking") + return Icon +} + +export const Status = ({ status }: { status: FlagType }) => { + if (!status) return null + + const getStatusIcon = () => { + switch (status) { + case "green-check": + return GreenCheckProductGlyphIcon + case "caution": + return CautionProductGlyphIcon + case "warning": + case "false": + return WarningProductGlyphIcon + default: + return UnknownProductGlyphIcon + } + } + + return +} + +const StakingBadge: React.FC<{ + type: "ui" | "platform" + children: React.ReactNode +}> = ({ type, children }) => { + const uiTypeColor = type === "ui" && "stakingPillUI" + const platformTypeColor = type === "platform" && "stakingPillPlatform" + + return ( + + {children} + + ) +} + +interface ICardProps { + product: Product +} + +export const StakingProductCard: React.FC = ({ + product: { + name, + imageName, + color, + url, + platforms, + ui, + minEth, + openSource, + audited, + bugBounty, + battleTested, + trustless, + selfCustody, + liquidityToken, + permissionless, + permissionlessNodes, + multiClient, + consensusDiversity, + executionDiversity, + economical, + matomo, + }, +}) => { + const PADDED_DIV_STYLE: BoxProps = { + px: 8, + py: 6, + } + + const Svg = getIconFromName(imageName) + type DataType = { label: JSX.Element; status?: FlagType } + const data: DataType[] = [ + { + label: , + status: openSource, + }, + { + label: , + status: audited, + }, + { + label: , + status: bugBounty, + }, + { + label: , + status: battleTested, + }, + { + label: , + status: trustless, + }, + { + label: , + status: permissionless, + }, + { + label: , + status: permissionlessNodes, + }, + { + label: , + status: multiClient, + }, + { + label: , + status: executionDiversity, + }, + { + label: , + status: consensusDiversity, + }, + { + label: , + status: selfCustody, + }, + { + label: , + status: liquidityToken, + }, + { + label: , + status: economical, + }, + ] + + const filteredData = data.filter( + (item): item is Required => !!item.status + ) + + return ( + + + {!!Svg && } + + {name} + + + {typeof minEth !== "undefined" && ( +
+ {minEth > 0 ? `From ${minEth} ETH` : "Any amount"} +
+ )} + + {platforms && + platforms.map((platform, idx) => ( + + {platform} + + ))} + {ui && + ui.map((_ui, idx) => ( + + {_ui} + + ))} + + + + {filteredData && + filteredData.map(({ label, status }, idx) => ( + + + {label} + + ))} + + + + + + + +
+ ) +} diff --git a/src/components/Staking/StakingProductsCardGrid/index.tsx b/src/components/Staking/StakingProductsCardGrid/index.tsx new file mode 100644 index 00000000000..28d66354517 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/index.tsx @@ -0,0 +1,30 @@ +import React from "react" +import { SimpleGrid } from "@chakra-ui/react" +import { StakingProductCard } from "./StakingProductCard" +import { useStakingProductsCardGrid } from "./useStakingProductsCardGrid" +import { StakingProductsCategoryKeys } from "./types" + +export interface IProps { + category: StakingProductsCategoryKeys +} + +const StakingProductCardGrid: React.FC = ({ category }) => { + const { rankedProducts } = useStakingProductsCardGrid({ category }) + + if (!rankedProducts) return null + + return ( + + {rankedProducts.map((product) => ( + + ))} + + ) +} + +export default StakingProductCardGrid diff --git a/src/components/Staking/StakingProductsCardGrid/types.ts b/src/components/Staking/StakingProductsCardGrid/types.ts new file mode 100644 index 00000000000..9d822f2ad34 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/types.ts @@ -0,0 +1,46 @@ +import { type MatomoEventOptions } from "../../../utils/matomo" +import type stakingProducts from "../../../data/staking-products.json" + +export enum FlagType { + VALID = "green-check", + CAUTION = "caution", + WARNING = "warning", + FALSE = "false", + UNKNOWN = "unknown", +} + +export type Product = { + name: string + imageName: string + color: string + url: string + platforms: Array + ui: Array + minEth?: number + openSource: FlagType + audited: FlagType + bugBounty: FlagType + battleTested: FlagType + trustless?: FlagType + selfCustody?: FlagType + liquidityToken?: FlagType + permissionless?: FlagType + permissionlessNodes?: FlagType + multiClient?: FlagType + consensusDiversity?: FlagType + executionDiversity?: FlagType + economical?: FlagType + matomo: MatomoEventOptions +} + +type StakingProductsType = typeof stakingProducts + +export type NodeToolsType = StakingProductsType["nodeTools"] +export type KeyGenType = StakingProductsType["keyGen"] +export type PoolsType = StakingProductsType["pools"] +export type SaasType = StakingProductsType["saas"] + +export type StakingProductsCategoryKeys = keyof StakingProductsType + +export type StakingCategoryType = + StakingProductsType[StakingProductsCategoryKeys][number] diff --git a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts new file mode 100644 index 00000000000..b3ff475b677 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts @@ -0,0 +1,128 @@ +import { useEffect, useState } from "react" +import { shuffle } from "lodash" +import { useColorModeValue } from "@chakra-ui/react" +import { + KeyGenType, + NodeToolsType, + PoolsType, + Product, + SaasType, + StakingProductsCategoryKeys, +} from "./types" +import stakingProducts from "../../../data/staking-products.json" + +import { + getBrandProperties, + getTagProperties, + getSharedSecurityProperties, + getFlagFromBoolean, + getDiversityOfClients, + getRankingScore, +} from "./utils" + +export const useStakingProductsCardGrid = ({ + category, +}: { + category: StakingProductsCategoryKeys +}) => { + const [rankedProducts, updateRankedProducts] = useState>([]) + const [SAT, LUM] = useColorModeValue(["75%", "60%"], ["50%", "35%"]) + + useEffect(() => { + const categoryProducts = stakingProducts[category] + const products: Array = [] + + function mapCatProducts( + products: T[], + cb: (listing: T) => Product + ) { + return products.map((item) => cb(item)) + } + + // Pooled staking services + if (category === "pools") { + products.push( + ...mapCatProducts(categoryProducts as PoolsType, (listing) => ({ + ...getBrandProperties({ ...listing, SAT, LUM }), + ...getTagProperties(listing), + ...getSharedSecurityProperties(listing), + trustless: getFlagFromBoolean(listing.isTrustless), + permissionlessNodes: getFlagFromBoolean( + listing.hasPermissionlessNodes + ), + executionDiversity: getDiversityOfClients( + listing.pctMajorityExecutionClient + ), + consensusDiversity: getDiversityOfClients( + listing.pctMajorityConsensusClient + ), + liquidityToken: getFlagFromBoolean(!!listing.tokens?.length), + minEth: listing.minEth, + })) + ) + } + + // Solo staking products + if (category === "nodeTools") { + products.push( + ...mapCatProducts(categoryProducts as NodeToolsType, (listing) => ({ + ...getBrandProperties({ ...listing, SAT, LUM }), + ...getTagProperties(listing), + ...getSharedSecurityProperties(listing), + trustless: getFlagFromBoolean(listing.isTrustless), + permissionless: getFlagFromBoolean(listing.isPermissionless), + multiClient: getFlagFromBoolean(listing.multiClient), + selfCustody: getFlagFromBoolean(true), + economical: getFlagFromBoolean(listing.minEth < 32), + minEth: listing.minEth, + })) + ) + } + // Staking as a service + if (category === "saas") { + products.push( + ...mapCatProducts(categoryProducts as SaasType, (listing) => ({ + ...getBrandProperties({ ...listing, SAT, LUM }), + ...getTagProperties(listing), + ...getSharedSecurityProperties(listing), + permissionless: getFlagFromBoolean(listing.isPermissionless), + executionDiversity: getDiversityOfClients( + listing.pctMajorityExecutionClient + ), + consensusDiversity: getDiversityOfClients( + listing.pctMajorityConsensusClient + ), + selfCustody: getFlagFromBoolean(listing.isSelfCustody), + minEth: listing.minEth, + })) + ) + } + // Key generators + if (category === "keyGen") { + products.push( + ...mapCatProducts(categoryProducts as KeyGenType, (listing) => ({ + ...getBrandProperties({ ...listing, SAT, LUM }), + ...getTagProperties(listing), + ...getSharedSecurityProperties(listing), + permissionless: getFlagFromBoolean(listing.isPermissionless), + selfCustody: getFlagFromBoolean(listing.isSelfCustody), + })) + ) + } + + if (products) { + updateRankedProducts( + shuffle(products) + .map((product) => ({ + ...product, + rankingScore: getRankingScore(product), + })) + .sort((a, b) => b.rankingScore - a.rankingScore) + ) + } + }, []) + + return { + rankedProducts, + } +} diff --git a/src/components/Staking/StakingProductsCardGrid/utils.ts b/src/components/Staking/StakingProductsCardGrid/utils.ts new file mode 100644 index 00000000000..2c8316671cf --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/utils.ts @@ -0,0 +1,138 @@ +import { Product, FlagType, StakingCategoryType } from "./types" + +const scoreOpenSource = (product: Product): 1 | 0 => { + return product.openSource === FlagType.VALID ? 1 : 0 +} + +const scoreAudited = (product: Product): 1 | 0 => { + return product.audited === FlagType.VALID ? 1 : 0 +} + +const scoreBugBounty = (product: Product): 1 | 0 => { + return product.bugBounty === FlagType.VALID ? 1 : 0 +} +1 +const scoreBattleTested = (product: Product): 2 | 1 | 0 => { + return product.battleTested === FlagType.VALID + ? 2 + : product.battleTested === FlagType.CAUTION + ? 1 + : 0 +} + +const scoreTrustless = (product: Product): 1 | 0 => { + return product.trustless === FlagType.VALID ? 1 : 0 +} + +const scorePermissionless = (product: Product): 1 | 0 => { + return product.permissionless === FlagType.VALID ? 1 : 0 +} + +const scorePermissionlessNodes = (product: Product): 1 | 0 => { + return product.permissionlessNodes === FlagType.VALID ? 1 : 0 +} + +const scoreMultiClient = (product: Product): 1 | 0 => { + return product.multiClient === FlagType.VALID ? 1 : 0 +} + +const scoreClientDiversity = (flag: FlagType | undefined): 2 | 1 | 0 => { + if (flag === FlagType.VALID) return 2 + + if (flag === FlagType.WARNING) return 1 + + return 0 +} + +const scoreEconomical = (product: Product): 1 | 0 => { + return product.economical === FlagType.VALID ? 1 : 0 +} + +const getRankingScore = (product: Product): number => { + let score = 0 + score += scoreOpenSource(product) + score += scoreAudited(product) + score += scoreBugBounty(product) + score += scoreBattleTested(product) + score += scoreTrustless(product) + score += scorePermissionless(product) + score += scorePermissionlessNodes(product) + score += scoreMultiClient(product) + score += scoreClientDiversity(product.executionDiversity) + score += scoreClientDiversity(product.consensusDiversity) + score += scoreEconomical(product) + return score +} + +const getBattleTestedFlag = ( + _launchDate: string +): FlagType.CAUTION | FlagType.WARNING | FlagType.VALID => { + const launchDate = new Date(_launchDate) + const now = new Date() + const halfYearAgo = new Date() + const oneYearAgo = new Date() + halfYearAgo.setDate(now.getDate() - 183) + oneYearAgo.setDate(now.getDate() - 365) + + if (halfYearAgo > launchDate) { + return FlagType.CAUTION + } + + if (oneYearAgo > launchDate) { + return FlagType.VALID + } + + return FlagType.WARNING +} + +const getDiversityOfClients = ( + _pctMajorityClient: number | null +): FlagType.VALID | FlagType.UNKNOWN | FlagType.WARNING => { + if (_pctMajorityClient === null) return FlagType.UNKNOWN + if (_pctMajorityClient > 50) return FlagType.WARNING + return FlagType.VALID +} + +const getFlagFromBoolean = (bool: boolean) => + bool ? FlagType.VALID : FlagType.FALSE + +const getBrandProperties = ({ + hue, + SAT, + LUM, + ...rest +}: Pick< + StakingCategoryType, + "name" | "imageName" | "hue" | "url" | "matomo" +> & { SAT: string; LUM: string }): Pick< + Product, + "name" | "imageName" | "color" | "url" | "matomo" +> => ({ + color: `hsla(${hue}, ${SAT}, ${LUM}, 1)`, + ...rest, +}) + +const getTagProperties = ( + props: Pick +): Pick => props + +const getSharedSecurityProperties = ( + props: Pick< + StakingCategoryType, + "isFoss" | "audits" | "hasBugBounty" | "launchDate" + > +): Pick => ({ + openSource: getFlagFromBoolean(props.isFoss), + audited: getFlagFromBoolean(!!props.audits?.length), + bugBounty: getFlagFromBoolean(props.hasBugBounty), + battleTested: getBattleTestedFlag(props.launchDate), +}) + +export { + getRankingScore, + getDiversityOfClients, + getFlagFromBoolean, + getBrandProperties, + getTagProperties, + getSharedSecurityProperties, +} From 56d454f54d47d127bbe3459c439778186472742f Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Sun, 4 Feb 2024 22:59:51 -0500 Subject: [PATCH 3/6] chore(StakingProductsCardGrid): update imports --- .../StakingProductsCardGrid/StakingProductCard.tsx | 4 +++- .../Staking/StakingProductsCardGrid/index.tsx | 3 ++- .../Staking/StakingProductsCardGrid/types.ts | 5 +++-- .../useStakingProductsCardGrid.ts | 11 ++++++----- .../Staking/StakingProductsCardGrid/utils.ts | 8 ++++---- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx index ef7620a6183..ed0137a0afd 100644 --- a/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx +++ b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx @@ -12,14 +12,16 @@ import { ListIcon, ListItem, } from "@chakra-ui/react" + import { ButtonLink } from "../../Buttons" -import Translation from "../../Translation" import { CautionProductGlyphIcon, GreenCheckProductGlyphIcon, UnknownProductGlyphIcon, WarningProductGlyphIcon, } from "../../icons/staking" +import Translation from "../../Translation" + import { FlagType, Product } from "./types" const getIconFromName = (imageName: string): typeof ChakraIcon | undefined => { diff --git a/src/components/Staking/StakingProductsCardGrid/index.tsx b/src/components/Staking/StakingProductsCardGrid/index.tsx index 28d66354517..94576e72afb 100644 --- a/src/components/Staking/StakingProductsCardGrid/index.tsx +++ b/src/components/Staking/StakingProductsCardGrid/index.tsx @@ -1,8 +1,9 @@ import React from "react" import { SimpleGrid } from "@chakra-ui/react" + import { StakingProductCard } from "./StakingProductCard" -import { useStakingProductsCardGrid } from "./useStakingProductsCardGrid" import { StakingProductsCategoryKeys } from "./types" +import { useStakingProductsCardGrid } from "./useStakingProductsCardGrid" export interface IProps { category: StakingProductsCategoryKeys diff --git a/src/components/Staking/StakingProductsCardGrid/types.ts b/src/components/Staking/StakingProductsCardGrid/types.ts index 9d822f2ad34..130d5c9e5e0 100644 --- a/src/components/Staking/StakingProductsCardGrid/types.ts +++ b/src/components/Staking/StakingProductsCardGrid/types.ts @@ -1,5 +1,6 @@ -import { type MatomoEventOptions } from "../../../utils/matomo" -import type stakingProducts from "../../../data/staking-products.json" +import type { MatomoEventOptions } from "@/lib/utils/matomo" + +import type stakingProducts from "@/data/staking-products.json" export enum FlagType { VALID = "green-check", diff --git a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts index b3ff475b677..e38a18f28b4 100644 --- a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts +++ b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts @@ -1,6 +1,9 @@ import { useEffect, useState } from "react" import { shuffle } from "lodash" import { useColorModeValue } from "@chakra-ui/react" + +import stakingProducts from "@/data/staking-products.json" + import { KeyGenType, NodeToolsType, @@ -9,15 +12,13 @@ import { SaasType, StakingProductsCategoryKeys, } from "./types" -import stakingProducts from "../../../data/staking-products.json" - import { getBrandProperties, - getTagProperties, - getSharedSecurityProperties, - getFlagFromBoolean, getDiversityOfClients, + getFlagFromBoolean, getRankingScore, + getSharedSecurityProperties, + getTagProperties, } from "./utils" export const useStakingProductsCardGrid = ({ diff --git a/src/components/Staking/StakingProductsCardGrid/utils.ts b/src/components/Staking/StakingProductsCardGrid/utils.ts index 2c8316671cf..7a2fbe4a27d 100644 --- a/src/components/Staking/StakingProductsCardGrid/utils.ts +++ b/src/components/Staking/StakingProductsCardGrid/utils.ts @@ -1,4 +1,4 @@ -import { Product, FlagType, StakingCategoryType } from "./types" +import { FlagType, Product, StakingCategoryType } from "./types" const scoreOpenSource = (product: Product): 1 | 0 => { return product.openSource === FlagType.VALID ? 1 : 0 @@ -129,10 +129,10 @@ const getSharedSecurityProperties = ( }) export { - getRankingScore, + getBrandProperties, getDiversityOfClients, getFlagFromBoolean, - getBrandProperties, - getTagProperties, + getRankingScore, getSharedSecurityProperties, + getTagProperties, } From 2a5dbe82e26bcd181d030d31d5ae85073ef82e61 Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Sun, 4 Feb 2024 23:00:26 -0500 Subject: [PATCH 4/6] chore(useStakingProductsCardGrid): update useEffect deps --- .../StakingProductsCardGrid/useStakingProductsCardGrid.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts index e38a18f28b4..f9344e62f7d 100644 --- a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts +++ b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts @@ -121,7 +121,7 @@ export const useStakingProductsCardGrid = ({ .sort((a, b) => b.rankingScore - a.rankingScore) ) } - }, []) + }, [LUM, SAT, category]) return { rankedProducts, From cc167817887a8ba35bed803c1d4ec695ec629b3a Mon Sep 17 00:00:00 2001 From: tylerapfledderer Date: Mon, 5 Feb 2024 00:10:59 -0500 Subject: [PATCH 5/6] refactor(StakingProductsCardGrid): update code to match prod --- .../StakingProductCard.tsx | 160 +++++++++--------- .../Staking/StakingProductsCardGrid/index.tsx | 10 +- .../Staking/StakingProductsCardGrid/types.ts | 2 +- .../useStakingProductsCardGrid.ts | 26 ++- 4 files changed, 96 insertions(+), 102 deletions(-) diff --git a/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx index ed0137a0afd..8e976f73b10 100644 --- a/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx +++ b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx @@ -1,4 +1,5 @@ -import * as React from "react" +import { useTranslation } from "next-i18next" +import type { ComponentType, ReactNode, SVGProps } from "react" import { Badge, Box, @@ -7,52 +8,55 @@ import { Flex, Heading, HStack, - Icon as ChakraIcon, + Icon, List, ListIcon, ListItem, } from "@chakra-ui/react" -import { ButtonLink } from "../../Buttons" +import { ButtonLink } from "@/components/Buttons" import { CautionProductGlyphIcon, GreenCheckProductGlyphIcon, UnknownProductGlyphIcon, WarningProductGlyphIcon, -} from "../../icons/staking" -import Translation from "../../Translation" +} from "@/components/icons/staking" import { FlagType, Product } from "./types" -const getIconFromName = (imageName: string): typeof ChakraIcon | undefined => { - const { [imageName + "GlyphIcon"]: Icon } = require("../icons/staking") +const getIconFromName = ( + imageName: string +): ComponentType> => { + const { + [imageName + "GlyphIcon"]: Icon, + } = require("@/components/icons/staking") return Icon } -export const Status = ({ status }: { status: FlagType }) => { +const Status = ({ status }: { status: FlagType | undefined }) => { if (!status) return null - const getStatusIcon = () => { - switch (status) { - case "green-check": - return GreenCheckProductGlyphIcon - case "caution": - return CautionProductGlyphIcon - case "warning": - case "false": - return WarningProductGlyphIcon - default: - return UnknownProductGlyphIcon - } + const styles = { fontSize: "2xl", m: 0 } + switch (status) { + case "green-check": + return + case "caution": + return + case "warning": + case "false": + return + default: + return } - - return } -const StakingBadge: React.FC<{ +const StakingBadge = ({ + type, + children, +}: { type: "ui" | "platform" - children: React.ReactNode -}> = ({ type, children }) => { + children: ReactNode +}) => { const uiTypeColor = type === "ui" && "stakingPillUI" const platformTypeColor = type === "platform" && "stakingPillPlatform" @@ -67,11 +71,11 @@ const StakingBadge: React.FC<{ ) } -interface ICardProps { +type StakingProductCardProps = { product: Product } -export const StakingProductCard: React.FC = ({ +export const StakingProductCard = ({ product: { name, imageName, @@ -95,72 +99,69 @@ export const StakingProductCard: React.FC = ({ economical, matomo, }, -}) => { +}: StakingProductCardProps) => { const PADDED_DIV_STYLE: BoxProps = { px: 8, py: 6, } + const { t } = useTranslation("page-staking") const Svg = getIconFromName(imageName) - type DataType = { label: JSX.Element; status?: FlagType } + type DataType = { label: string; status?: FlagType } const data: DataType[] = [ { - label: , + label: t("page-staking-considerations-solo-1-title"), status: openSource, }, { - label: , + label: t("page-staking-considerations-solo-2-title"), status: audited, }, { - label: , + label: t("page-staking-considerations-solo-3-title"), status: bugBounty, }, { - label: , + label: t("page-staking-considerations-solo-4-title"), status: battleTested, }, { - label: , + label: t("page-staking-considerations-solo-5-title"), status: trustless, }, { - label: , + label: t("page-staking-considerations-solo-6-title"), status: permissionless, }, { - label: , + label: t("page-staking-considerations-pools-6-title"), status: permissionlessNodes, }, { - label: , + label: t("page-staking-considerations-solo-7-title"), status: multiClient, }, { - label: , + label: t("page-staking-considerations-saas-7-title"), status: executionDiversity, }, { - label: , + label: t("page-staking-considerations-saas-8-title"), status: consensusDiversity, }, { - label: , + label: t("page-staking-considerations-solo-8-title"), status: selfCustody, }, { - label: , + label: t("page-staking-considerations-pools-8-title"), status: liquidityToken, }, { - label: , + label: t("page-staking-considerations-solo-9-title"), status: economical, }, - ] - - const filteredData = data.filter( - (item): item is Required => !!item.status - ) + ].filter(({ status }) => !!status) return ( = ({ borderRadius="base" maxH={24} > - {!!Svg && } + {!!Svg && } {name} @@ -203,45 +204,42 @@ export const StakingProductCard: React.FC = ({ flex={1} alignItems="flex-start" > - {platforms && - platforms.map((platform, idx) => ( - - {platform} - - ))} - {ui && - ui.map((_ui, idx) => ( - - {_ui} - - ))} + {platforms.map((platform, idx) => ( + + {platform} + + ))} + {ui.map((_ui, idx) => ( + + {_ui} + + ))} - {filteredData && - filteredData.map(({ label, status }, idx) => ( - - - {label} - - ))} + {data.map(({ label, status }, idx) => ( + + + {label} + + ))} - - + + {t("page-staking-products-get-started")} diff --git a/src/components/Staking/StakingProductsCardGrid/index.tsx b/src/components/Staking/StakingProductsCardGrid/index.tsx index 94576e72afb..70cafb7131a 100644 --- a/src/components/Staking/StakingProductsCardGrid/index.tsx +++ b/src/components/Staking/StakingProductsCardGrid/index.tsx @@ -5,15 +5,15 @@ import { StakingProductCard } from "./StakingProductCard" import { StakingProductsCategoryKeys } from "./types" import { useStakingProductsCardGrid } from "./useStakingProductsCardGrid" -export interface IProps { +export type StakingProductsCardGridProps = { category: StakingProductsCategoryKeys } -const StakingProductCardGrid: React.FC = ({ category }) => { +const StakingProductsCardGrid = ({ + category, +}: StakingProductsCardGridProps) => { const { rankedProducts } = useStakingProductsCardGrid({ category }) - if (!rankedProducts) return null - return ( = ({ category }) => { ) } -export default StakingProductCardGrid +export default StakingProductsCardGrid diff --git a/src/components/Staking/StakingProductsCardGrid/types.ts b/src/components/Staking/StakingProductsCardGrid/types.ts index 130d5c9e5e0..bb509030cca 100644 --- a/src/components/Staking/StakingProductsCardGrid/types.ts +++ b/src/components/Staking/StakingProductsCardGrid/types.ts @@ -34,7 +34,7 @@ export type Product = { matomo: MatomoEventOptions } -type StakingProductsType = typeof stakingProducts +export type StakingProductsType = typeof stakingProducts export type NodeToolsType = StakingProductsType["nodeTools"] export type KeyGenType = StakingProductsType["keyGen"] diff --git a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts index f9344e62f7d..5b26115b42d 100644 --- a/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts +++ b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts @@ -33,10 +33,7 @@ export const useStakingProductsCardGrid = ({ const categoryProducts = stakingProducts[category] const products: Array = [] - function mapCatProducts( - products: T[], - cb: (listing: T) => Product - ) { + function mapCatProducts(products: T[], cb: (listing: T) => Product) { return products.map((item) => cb(item)) } @@ -111,17 +108,16 @@ export const useStakingProductsCardGrid = ({ ) } - if (products) { - updateRankedProducts( - shuffle(products) - .map((product) => ({ - ...product, - rankingScore: getRankingScore(product), - })) - .sort((a, b) => b.rankingScore - a.rankingScore) - ) - } - }, [LUM, SAT, category]) + updateRankedProducts( + shuffle(products) + .map((product) => ({ + ...product, + rankingScore: getRankingScore(product), + })) + .sort((a, b) => b.rankingScore - a.rankingScore) + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) return { rankedProducts, From 32ef36b4768594b8b5bfd2b13fe216636f349c0f Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:42:46 -0700 Subject: [PATCH 6/6] chore: rm react import --- src/components/Staking/StakingProductsCardGrid/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Staking/StakingProductsCardGrid/index.tsx b/src/components/Staking/StakingProductsCardGrid/index.tsx index 70cafb7131a..6031c088e96 100644 --- a/src/components/Staking/StakingProductsCardGrid/index.tsx +++ b/src/components/Staking/StakingProductsCardGrid/index.tsx @@ -1,4 +1,3 @@ -import React from "react" import { SimpleGrid } from "@chakra-ui/react" import { StakingProductCard } from "./StakingProductCard"