Skip to content

Commit 769fb04

Browse files
committed
chore: clean up
1 parent 18c8cec commit 769fb04

2 files changed

Lines changed: 197 additions & 83 deletions

File tree

app/components/Views/SocialLeaderboard/FeedView/components/FeedTypeEmptyState.tsx

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ const FeedTypeEmptyState: React.FC<FeedTypeEmptyStateProps> = ({
4949
? 'social_leaderboard.feed.empty_type.tokens.title'
5050
: 'social_leaderboard.feed.empty_type.perps.title';
5151

52+
const renderLoadMoreAction = () => {
53+
if (!hasNextPage) {
54+
return null;
55+
}
56+
if (isFetchingNextPage) {
57+
return <ActivityIndicator size="small" color={colors.icon.default} />;
58+
}
59+
return (
60+
<Button
61+
variant={ButtonVariant.Secondary}
62+
size={ButtonSize.Sm}
63+
onPress={onLoadMore}
64+
twClassName="self-center"
65+
testID={FeedViewSelectorsIDs.LOAD_MORE_BUTTON}
66+
>
67+
{strings('social_leaderboard.feed.load_more')}
68+
</Button>
69+
);
70+
};
71+
5272
return (
5373
<Box
5474
alignItems={BoxAlignItems.Center}
@@ -76,21 +96,7 @@ const FeedTypeEmptyState: React.FC<FeedTypeEmptyStateProps> = ({
7696
>
7797
{strings('social_leaderboard.feed.empty_type.description')}
7898
</Text>
79-
{hasNextPage ? (
80-
isFetchingNextPage ? (
81-
<ActivityIndicator size="small" color={colors.icon.default} />
82-
) : (
83-
<Button
84-
variant={ButtonVariant.Secondary}
85-
size={ButtonSize.Sm}
86-
onPress={onLoadMore}
87-
twClassName="self-center"
88-
testID={FeedViewSelectorsIDs.LOAD_MORE_BUTTON}
89-
>
90-
{strings('social_leaderboard.feed.load_more')}
91-
</Button>
92-
)
93-
) : null}
99+
{renderLoadMoreAction()}
94100
</Box>
95101
);
96102
};

app/components/Views/SocialLeaderboard/FeedView/utils/mapFeedItem.ts

Lines changed: 176 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ import {
1717
formatSignedUsd,
1818
formatUsd,
1919
} from '../../utils/formatters';
20-
import type { FeedAction, FeedItem } from '../types';
20+
import type {
21+
FeedAction,
22+
FeedItem,
23+
FeedPerpItem,
24+
FeedSpotItem,
25+
} from '../types';
2126

2227
const isPresentNumber = (value: number | null | undefined): value is number =>
2328
value != null && Number.isFinite(value);
@@ -26,6 +31,14 @@ const isPresentNumber = (value: number | null | undefined): value is number =>
2631
const toMs = (timestamp: number): number =>
2732
timestamp < 1e12 ? timestamp * 1000 : timestamp;
2833

34+
interface FeedItemPresentation {
35+
valueLabel: string;
36+
pnlLabel: string;
37+
hasValueData: boolean;
38+
hasPnlData: boolean;
39+
isPnlPositive: boolean;
40+
}
41+
2942
/**
3043
* Picks the trade that drives a feed row's action verb: the one whose timestamp
3144
* matches the feed item, falling back to the most recent trade when none lines
@@ -46,8 +59,10 @@ function findTriggeringTrade(
4659
return exact;
4760
}
4861

49-
return trades.reduce((latest, trade) =>
50-
toMs(trade.timestamp) > toMs(latest.timestamp) ? trade : latest,
62+
return trades.reduce(
63+
(latest, trade) =>
64+
toMs(trade.timestamp) > toMs(latest.timestamp) ? trade : latest,
65+
trades[0],
5166
);
5267
}
5368

@@ -89,99 +104,153 @@ function buildSubHeader(trade: Trade | undefined): string {
89104
return size;
90105
}
91106

92-
/**
93-
* Maps a core `SocialService` feed item (`Position` + `actor` + `timestamp`)
94-
* into the presentation `FeedItem` consumed by `FeedItemRow`.
95-
*
96-
* Returns `null` for spot trades whose chain we can't map to a CAIP id — the
97-
* Trade button and network badge both need it, so the row is skipped rather
98-
* than rendered half-wired.
99-
*/
100-
export function mapFeedItem(coreItem: CoreFeedItem): FeedItem | null {
101-
const { actor, timestamp, trades } = coreItem;
107+
function realizedPnlPercent(
108+
realizedPnl: number,
109+
boughtUsd: number | null | undefined,
110+
): number | null {
111+
if (!boughtUsd) {
112+
return null;
113+
}
114+
return (realizedPnl / boughtUsd) * 100;
115+
}
102116

103-
const timestampMs = toMs(timestamp);
104-
const isPerp = isPerpPosition(coreItem);
105-
const isClosed = isClosedPosition(coreItem);
106-
const trade = findTriggeringTrade(trades ?? [], timestampMs);
107-
const action = resolveAction(trade, isPerp, isClosed);
108-
const subHeader = buildSubHeader(trade);
117+
function resolvePnlValue(
118+
coreItem: CoreFeedItem,
119+
isPerp: boolean,
120+
isClosed: boolean,
121+
): number | null | undefined {
122+
if (isPerp) {
123+
return coreItem.pnlValueUsd ?? coreItem.realizedPnl;
124+
}
125+
if (isClosed) {
126+
return coreItem.realizedPnl;
127+
}
128+
return coreItem.pnlValueUsd;
129+
}
109130

110-
const pnlValue = isPerp
111-
? (coreItem.pnlValueUsd ?? coreItem.realizedPnl)
112-
: isClosed
113-
? coreItem.realizedPnl
114-
: coreItem.pnlValueUsd;
115-
const pnlPercent = isPerp
116-
? (coreItem.pnlPercent ??
117-
(coreItem.boughtUsd
118-
? (coreItem.realizedPnl / coreItem.boughtUsd) * 100
119-
: null))
120-
: isClosed
121-
? coreItem.boughtUsd
122-
? (coreItem.realizedPnl / coreItem.boughtUsd) * 100
123-
: null
124-
: (coreItem.pnlPercent ?? null);
131+
function resolvePnlPercent(
132+
coreItem: CoreFeedItem,
133+
isPerp: boolean,
134+
isClosed: boolean,
135+
): number | null {
136+
if (isPerp) {
137+
return (
138+
coreItem.pnlPercent ??
139+
realizedPnlPercent(coreItem.realizedPnl, coreItem.boughtUsd)
140+
);
141+
}
142+
if (isClosed) {
143+
return realizedPnlPercent(coreItem.realizedPnl, coreItem.boughtUsd);
144+
}
145+
return coreItem.pnlPercent ?? null;
146+
}
125147

148+
function resolveValueLabel(
149+
hasValueData: boolean,
150+
isClosed: boolean,
151+
pnlValue: number | null | undefined,
152+
currentValueUSD: number | null | undefined,
153+
): string {
154+
if (!hasValueData) {
155+
return '';
156+
}
157+
if (isClosed && isPresentNumber(pnlValue)) {
158+
return formatSignedUsd(pnlValue);
159+
}
160+
if (isPresentNumber(currentValueUSD)) {
161+
return formatUsd(currentValueUSD);
162+
}
163+
return '';
164+
}
165+
166+
function buildFeedItemPresentation(
167+
coreItem: CoreFeedItem,
168+
isPerp: boolean,
169+
isClosed: boolean,
170+
): FeedItemPresentation {
171+
const pnlValue = resolvePnlValue(coreItem, isPerp, isClosed);
172+
const pnlPercent = resolvePnlPercent(coreItem, isPerp, isClosed);
126173
const hasValueData = isClosed
127174
? isPresentNumber(pnlValue)
128175
: isPresentNumber(coreItem.currentValueUSD);
129176
const hasPnlData = isPresentNumber(pnlPercent);
130-
131-
const valueLabel = hasValueData
132-
? isClosed
133-
? formatSignedUsd(pnlValue)
134-
: formatUsd(coreItem.currentValueUSD)
135-
: '';
177+
const valueLabel = resolveValueLabel(
178+
hasValueData,
179+
isClosed,
180+
pnlValue,
181+
coreItem.currentValueUSD,
182+
);
136183
const pnlLabel = hasPnlData ? formatPercent(pnlPercent) : '';
137184
const pnlSignSource = isClosed
138185
? pnlValue
139186
: (coreItem.pnlValueUsd ?? pnlPercent);
140187
const isPnlPositive =
141188
hasPnlData && isPresentNumber(pnlSignSource) && pnlSignSource >= 0;
142189

143-
const base = {
144-
id: `${coreItem.positionId}-${timestamp}`,
145-
username: actor.name,
146-
traderAddress: actor.address,
147-
avatarUri: actor.imageUrl ?? undefined,
148-
action,
149-
timestamp: timestampMs,
150-
subHeader,
190+
return {
151191
valueLabel,
152192
pnlLabel,
153193
hasValueData,
154194
hasPnlData,
155195
isPnlPositive,
156196
};
197+
}
157198

158-
if (isPerp) {
159-
const { targetSymbol } = getSupportedXyzPerpMarketSymbol(
160-
coreItem.tokenSymbol,
161-
);
162-
// Display uses the prefix-free symbol; navigation keeps the tradable
163-
const displaySymbol = getPerpsDisplaySymbol(coreItem.tokenSymbol);
164-
return {
165-
...base,
166-
type: 'perps',
167-
marketSymbol: displaySymbol,
168-
marketName: displaySymbol,
169-
tradeSymbol: targetSymbol,
170-
direction: getPerpPositionDirection(coreItem) ?? 'long',
171-
// Prefer the position leverage, fall back to the triggering trade's, and
172-
// leave `null` when neither is present so the badge is hidden rather than
173-
// showing a misleading "1x" (parity with PositionRow / TradeRow).
174-
leverage: coreItem.perpLeverage ?? trade?.perpLeverage ?? null,
175-
};
176-
}
199+
function mapPerpFeedItem(
200+
coreItem: CoreFeedItem,
201+
trade: Trade | undefined,
202+
presentation: FeedItemPresentation,
203+
action: FeedAction,
204+
timestampMs: number,
205+
subHeader: string,
206+
): FeedPerpItem {
207+
const { targetSymbol } = getSupportedXyzPerpMarketSymbol(
208+
coreItem.tokenSymbol,
209+
);
210+
const displaySymbol = getPerpsDisplaySymbol(coreItem.tokenSymbol);
177211

212+
return {
213+
id: `${coreItem.positionId}-${coreItem.timestamp}`,
214+
username: coreItem.actor.name,
215+
traderAddress: coreItem.actor.address,
216+
avatarUri: coreItem.actor.imageUrl ?? undefined,
217+
action,
218+
timestamp: timestampMs,
219+
subHeader,
220+
...presentation,
221+
type: 'perps',
222+
marketSymbol: displaySymbol,
223+
marketName: displaySymbol,
224+
tradeSymbol: targetSymbol,
225+
direction: getPerpPositionDirection(coreItem) ?? 'long',
226+
// Prefer the position leverage, fall back to the triggering trade's, and
227+
// leave `null` when neither is present so the badge is hidden rather than
228+
// showing a misleading "1x" (parity with PositionRow / TradeRow).
229+
leverage: coreItem.perpLeverage ?? trade?.perpLeverage ?? null,
230+
};
231+
}
232+
233+
function mapSpotFeedItem(
234+
coreItem: CoreFeedItem,
235+
presentation: FeedItemPresentation,
236+
action: FeedAction,
237+
timestampMs: number,
238+
subHeader: string,
239+
): FeedSpotItem | null {
178240
const chain = chainNameToId(coreItem.chain);
179241
if (!chain) {
180242
return null;
181243
}
182244

183245
return {
184-
...base,
246+
id: `${coreItem.positionId}-${coreItem.timestamp}`,
247+
username: coreItem.actor.name,
248+
traderAddress: coreItem.actor.address,
249+
avatarUri: coreItem.actor.imageUrl ?? undefined,
250+
action,
251+
timestamp: timestampMs,
252+
subHeader,
253+
...presentation,
185254
type: 'spot',
186255
tokenSymbol: coreItem.tokenSymbol,
187256
tokenName: coreItem.tokenName,
@@ -190,3 +259,42 @@ export function mapFeedItem(coreItem: CoreFeedItem): FeedItem | null {
190259
chainIdHex: caipChainIdToHex(chain),
191260
};
192261
}
262+
263+
/**
264+
* Maps a core `SocialService` feed item (`Position` + `actor` + `timestamp`)
265+
* into the presentation `FeedItem` consumed by `FeedItemRow`.
266+
*
267+
* Returns `null` for spot trades whose chain we can't map to a CAIP id — the
268+
* Trade button and network badge both need it, so the row is skipped rather
269+
* than rendered half-wired.
270+
*/
271+
export function mapFeedItem(coreItem: CoreFeedItem): FeedItem | null {
272+
const { timestamp, trades } = coreItem;
273+
274+
const timestampMs = toMs(timestamp);
275+
const isPerp = isPerpPosition(coreItem);
276+
const isClosed = isClosedPosition(coreItem);
277+
const trade = findTriggeringTrade(trades ?? [], timestampMs);
278+
const action = resolveAction(trade, isPerp, isClosed);
279+
const subHeader = buildSubHeader(trade);
280+
const presentation = buildFeedItemPresentation(coreItem, isPerp, isClosed);
281+
282+
if (isPerp) {
283+
return mapPerpFeedItem(
284+
coreItem,
285+
trade,
286+
presentation,
287+
action,
288+
timestampMs,
289+
subHeader,
290+
);
291+
}
292+
293+
return mapSpotFeedItem(
294+
coreItem,
295+
presentation,
296+
action,
297+
timestampMs,
298+
subHeader,
299+
);
300+
}

0 commit comments

Comments
 (0)