1
- import React , { ComponentType , SVGProps , useEffect , useState } from "react"
1
+ import React , { useEffect , useState } from "react"
2
2
import { shuffle } from "lodash"
3
3
import {
4
4
Badge ,
8
8
Flex ,
9
9
Heading ,
10
10
HStack ,
11
- Icon ,
11
+ Icon as ChakraIcon ,
12
12
List ,
13
13
ListIcon ,
14
14
ListItem ,
@@ -44,28 +44,29 @@ enum FlagType {
44
44
UNKNOWN = "unknown" ,
45
45
}
46
46
47
- const getIconFromName = (
48
- imageName : string
49
- ) : ComponentType < SVGProps < SVGElement > > => {
47
+ const getIconFromName = ( imageName : string ) : typeof ChakraIcon | undefined => {
50
48
const { [ imageName + "GlyphIcon" ] : Icon } = require ( "../icons/staking" )
51
49
return Icon
52
50
}
53
51
54
- const Status : React . FC < { status : FlagType } > = ( { status } ) => {
52
+ const Status = ( { status } : { status : FlagType } ) => {
55
53
if ( ! status ) return null
56
54
57
- const styles = { fontSize : "2xl" , m : 0 }
58
- switch ( status ) {
59
- case "green-check" :
60
- return < ListIcon as = { GreenCheckProductGlyphIcon } { ...styles } />
61
- case "caution" :
62
- return < ListIcon as = { CautionProductGlyphIcon } { ...styles } />
63
- case "warning" :
64
- case "false" :
65
- return < ListIcon as = { WarningProductGlyphIcon } { ...styles } />
66
- default :
67
- return < ListIcon as = { UnknownProductGlyphIcon } { ...styles } />
55
+ const getStatusIcon = ( ) => {
56
+ switch ( status ) {
57
+ case "green-check" :
58
+ return GreenCheckProductGlyphIcon
59
+ case "caution" :
60
+ return CautionProductGlyphIcon
61
+ case "warning" :
62
+ case "false" :
63
+ return WarningProductGlyphIcon
64
+ default :
65
+ return UnknownProductGlyphIcon
66
+ }
68
67
}
68
+
69
+ return < ListIcon as = { getStatusIcon ( ) } fontSize = "2xl" m = { 0 } />
69
70
}
70
71
71
72
const StakingBadge : React . FC < {
@@ -93,20 +94,20 @@ type Product = {
93
94
url : string
94
95
platforms : Array < string >
95
96
ui : Array < string >
96
- minEth : number
97
+ minEth ? : number
97
98
openSource : FlagType
98
99
audited : FlagType
99
100
bugBounty : FlagType
100
101
battleTested : FlagType
101
- trustless : FlagType
102
- selfCustody : FlagType
103
- liquidityToken : FlagType
104
- permissionless : FlagType
105
- permissionlessNodes : FlagType
106
- multiClient : FlagType
107
- consensusDiversity : FlagType
108
- executionDiversity : FlagType
109
- economical : FlagType
102
+ trustless ? : FlagType
103
+ selfCustody ? : FlagType
104
+ liquidityToken ? : FlagType
105
+ permissionless ? : FlagType
106
+ permissionlessNodes ? : FlagType
107
+ multiClient ? : FlagType
108
+ consensusDiversity ? : FlagType
109
+ executionDiversity ? : FlagType
110
+ economical ? : FlagType
110
111
matomo : MatomoEventOptions
111
112
}
112
113
interface ICardProps {
@@ -139,7 +140,8 @@ const StakingProductCard: React.FC<ICardProps> = ({
139
140
} ,
140
141
} ) => {
141
142
const Svg = getIconFromName ( imageName )
142
- const data = [
143
+ type DataType = { label : JSX . Element ; status ?: FlagType }
144
+ const data : DataType [ ] = [
143
145
{
144
146
label : < Translation id = "page-staking-considerations-solo-1-title" /> ,
145
147
status : openSource ,
@@ -192,7 +194,11 @@ const StakingProductCard: React.FC<ICardProps> = ({
192
194
label : < Translation id = "page-staking-considerations-solo-9-title" /> ,
193
195
status : economical ,
194
196
} ,
195
- ] . filter ( ( { status } ) => ! ! status )
197
+ ]
198
+
199
+ const filteredData = data . filter (
200
+ ( item ) : item is Required < DataType > => ! ! item . status
201
+ )
196
202
197
203
return (
198
204
< Flex
@@ -212,7 +218,7 @@ const StakingProductCard: React.FC<ICardProps> = ({
212
218
borderRadius = "base"
213
219
maxH = { 24 }
214
220
>
215
- { ! ! Svg && < Icon as = { Svg } fontSize = "2rem" color = "white" /> }
221
+ { ! ! Svg && < Svg fontSize = "2rem" color = "white" /> }
216
222
< Heading as = "h4" fontSize = "2xl" color = "white" >
217
223
{ name }
218
224
</ Heading >
@@ -250,8 +256,8 @@ const StakingProductCard: React.FC<ICardProps> = ({
250
256
</ Flex >
251
257
< Box { ...PADDED_DIV_STYLE } py = { 0 } >
252
258
< List m = { 0 } gap = { 3 } >
253
- { data &&
254
- data . map ( ( { label, status } , idx ) => (
259
+ { filteredData &&
260
+ filteredData . map ( ( { label, status } , idx ) => (
255
261
< ListItem
256
262
as = { Flex }
257
263
key = { idx }
@@ -280,8 +286,20 @@ const StakingProductCard: React.FC<ICardProps> = ({
280
286
)
281
287
}
282
288
289
+ type StakingProductsType = typeof stakingProducts
290
+
291
+ type NodeToolsType = StakingProductsType [ "nodeTools" ]
292
+ type KeyGenType = StakingProductsType [ "keyGen" ]
293
+ type PoolsType = StakingProductsType [ "pools" ]
294
+ type SaasType = StakingProductsType [ "saas" ]
295
+
296
+ type StakingProductsCategoryKeys = keyof StakingProductsType
297
+
298
+ type StakingCategoryType =
299
+ StakingProductsType [ StakingProductsCategoryKeys ] [ number ]
300
+
283
301
export interface IProps {
284
- category : string
302
+ category : StakingProductsCategoryKeys
285
303
}
286
304
287
305
const StakingProductCardGrid : React . FC < IProps > = ( { category } ) => {
@@ -324,8 +342,12 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
324
342
return product . multiClient === FlagType . VALID ? 1 : 0
325
343
}
326
344
327
- const scoreClientDiversity = ( flag : FlagType ) : 2 | 1 | 0 => {
328
- return flag === FlagType . VALID ? 2 : flag === FlagType . WARNING ? 1 : 0
345
+ const scoreClientDiversity = ( flag : FlagType | undefined ) : 2 | 1 | 0 => {
346
+ if ( flag === FlagType . VALID ) return 2
347
+
348
+ if ( flag === FlagType . WARNING ) return 1
349
+
350
+ return 0
329
351
}
330
352
331
353
const scoreEconomical = ( product : Product ) : 1 | 0 => {
@@ -348,75 +370,83 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
348
370
return score
349
371
}
350
372
351
- const getBattleTestedFlag = ( _launchDate : string ) : FlagType => {
352
- let battleTested = FlagType . WARNING
373
+ const getBattleTestedFlag = (
374
+ _launchDate : string
375
+ ) : FlagType . CAUTION | FlagType . WARNING | FlagType . VALID => {
353
376
const launchDate = new Date ( _launchDate )
354
377
const now = new Date ( )
355
378
const halfYearAgo = new Date ( )
356
379
const oneYearAgo = new Date ( )
357
380
halfYearAgo . setDate ( now . getDate ( ) - 183 )
358
381
oneYearAgo . setDate ( now . getDate ( ) - 365 )
382
+
359
383
if ( halfYearAgo > launchDate ) {
360
- battleTested = FlagType . CAUTION
384
+ return FlagType . CAUTION
361
385
}
386
+
362
387
if ( oneYearAgo > launchDate ) {
363
- battleTested = FlagType . VALID
388
+ return FlagType . VALID
364
389
}
365
- return battleTested
390
+
391
+ return FlagType . WARNING
366
392
}
367
393
368
394
const getDiversityOfClients = (
369
395
_pctMajorityClient : number | null
370
- ) : FlagType => {
396
+ ) : FlagType . VALID | FlagType . UNKNOWN | FlagType . WARNING => {
371
397
if ( _pctMajorityClient === null ) return FlagType . UNKNOWN
372
398
if ( _pctMajorityClient > 50 ) return FlagType . WARNING
373
399
return FlagType . VALID
374
400
}
375
401
376
- const getFlagFromBoolean = ( bool : boolean ) : FlagType =>
377
- ! ! bool ? FlagType . VALID : FlagType . FALSE
402
+ const getFlagFromBoolean = ( bool : boolean ) =>
403
+ bool ? FlagType . VALID : FlagType . FALSE
378
404
379
405
const getBrandProperties = ( {
380
- name,
381
- imageName,
382
406
hue,
383
- url,
384
- socials,
385
- matomo,
386
- } ) => ( {
387
- name,
388
- imageName,
407
+ ...rest
408
+ } : Pick <
409
+ StakingCategoryType ,
410
+ "name" | "imageName" | "hue" | "url" | "matomo"
411
+ > ) : Pick < Product , "name" | "imageName" | "color" | "url" | "matomo" > => ( {
389
412
color : `hsla(${ hue } , ${ SAT } , ${ LUM } , 1)` ,
390
- url,
391
- socials,
392
- matomo,
413
+ ...rest ,
393
414
} )
394
415
395
- const getTagProperties = ( { platforms, ui } ) => ( {
396
- platforms,
397
- ui,
398
- } )
416
+ const getTagProperties = (
417
+ props : Pick < StakingCategoryType , "platforms" | "ui" >
418
+ ) : Pick < Product , "platforms" | "ui" > => props
399
419
400
- const getSharedSecurityProperties = ( {
401
- isFoss,
402
- audits,
403
- hasBugBounty,
404
- launchDate,
405
- } ) => ( {
406
- openSource : getFlagFromBoolean ( isFoss ) ,
407
- audited : getFlagFromBoolean ( audits ?. length ) ,
408
- bugBounty : getFlagFromBoolean ( hasBugBounty ) ,
409
- battleTested : getBattleTestedFlag ( launchDate ) ,
420
+ const getSharedSecurityProperties = (
421
+ props : Pick <
422
+ StakingCategoryType ,
423
+ "isFoss" | "audits" | "hasBugBounty" | "launchDate"
424
+ >
425
+ ) : Pick <
426
+ Product ,
427
+ "openSource" | "audited" | "bugBounty" | "battleTested"
428
+ > => ( {
429
+ openSource : getFlagFromBoolean ( props . isFoss ) ,
430
+ audited : getFlagFromBoolean ( ! ! props . audits ?. length ) ,
431
+ bugBounty : getFlagFromBoolean ( props . hasBugBounty ) ,
432
+ battleTested : getBattleTestedFlag ( props . launchDate ) ,
410
433
} )
411
434
412
435
useEffect ( ( ) => {
413
436
const categoryProducts = stakingProducts [ category ]
414
437
const products : Array < Product > = [ ]
415
438
439
+ function mapCatProducts < T extends unknown > (
440
+ products : T [ ] ,
441
+ cb : ( listing : T ) => Product
442
+ ) {
443
+ return products . map ( ( item ) => cb ( item ) )
444
+ }
445
+
416
446
// Pooled staking services
417
447
if ( category === "pools" ) {
418
448
products . push (
419
- ...categoryProducts . map ( ( listing ) => ( {
449
+ ...mapCatProducts ( categoryProducts as PoolsType , ( listing ) => ( {
420
450
...getBrandProperties ( listing ) ,
421
451
...getTagProperties ( listing ) ,
422
452
...getSharedSecurityProperties ( listing ) ,
@@ -430,7 +460,7 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
430
460
consensusDiversity : getDiversityOfClients (
431
461
listing . pctMajorityConsensusClient
432
462
) ,
433
- liquidityToken : getFlagFromBoolean ( listing . tokens ?. length ) ,
463
+ liquidityToken : getFlagFromBoolean ( ! ! listing . tokens ?. length ) ,
434
464
minEth : listing . minEth ,
435
465
} ) )
436
466
)
@@ -439,7 +469,7 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
439
469
// Solo staking products
440
470
if ( category === "nodeTools" ) {
441
471
products . push (
442
- ...categoryProducts . map ( ( listing ) => ( {
472
+ ...mapCatProducts ( categoryProducts as NodeToolsType , ( listing ) => ( {
443
473
...getBrandProperties ( listing ) ,
444
474
...getTagProperties ( listing ) ,
445
475
...getSharedSecurityProperties ( listing ) ,
@@ -455,7 +485,7 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
455
485
// Staking as a service
456
486
if ( category === "saas" ) {
457
487
products . push (
458
- ...categoryProducts . map ( ( listing ) => ( {
488
+ ...mapCatProducts ( categoryProducts as SaasType , ( listing ) => ( {
459
489
...getBrandProperties ( listing ) ,
460
490
...getTagProperties ( listing ) ,
461
491
...getSharedSecurityProperties ( listing ) ,
@@ -474,7 +504,7 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
474
504
// Key generators
475
505
if ( category === "keyGen" ) {
476
506
products . push (
477
- ...categoryProducts . map ( ( listing ) => ( {
507
+ ...mapCatProducts ( categoryProducts as KeyGenType , ( listing ) => ( {
478
508
...getBrandProperties ( listing ) ,
479
509
...getTagProperties ( listing ) ,
480
510
...getSharedSecurityProperties ( listing ) ,
@@ -494,7 +524,6 @@ const StakingProductCardGrid: React.FC<IProps> = ({ category }) => {
494
524
. sort ( ( a , b ) => b . rankingScore - a . rankingScore )
495
525
)
496
526
}
497
- // eslint-disable-next-line react-hooks/exhaustive-deps
498
527
} , [ ] )
499
528
500
529
if ( ! rankedProducts ) return null
0 commit comments