Skip to content

Commit bbdc25b

Browse files
kevinbluermatallui
authored andcommitted
feat(predict): cp-7.62.0 adds PredictMarketSportCard (#24601)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR implements the sports market card component and refactors the scoreboard to derive UI state from API data for the Predict feature. **Key changes:** - `PredictMarketSportCard` - New component displaying sports betting markets with team gradient backgrounds, integrated scoreboard, and dynamic team data (colors, abbreviations) - `PredictSportScoreboard` refactor - Updated to use centralized types (PredictGameStatus, PredictSportTeam) and derive possession/winner state from props: - turn prop (team abbreviation) determines possession indicator - Scores determine winner display when game is final - Supports 4 UI states: Pre-game (scheduled), In-progress (ongoing), Halftime (ongoing + period: 'HT'), Final (ended) - `formatGameStartTime` - New utility function for locale-aware date/time formatting of ISO 8601 strings - Type consolidation - Removed `PredictSportScoreboard.types.ts` in favor of centralized types in `types/index.ts` - Test coverage - Added comprehensive tests for `PredictMarketSportCard`, `formatGameStartTime`, and updated PredictSportScoreboard tests **Architecture improvement:** The scoreboard component now derives UI state (`possession`, `winner`) internally from API data (`turn`, `awayScore`, `homeScore`) rather than requiring parent components to compute and pass these values. This reduces coupling and makes the component easier to use with real API responses. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: [PRED-482](https://consensyssoftware.atlassian.net/browse/PRED-482) ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <img width="746" height="400" alt="image" src="https://github.com/user-attachments/assets/f1aa5ea7-24f3-41dd-8012-2885bcc50f93" /> <img width="748" height="408" alt="image" src="https://github.com/user-attachments/assets/65f96c30-03fc-4478-be7f-9d92dcd40ffa" /> <img width="734" height="410" alt="image" src="https://github.com/user-attachments/assets/b6cb2c87-dae1-4465-bcfe-2f82dc18f1e0" /> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. [PRED-482]: https://consensyssoftware.atlassian.net/browse/PRED-482?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Implements a new sports market card and simplifies scoreboard integration by deriving UI state directly from API data. > > - Adds `PredictMarketSportCard` showing title, gradient, `PredictSportScoreboard`, conditional action buttons, and navigation; uses `formatGameStartTime` for date/time > - Refactors `PredictSportScoreboard` to accept `gameStatus`, `period`, and `turn`, deriving PreGame/InProgress/Halftime/Final, possession, and winner internally; removes `PredictSportScoreboard.types.ts` and re-exports props from implementation > - Updates `PredictGameDetailsContent` to pass `gameStatus`, `period`, and `turn` (removes local game state/possession/winner computations) > - Adds `formatGameStartTime` utility with tests; adds comprehensive tests for the new card and updated scoreboard > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2e1d829. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Luis Taniça <matallui@gmail.com>
1 parent 95b43cf commit bbdc25b

11 files changed

Lines changed: 916 additions & 242 deletions

File tree

app/components/UI/Predict/components/PredictGameDetailsContent/PredictGameDetailsContent.test.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,32 +92,39 @@ jest.mock('../PredictSportTeamGradient', () => {
9292

9393
jest.mock('../PredictSportScoreboard', () => {
9494
const { View } = jest.requireActual('react-native');
95-
const actualModule = jest.requireActual(
96-
'../PredictSportScoreboard/PredictSportScoreboard.types',
97-
);
9895
return {
9996
__esModule: true,
10097
default: function MockPredictSportScoreboard({
10198
testID,
102-
gameState,
99+
gameStatus,
100+
period,
103101
awayTeam,
104102
homeTeam,
105103
}: {
106104
testID?: string;
107-
gameState?: string;
105+
gameStatus?: string;
106+
period?: string | null;
108107
awayTeam?: { abbreviation: string };
109108
homeTeam?: { abbreviation: string };
110109
}) {
110+
// Derive UI state label from gameStatus + period (same logic as real component)
111+
let stateLabel = 'undefined';
112+
if (gameStatus === 'scheduled') {
113+
stateLabel = 'PreGame';
114+
} else if (gameStatus === 'ended') {
115+
stateLabel = 'Final';
116+
} else if (gameStatus === 'ongoing' && period === 'HT') {
117+
stateLabel = 'Halftime';
118+
} else if (gameStatus === 'ongoing') {
119+
stateLabel = 'InProgress';
120+
}
111121
return (
112122
<View
113123
testID={testID}
114-
accessibilityHint={`state:${gameState ?? 'undefined'},away:${awayTeam?.abbreviation ?? 'undefined'},home:${homeTeam?.abbreviation ?? 'undefined'}`}
124+
accessibilityHint={`state:${stateLabel},away:${awayTeam?.abbreviation ?? 'undefined'},home:${homeTeam?.abbreviation ?? 'undefined'}`}
115125
/>
116126
);
117127
},
118-
GameState: actualModule.GameState,
119-
Possession: actualModule.Possession,
120-
Winner: actualModule.Winner,
121128
};
122129
});
123130

app/components/UI/Predict/components/PredictGameDetailsContent/PredictGameDetailsContent.tsx

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,9 @@ import PredictGameAboutSheet from '../PredictGameDetailsFooter/PredictGameAboutS
2626
import { usePredictBottomSheet } from '../../hooks/usePredictBottomSheet';
2727
import { PredictGameDetailsContentProps } from './PredictGameDetailsContent.types';
2828
import PredictSportTeamGradient from '../PredictSportTeamGradient';
29-
import PredictSportScoreboard, {
30-
GameState,
31-
Possession,
32-
Winner,
33-
} from '../PredictSportScoreboard';
29+
import PredictSportScoreboard from '../PredictSportScoreboard';
3430
import PredictGameChart from '../PredictGameChart';
3531
import PredictPicks from '../PredictPicks/PredictPicks';
36-
import { PredictGameScore, PredictGameStatus } from '../../types';
37-
38-
const getGameState = (
39-
status: PredictGameStatus,
40-
period: string | null,
41-
): GameState => {
42-
if (status === 'scheduled') return GameState.PreGame;
43-
if (status === 'ended') return GameState.Final;
44-
if (period?.toUpperCase() === 'HT') return GameState.Halftime;
45-
return GameState.InProgress;
46-
};
47-
48-
const getPossession = (
49-
turn: string | undefined,
50-
awayAbbr: string,
51-
homeAbbr: string,
52-
): Possession => {
53-
if (!turn) return Possession.None;
54-
const lowerTurn = turn.toLowerCase();
55-
if (lowerTurn === awayAbbr.toLowerCase()) return Possession.Away;
56-
if (lowerTurn === homeAbbr.toLowerCase()) return Possession.Home;
57-
return Possession.None;
58-
};
59-
60-
const getWinner = (score: PredictGameScore | null): Winner => {
61-
if (!score) return Winner.None;
62-
if (score.away > score.home) return Winner.Away;
63-
if (score.home > score.away) return Winner.Home;
64-
return Winner.None;
65-
};
6632

6733
const formatGameDateTime = (
6834
startTime: string,
@@ -120,15 +86,6 @@ const PredictGameDetailsContent: React.FC<PredictGameDetailsContentProps> = ({
12086
return null;
12187
}
12288

123-
const gameState = getGameState(game.status, game.period);
124-
const possession = getPossession(
125-
game.turn,
126-
game.awayTeam.abbreviation,
127-
game.homeTeam.abbreviation,
128-
);
129-
const winner =
130-
gameState === GameState.Final ? getWinner(game.score) : Winner.None;
131-
13289
return (
13390
<PredictSportTeamGradient
13491
awayColor={game.awayTeam.color}
@@ -194,13 +151,13 @@ const PredictGameDetailsContent: React.FC<PredictGameDetailsContentProps> = ({
194151
}}
195152
awayScore={game.score?.away}
196153
homeScore={game.score?.home}
197-
gameState={gameState}
154+
gameStatus={game.status}
155+
period={game.period}
198156
eventTitle={market.title}
199157
date={gameDateTime?.date}
200158
time={gameDateTime?.time}
201159
quarter={game.period ?? undefined}
202-
possession={possession}
203-
winner={winner}
160+
turn={game.turn}
204161
testID="game-scoreboard"
205162
/>
206163

0 commit comments

Comments
 (0)