Skip to content

Commit

Permalink
feat(earn): add safety card to pool info screen (#6162)
Browse files Browse the repository at this point in the history
### Description

Part 1 of adding safety score section, showing details / bottom sheet
will be in a follow up

### Test plan

unit tests, manually by opening beefy pool



<img
src="https://github.com/user-attachments/assets/a9bd00c5-737d-42f8-94a0-7d3266ce8f84"
width="250" />


### Related issues

- Part of ACT-1405

### Backwards compatibility

Yes

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [x] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
satish-ravi authored Oct 17, 2024
1 parent 419f30d commit 8db264a
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 2 deletions.
4 changes: 3 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2699,6 +2699,7 @@
"ratePercent": "{{rate}}%",
"rewards": "Rewards",
"noRewards": "No rewards",
"safetyScore": "Safety Score",
"tvl": "TVL",
"ageOfPool": "Age of Pool",
"learnMoreOnProvider": "View Pool on {{providerName}}",
Expand All @@ -2721,7 +2722,8 @@
"dailyYieldRateTitle": "Daily Rate",
"dailyYieldRateDescription": "The daily rate displayed reflects the daily rate provided by {{providerName}}.",
"dailyYieldRateLink": "View More Daily Rate Details On {{providerName}}"
}
},
"viewMoreDetails": "View More Details"
},
"beforeDepositBottomSheet": {
"youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit",
Expand Down
21 changes: 21 additions & 0 deletions src/earn/EarnPoolInfoScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,27 @@ describe('EarnPoolInfoScreen', () => {
).toBeTruthy()
})

it('renders safety card when safety is provided', () => {
const mockPool = {
...mockEarnPositions[0],
balance: '100',
dataProps: {
...mockEarnPositions[0].dataProps,
safety: {
level: 'high' as const,
risks: [
{ isPositive: false, title: 'Risk 1', category: 'Category 1' },
{ isPositive: true, title: 'Risk 2', category: 'Category 2' },
],
},
},
}

const { getByTestId } = renderEarnPoolInfoScreen(mockPool)

expect(getByTestId('SafetyCard')).toBeTruthy()
})

it('navigates to external URI when "View Pool on Provider" is tapped', () => {
const { getByText } = renderEarnPoolInfoScreen(mockEarnPositions[0])

Expand Down
3 changes: 2 additions & 1 deletion src/earn/EarnPoolInfoScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import TokenIcon, { IconSize } from 'src/components/TokenIcon'
import Touchable from 'src/components/Touchable'
import BeforeDepositBottomSheet from 'src/earn/BeforeDepositBottomSheet'
import { useDepositEntrypointInfo } from 'src/earn/hooks'
import { SafetyCard } from 'src/earn/SafetyCard'
import OpenLinkIcon from 'src/icons/OpenLinkIcon'
import { useDollarsToLocalAmount } from 'src/localCurrency/hooks'
import { getLocalCurrencySymbol, usdToLocalCurrencyRateSelector } from 'src/localCurrency/selectors'
Expand Down Expand Up @@ -616,7 +617,7 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) {
}}
/>
)}

