-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(points): points activities list redesign (#5616)
### Description Implement new points activities list design. Figma: https://www.figma.com/design/rXBDplfMHHqYmuu6EkgMEo/Gamification-experiments?node-id=1747-6464&t=aOfd2uHuL1gAJfTG-0 Important changes: 1. Activities are rendered as a list 2. No more grouping by points amount; the amount is indicated on the card 3. Activities can have previous amount crossed out 4. "More coming soon" card dividing incomplete and completed activities 5. "More coming soon" card has a new icon (rushing clock instead of rocket) Implementation notes: * `ActivityCard` is refactored as a "dumb" component only relying on its props * Added a specific `CheckCircle` icon instead of a mix of another icon and css styling used previously | New design | Previous design | |--------|--------| | ![new design](https://github.com/valora-inc/wallet/assets/2737872/30687de9-2b81-43b7-a67c-dc650e67af21) | ![prev design](https://github.com/valora-inc/wallet/assets/2737872/8ae2badb-8793-4f36-bb43-d174f6323c61) | Crossed out previous amount: | Card | Bottom sheet | |--------|--------| | <img width="279" alt="previous amount" src="https://github.com/valora-inc/wallet/assets/2737872/3fc30522-ab1d-4ba1-a443-d926f20b3964"> | ![image](https://github.com/valora-inc/wallet/assets/2737872/65be43d9-d63e-42ed-82de-43fcbd327d53) | ### Test plan * Tested manually * Updated unit tests ### Related issues - Fixes RET-1103 ### Backwards compatibility Y ### 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
Showing
17 changed files
with
393 additions
and
357 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import React from 'react' | ||
import Colors from 'src/styles/colors' | ||
import Svg, { Path } from 'svgs' | ||
|
||
interface Props { | ||
size?: number | ||
color?: string | ||
testID?: string | ||
} | ||
|
||
const CheckCircle = ({ size = 24, color = Colors.black, testID }: Props) => ( | ||
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" testID={testID}> | ||
<Path | ||
fill={color} | ||
d="m10.6 13.8-2.15-2.15a.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275.948.948 0 0 0-.275.7c0 .283.092.517.275.7L9.9 15.9c.2.2.433.3.7.3.267 0 .5-.1.7-.3l5.65-5.65a.948.948 0 0 0 .275-.7.948.948 0 0 0-.275-.7.948.948 0 0 0-.7-.275.948.948 0 0 0-.7.275L10.6 13.8ZM12 22a9.738 9.738 0 0 1-3.9-.788 10.099 10.099 0 0 1-3.175-2.137c-.9-.9-1.612-1.958-2.137-3.175A9.738 9.738 0 0 1 2 12c0-1.383.263-2.683.788-3.9a10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.738 9.738 0 0 1 12 2c1.383 0 2.683.263 3.9.788a10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.738 9.738 0 0 1 12 22Zm0-2c2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 2.233.775 4.125 2.325 5.675C7.875 19.225 9.767 20 12 20Z" | ||
/> | ||
</Svg> | ||
) | ||
export default CheckCircle |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react' | ||
import Colors from 'src/styles/colors' | ||
import Svg, { Path } from 'svgs' | ||
|
||
interface Props { | ||
size?: number | ||
color?: string | ||
} | ||
|
||
const RushingClock = ({ size = 24, color = Colors.black }: Props) => ( | ||
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none"> | ||
<Path | ||
fill={color} | ||
d="M14.667 18.333c-1.861 0-3.438-.645-4.73-1.937C8.646 15.104 8 13.528 8 11.666c0-1.847.646-3.42 1.938-4.718C11.229 5.649 12.806 5 14.666 5c1.847 0 3.42.65 4.718 1.948 1.299 1.299 1.948 2.871 1.948 4.719 0 1.86-.649 3.437-1.948 4.729-1.298 1.291-2.871 1.937-4.718 1.937Zm0-1.666c1.389 0 2.57-.486 3.541-1.459.973-.972 1.459-2.152 1.459-3.541 0-1.39-.486-2.57-1.459-3.542-.972-.972-2.152-1.458-3.541-1.458-1.39 0-2.57.486-3.542 1.458s-1.458 2.153-1.458 3.542c0 1.389.486 2.57 1.458 3.541.972.973 2.153 1.459 3.542 1.459Zm1.896-1.896 1.187-1.188-2.25-2.25v-3h-1.667v3.688l2.73 2.75ZM3.832 9.167V7.5h3.334v1.667H3.833ZM3 12.5v-1.667h4.167V12.5H3Zm.833 3.333v-1.666h3.334v1.666H3.833Z" | ||
/> | ||
</Svg> | ||
) | ||
export default RushingClock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { fireEvent, render } from '@testing-library/react-native' | ||
import React from 'react' | ||
import { PointsEvents } from 'src/analytics/Events' | ||
import ValoraAnalytics from 'src/analytics/ValoraAnalytics' | ||
import SwapArrows from 'src/icons/SwapArrows' | ||
import ActivityCard, { Props } from './ActivityCard' | ||
|
||
jest.mock('src/analytics/ValoraAnalytics') | ||
|
||
const mockProps: Props = { | ||
activityId: 'swap', | ||
pointsAmount: 20, | ||
previousPointsAmount: 10, | ||
completed: false, | ||
title: 'Swap', | ||
icon: <SwapArrows testID="SwapArrowsIcon" />, | ||
onPress: jest.fn(), | ||
} | ||
|
||
describe('ActivityCard', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
it('renders correctly', () => { | ||
const { getByText, getByTestId } = render(<ActivityCard {...mockProps} />) | ||
|
||
expect(getByText('Swap')).toBeTruthy() | ||
expect(getByText('20')).toBeTruthy() | ||
expect(getByText('10')).toBeTruthy() | ||
expect(getByTestId('SwapArrowsIcon')).toBeTruthy() | ||
}) | ||
|
||
it('renders correcly when completed', () => { | ||
const completedProps = { ...mockProps, completed: true } | ||
const { getByTestId } = render(<ActivityCard {...completedProps} />) | ||
|
||
const card = getByTestId('ActivityCard/swap') | ||
expect(card).toBeDisabled() | ||
expect(card.props.style).toContainEqual({ opacity: 0.5 }) | ||
expect(getByTestId('CheckCircleIcon')).toBeTruthy() | ||
}) | ||
|
||
it('handles press correctly', () => { | ||
const { getByText } = render(<ActivityCard {...mockProps} />) | ||
|
||
fireEvent.press(getByText('Swap')) | ||
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_screen_card_press, { | ||
activityId: 'swap', | ||
}) | ||
expect(mockProps.onPress).toHaveBeenCalled() | ||
}) | ||
|
||
it('tracks analytics event even when no press handler provided', () => { | ||
const noPressProps = { ...mockProps, onPress: undefined } | ||
const { getByText } = render(<ActivityCard {...noPressProps} />) | ||
|
||
fireEvent.press(getByText('Swap')) | ||
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_screen_card_press, { | ||
activityId: 'swap', | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,100 @@ | ||
import React from 'react' | ||
import React, { ReactNode } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { StyleSheet, Text, View } from 'react-native' | ||
import { PointsEvents } from 'src/analytics/Events' | ||
import ValoraAnalytics from 'src/analytics/ValoraAnalytics' | ||
import Touchable from 'src/components/Touchable' | ||
import CheckCircle from 'src/icons/CheckCircle' | ||
import LogoHeart from 'src/icons/LogoHeart' | ||
import RushingClock from 'src/icons/RushingClock' | ||
import { PointsActivity } from 'src/points/types' | ||
import { Colors } from 'src/styles/colors' | ||
import { typeScale } from 'src/styles/fonts' | ||
import { Spacing } from 'src/styles/styles' | ||
import { StyleSheet, Text, View } from 'react-native' | ||
import { Colors } from 'src/styles/colors' | ||
import Checkmark from 'src/icons/Checkmark' | ||
import { PointsActivityId, BottomSheetMetadata, BottomSheetParams } from 'src/points/types' | ||
import useCardDefinitions from 'src/points/cardDefinitions' | ||
import ValoraAnalytics from 'src/analytics/ValoraAnalytics' | ||
import { PointsEvents } from 'src/analytics/Events' | ||
|
||
interface Props { | ||
activityId: PointsActivityId | ||
pointsAmount: number | ||
onPress: (bottomSheetParams: BottomSheetParams) => void | ||
completed?: boolean | ||
export interface Props extends PointsActivity { | ||
title: string | ||
icon: ReactNode | ||
onPress?: () => void | ||
} | ||
|
||
export default function ActivityCard({ activityId, pointsAmount, onPress, completed }: Props) { | ||
const cardDefinition = useCardDefinitions(pointsAmount)[activityId] | ||
export default function ActivityCard({ | ||
activityId, | ||
pointsAmount, | ||
previousPointsAmount, | ||
completed = false, | ||
title, | ||
icon, | ||
onPress, | ||
}: Props) { | ||
const handleOnPress = () => { | ||
ValoraAnalytics.track(PointsEvents.points_screen_card_press, { activityId }) | ||
onPress?.() | ||
} | ||
|
||
const isCompleted = completed !== undefined ? completed : cardDefinition.defaultCompletionStatus | ||
return ( | ||
<Touchable | ||
testID={`ActivityCard/${activityId}`} | ||
style={[styles.card, completed && styles.faded]} | ||
borderRadius={Spacing.Smallest8} | ||
disabled={completed} | ||
onPress={handleOnPress} | ||
> | ||
<> | ||
{completed ? <CheckCircle testID="CheckCircleIcon" /> : icon} | ||
<Text style={styles.cardTitle}>{title}</Text> | ||
<View style={styles.pointsAmountContainer}> | ||
<Text style={styles.previousPointsAmount}>{previousPointsAmount}</Text> | ||
<Text style={styles.pointsAmount}>{pointsAmount}</Text> | ||
<LogoHeart size={Spacing.Regular16} /> | ||
</View> | ||
</> | ||
</Touchable> | ||
) | ||
} | ||
|
||
const onPressWrapper = (bottomSheetMetadata: BottomSheetMetadata) => { | ||
return () => { | ||
ValoraAnalytics.track(PointsEvents.points_screen_card_press, { | ||
activityId, | ||
}) | ||
onPress({ | ||
...bottomSheetMetadata, | ||
pointsAmount, | ||
activityId, | ||
}) | ||
} | ||
} | ||
export function MoreComingCard() { | ||
const { t } = useTranslation() | ||
|
||
const cardStyle = { | ||
...styles.card, | ||
opacity: isCompleted ? 0.5 : 1, | ||
} | ||
return ( | ||
<View style={styles.container}> | ||
<View style={styles.cardContainer}> | ||
<Touchable | ||
testID={`PointsActivityCard-${activityId}-${pointsAmount}`} | ||
style={cardStyle} | ||
onPress={ | ||
cardDefinition.bottomSheet ? onPressWrapper(cardDefinition.bottomSheet) : undefined | ||
} | ||
disabled={!cardDefinition.bottomSheet} | ||
borderRadius={Spacing.Regular16} | ||
> | ||
<> | ||
{isCompleted && ( | ||
<View style={styles.checkmarkIcon}> | ||
<Checkmark height={12} width={12} color={Colors.black} stroke={true} /> | ||
</View> | ||
)} | ||
{cardDefinition.icon} | ||
<Text style={styles.cardTitle}>{cardDefinition.title}</Text> | ||
</> | ||
</Touchable> | ||
</View> | ||
<View style={styles.card}> | ||
<RushingClock /> | ||
<Text style={styles.cardTitle}>{t('points.activityCards.moreComing.title')}</Text> | ||
</View> | ||
) | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flexBasis: '50%', | ||
padding: Spacing.Smallest8, | ||
}, | ||
checkmarkIcon: { | ||
position: 'absolute', | ||
top: Spacing.Smallest8, | ||
right: Spacing.Smallest8, | ||
borderRadius: 16, | ||
borderWidth: 1.5, | ||
borderColor: Colors.black, | ||
}, | ||
cardTitle: { | ||
...typeScale.labelXSmall, | ||
paddingTop: Spacing.Smallest8, | ||
textAlign: 'center', | ||
}, | ||
cardContainer: { | ||
borderRadius: Spacing.Regular16, | ||
backgroundColor: Colors.gray1, | ||
...typeScale.labelSmall, | ||
}, | ||
card: { | ||
flex: 1, | ||
justifyContent: 'center', | ||
backgroundColor: Colors.gray1, | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
padding: Spacing.Regular16, | ||
minHeight: 96, | ||
gap: Spacing.Smallest8, | ||
}, | ||
faded: { | ||
opacity: 0.5, | ||
}, | ||
pointsAmountContainer: { | ||
marginLeft: 'auto', | ||
flexDirection: 'row', | ||
borderRadius: 100, | ||
borderWidth: 1, | ||
borderColor: Colors.gray2, | ||
paddingHorizontal: Spacing.Smallest8, | ||
paddingVertical: Spacing.Tiny4, | ||
gap: Spacing.Tiny4, | ||
}, | ||
pointsAmount: { | ||
...typeScale.labelXSmall, | ||
color: Colors.black, | ||
}, | ||
previousPointsAmount: { | ||
...typeScale.labelXSmall, | ||
color: Colors.gray3, | ||
textDecorationLine: 'line-through', | ||
}, | ||
}) |
Oops, something went wrong.