diff --git a/src/components/Staking/StakingProductsCardGrid.tsx b/src/components/Staking/StakingProductsCardGrid.tsx deleted file mode 100644 index 762bd803c72..00000000000 --- a/src/components/Staking/StakingProductsCardGrid.tsx +++ /dev/null @@ -1,522 +0,0 @@ -import { ComponentType, SVGProps, useEffect, useState } from "react" -import shuffle from "lodash/shuffle" -import { useTranslation } from "next-i18next" -import { - Badge, - Box, - BoxProps, - Center, - Flex, - Heading, - HStack, - Icon, - List, - ListIcon, - ListItem, - SimpleGrid, - useColorModeValue, -} from "@chakra-ui/react" - -import { ButtonLink } from "@/components/Buttons" -// SVG imports -import { - CautionProductGlyphIcon, - GreenCheckProductGlyphIcon, - UnknownProductGlyphIcon, - WarningProductGlyphIcon, -} from "@/components/icons/staking" - -import { MatomoEventOptions } from "@/lib/utils/matomo" - -import stakingProducts from "@/data/staking-products.json" - -const PADDED_DIV_STYLE: BoxProps = { - px: 8, - py: 6, -} - -// TODO: Remove enum, replace with "as const" object -enum FlagType { - VALID = "green-check", - CAUTION = "caution", - WARNING = "warning", - FALSE = "false", - UNKNOWN = "unknown", -} - -const getIconFromName = ( - imageName: string -): ComponentType> => { - const { - [imageName + "GlyphIcon"]: Icon, - } = require("@/components/icons/staking") - return Icon -} - -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 StakingBadge = ({ - type, - children, -}: { - type: "ui" | "platform" - children: React.ReactNode -}) => { - 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 -} -type StakingProductCardProps = { - product: Product -} - -const StakingProductCard = ({ - product: { - name, - imageName, - color, - url, - platforms, - ui, - minEth, - openSource, - audited, - bugBounty, - battleTested, - trustless, - selfCustody, - liquidityToken, - permissionless, - permissionlessNodes, - multiClient, - consensusDiversity, - executionDiversity, - economical, - matomo, - }, -}: StakingProductCardProps) => { - const { t } = useTranslation("page-staking") - const Svg = getIconFromName(imageName) - const data = [ - { - label: t("page-staking-considerations-solo-1-title"), - status: openSource, - }, - { - label: t("page-staking-considerations-solo-2-title"), - status: audited, - }, - { - label: t("page-staking-considerations-solo-3-title"), - status: bugBounty, - }, - { - label: t("page-staking-considerations-solo-4-title"), - status: battleTested, - }, - { - label: t("page-staking-considerations-solo-5-title"), - status: trustless, - }, - { - label: t("page-staking-considerations-solo-6-title"), - status: permissionless, - }, - { - label: t("page-staking-considerations-pools-6-title"), - status: permissionlessNodes, - }, - { - label: t("page-staking-considerations-solo-7-title"), - status: multiClient, - }, - { - label: t("page-staking-considerations-saas-7-title"), - status: executionDiversity, - }, - { - label: t("page-staking-considerations-saas-8-title"), - status: consensusDiversity, - }, - { - label: t("page-staking-considerations-solo-8-title"), - status: selfCustody, - }, - { - label: t("page-staking-considerations-pools-8-title"), - status: liquidityToken, - }, - { - label: t("page-staking-considerations-solo-9-title"), - status: economical, - }, - ].filter(({ status }) => !!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} - - ))} - - - - {data && - data.map(({ label, status }, idx) => ( - - - {label} - - ))} - - - - - {t("page-staking-products-get-started")} - - -
- ) -} - -export type StakingProductCardGridProps = { - category: string -} - -const StakingProductCardGrid = ({ category }: StakingProductCardGridProps) => { - 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): 2 | 1 | 0 => { - return flag === FlagType.VALID ? 2 : flag === FlagType.WARNING ? 1 : 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 => { - let battleTested = FlagType.WARNING - 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 - } - if (oneYearAgo > launchDate) { - battleTested = FlagType.VALID - } - return battleTested - } - - const getDiversityOfClients = ( - _pctMajorityClient: number | null - ): FlagType => { - 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 getBrandProperties = ({ - name, - imageName, - hue, - url, - socials, - matomo, - }) => ({ - name, - imageName, - color: `hsla(${hue}, ${SAT}, ${LUM}, 1)`, - url, - socials, - matomo, - }) - - const getTagProperties = ({ platforms, ui }) => ({ - platforms, - ui, - }) - - const getSharedSecurityProperties = ({ - isFoss, - audits, - hasBugBounty, - launchDate, - }) => ({ - openSource: getFlagFromBoolean(isFoss), - audited: getFlagFromBoolean(audits?.length), - bugBounty: getFlagFromBoolean(hasBugBounty), - battleTested: getBattleTestedFlag(launchDate), - }) - - useEffect(() => { - const categoryProducts = stakingProducts[category] - const products: Array = [] - - // Pooled staking services - if (category === "pools") { - products.push( - ...categoryProducts.map((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( - ...categoryProducts.map((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( - ...categoryProducts.map((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( - ...categoryProducts.map((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) - ) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - 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..8e976f73b10 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/StakingProductCard.tsx @@ -0,0 +1,247 @@ +import { useTranslation } from "next-i18next" +import type { ComponentType, ReactNode, SVGProps } from "react" +import { + Badge, + Box, + BoxProps, + Center, + Flex, + Heading, + HStack, + Icon, + List, + ListIcon, + ListItem, +} from "@chakra-ui/react" + +import { ButtonLink } from "@/components/Buttons" +import { + CautionProductGlyphIcon, + GreenCheckProductGlyphIcon, + UnknownProductGlyphIcon, + WarningProductGlyphIcon, +} from "@/components/icons/staking" + +import { FlagType, Product } from "./types" + +const getIconFromName = ( + imageName: string +): ComponentType> => { + const { + [imageName + "GlyphIcon"]: Icon, + } = require("@/components/icons/staking") + return Icon +} + +const Status = ({ status }: { status: FlagType | undefined }) => { + 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 StakingBadge = ({ + type, + children, +}: { + type: "ui" | "platform" + children: ReactNode +}) => { + const uiTypeColor = type === "ui" && "stakingPillUI" + const platformTypeColor = type === "platform" && "stakingPillPlatform" + + return ( + + {children} + + ) +} + +type StakingProductCardProps = { + product: Product +} + +export const StakingProductCard = ({ + product: { + name, + imageName, + color, + url, + platforms, + ui, + minEth, + openSource, + audited, + bugBounty, + battleTested, + trustless, + selfCustody, + liquidityToken, + permissionless, + permissionlessNodes, + multiClient, + consensusDiversity, + executionDiversity, + economical, + matomo, + }, +}: StakingProductCardProps) => { + const PADDED_DIV_STYLE: BoxProps = { + px: 8, + py: 6, + } + + const { t } = useTranslation("page-staking") + const Svg = getIconFromName(imageName) + type DataType = { label: string; status?: FlagType } + const data: DataType[] = [ + { + label: t("page-staking-considerations-solo-1-title"), + status: openSource, + }, + { + label: t("page-staking-considerations-solo-2-title"), + status: audited, + }, + { + label: t("page-staking-considerations-solo-3-title"), + status: bugBounty, + }, + { + label: t("page-staking-considerations-solo-4-title"), + status: battleTested, + }, + { + label: t("page-staking-considerations-solo-5-title"), + status: trustless, + }, + { + label: t("page-staking-considerations-solo-6-title"), + status: permissionless, + }, + { + label: t("page-staking-considerations-pools-6-title"), + status: permissionlessNodes, + }, + { + label: t("page-staking-considerations-solo-7-title"), + status: multiClient, + }, + { + label: t("page-staking-considerations-saas-7-title"), + status: executionDiversity, + }, + { + label: t("page-staking-considerations-saas-8-title"), + status: consensusDiversity, + }, + { + label: t("page-staking-considerations-solo-8-title"), + status: selfCustody, + }, + { + label: t("page-staking-considerations-pools-8-title"), + status: liquidityToken, + }, + { + label: t("page-staking-considerations-solo-9-title"), + status: economical, + }, + ].filter(({ status }) => !!status) + + return ( + + + {!!Svg && } + + {name} + + + {typeof minEth !== "undefined" && ( +
+ {minEth > 0 ? `From ${minEth} ETH` : "Any amount"} +
+ )} + + {platforms.map((platform, idx) => ( + + {platform} + + ))} + {ui.map((_ui, idx) => ( + + {_ui} + + ))} + + + + {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 new file mode 100644 index 00000000000..6031c088e96 --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/index.tsx @@ -0,0 +1,30 @@ +import { SimpleGrid } from "@chakra-ui/react" + +import { StakingProductCard } from "./StakingProductCard" +import { StakingProductsCategoryKeys } from "./types" +import { useStakingProductsCardGrid } from "./useStakingProductsCardGrid" + +export type StakingProductsCardGridProps = { + category: StakingProductsCategoryKeys +} + +const StakingProductsCardGrid = ({ + category, +}: StakingProductsCardGridProps) => { + const { rankedProducts } = useStakingProductsCardGrid({ category }) + + return ( + + {rankedProducts.map((product) => ( + + ))} + + ) +} + +export default StakingProductsCardGrid diff --git a/src/components/Staking/StakingProductsCardGrid/types.ts b/src/components/Staking/StakingProductsCardGrid/types.ts new file mode 100644 index 00000000000..bb509030cca --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/types.ts @@ -0,0 +1,47 @@ +import type { MatomoEventOptions } from "@/lib/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 +} + +export 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..5b26115b42d --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/useStakingProductsCardGrid.ts @@ -0,0 +1,125 @@ +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, + PoolsType, + Product, + SaasType, + StakingProductsCategoryKeys, +} from "./types" +import { + getBrandProperties, + getDiversityOfClients, + getFlagFromBoolean, + getRankingScore, + getSharedSecurityProperties, + getTagProperties, +} 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), + })) + ) + } + + 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, + } +} diff --git a/src/components/Staking/StakingProductsCardGrid/utils.ts b/src/components/Staking/StakingProductsCardGrid/utils.ts new file mode 100644 index 00000000000..7a2fbe4a27d --- /dev/null +++ b/src/components/Staking/StakingProductsCardGrid/utils.ts @@ -0,0 +1,138 @@ +import { FlagType, Product, 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 { + getBrandProperties, + getDiversityOfClients, + getFlagFromBoolean, + getRankingScore, + getSharedSecurityProperties, + getTagProperties, +}