{!!dataProps.safety && <SafetyCard safety={dataProps.safety} />}
<TvlCard
earnPosition={pool}
onInfoIconPress={() => {
Expand Down
31 changes: 31 additions & 0 deletions src/earn/SafetyCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render } from '@testing-library/react-native'
import React from 'react'
import { SafetyCard } from 'src/earn/SafetyCard'
import Colors from 'src/styles/colors'

describe('SafetyCard', () => {
it('renders correctly', () => {
const { getByTestId, getAllByTestId } = render(
<SafetyCard safety={{ level: 'low', risks: [] }} />
)

expect(getByTestId('SafetyCard')).toBeDefined()
expect(getByTestId('SafetyCardInfoIcon')).toBeDefined()
expect(getAllByTestId('SafetyCard/Bar')).toHaveLength(3)
expect(getByTestId('SafetyCard/ViewDetails')).toBeDefined()
})

it.each([
{ level: 'low', colors: [Colors.primary, Colors.gray2, Colors.gray2] },
{ level: 'medium', colors: [Colors.primary, Colors.primary, Colors.gray2] },
{ level: 'high', colors: [Colors.primary, Colors.primary, Colors.primary] },
] as const)('should render correct triple bars for safety level $level', ({ level, colors }) => {
const { getAllByTestId } = render(<SafetyCard safety={{ level, risks: [] }} />)

const bars = getAllByTestId('SafetyCard/Bar')
expect(bars.length).toBe(3)
expect(bars[0]).toHaveStyle({ backgroundColor: colors[0] })
expect(bars[1]).toHaveStyle({ backgroundColor: colors[1] })
expect(bars[2]).toHaveStyle({ backgroundColor: colors[2] })
})
})
102 changes: 102 additions & 0 deletions src/earn/SafetyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { LabelWithInfo } from 'src/components/LabelWithInfo'
import Touchable from 'src/components/Touchable'
import { Safety } from 'src/positions/types'
import Colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'

const BAR_HEIGHTS = [8, 13, 18]

const LEVEL_TO_MAX_HIGHLIGHTED_BAR: Record<Safety['level'], 1 | 2 | 3> = {
low: 1,
medium: 2,
high: 3,
}

export function SafetyCard({ safety }: { safety: Safety }) {
const { t } = useTranslation()
return (
<View style={styles.card} testID="SafetyCard">
<View style={styles.cardLineContainer}>
<View style={styles.cardLineLabel}>
<LabelWithInfo
onPress={() => {
// todo(act-1405): open bottom sheet
}}
label={t('earnFlow.poolInfoScreen.safetyScore')}
labelStyle={styles.cardTitleText}
testID="SafetyCardInfoIcon"
/>
</View>
<View style={styles.tripleBarContainer}>
{BAR_HEIGHTS.map((height, index) => (
<View
testID="SafetyCard/Bar"
key={index}
style={[
styles.bar,
{ height },
index < LEVEL_TO_MAX_HIGHLIGHTED_BAR[safety.level] && styles.barHighlighted,
]}
/>
))}
</View>
</View>
<Touchable
testID="SafetyCard/ViewDetails"
style={styles.cardLineContainer}
onPress={() => {
// todo(act-1405): expand and display risks
}}
>
<Text style={styles.viewDetailsText}>{t('earnFlow.poolInfoScreen.viewMoreDetails')}</Text>
</Touchable>
</View>
)
}

const styles = StyleSheet.create({
card: {
padding: Spacing.Regular16,
borderColor: Colors.gray2,
borderWidth: 1,
borderRadius: 12,
gap: Spacing.Regular16,
},
cardLineContainer: {
flex: 1,
flexDirection: 'row',
},
cardTitleText: {
...typeScale.labelSemiBoldMedium,
color: Colors.black,
},
cardLineLabel: {
paddingRight: 20, // Prevents Icon from being cut off on long labels
},
tripleBarContainer: {
flex: 1,
gap: 2,
flexDirection: 'row',
paddingHorizontal: Spacing.Tiny4,
alignItems: 'flex-end',
paddingBottom: 3,
justifyContent: 'flex-end',
},
bar: {
width: 4,
backgroundColor: Colors.gray2,
},
barHighlighted: {
backgroundColor: Colors.primary,
},
viewDetailsText: {
...typeScale.labelSemiBoldSmall,
color: Colors.gray3,
textAlign: 'center',
flex: 1,
},
})
12 changes: 12 additions & 0 deletions src/positions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ export interface EarningItem {
includedInPoolBalance?: boolean
}

interface SafetyRisk {
isPositive: boolean
title: string
category: string
}

export interface Safety {
level: 'low' | 'medium' | 'high'
risks: SafetyRisk[]
}

interface EarnDataProps {
contractCreatedAt?: string // ISO string
manageUrl?: string
Expand All @@ -37,6 +48,7 @@ interface EarnDataProps {
withdrawTokenId: string
rewardsPositionIds?: string[]
dailyYieldRatePercentage?: number
safety?: Safety
// We'll add more fields here as needed
}

Expand Down
47 changes: 47 additions & 0 deletions test/RootStateSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,9 @@
},
"type": "array"
},
"safety": {
"$ref": "#/definitions/Safety"
},
"termsUrl": {
"type": "string"
},
Expand Down Expand Up @@ -3446,6 +3449,50 @@
],
"type": "object"
},
"Safety": {
"additionalProperties": false,
"properties": {
"level": {
"enum": [
"high",
"low",
"medium"
],
"type": "string"
},
"risks": {
"items": {
"$ref": "#/definitions/SafetyRisk"
},
"type": "array"
}
},
"required": [
"level",
"risks"
],
"type": "object"
},
"SafetyRisk": {
"additionalProperties": false,
"properties": {
"category": {
"type": "string"
},
"isPositive": {
"type": "boolean"
},
"title": {
"type": "string"
}
},
"required": [
"category",
"isPositive",
"title"
],
"type": "object"
},
"Screens": {
"enum": [
"AccounSetupFailureScreen",
Expand Down

0 comments on commit 8db264a

Please sign in to comment.