Skip to content

Commit 7d9e9e6

Browse files
fix(earn): reset the deposit when the selected asset changes
1 parent c9ceefb commit 7d9e9e6

4 files changed

Lines changed: 118 additions & 70 deletions

File tree

extension/src/popup/components/earn/EarnAmount/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ export const EarnAmount = ({ goBack, onConfirm }: EarnAmountProps) => {
101101
// eslint-disable-next-line react-hooks/exhaustive-deps
102102
}, []);
103103

104+
// A different asset means the previous asset's rejection no longer describes
105+
// anything: the amount and simulation have been cleared with it. The screen
106+
// stays mounted while the picker is up, so nothing tears this state down.
107+
useEffect(() => {
108+
setSimulationError("");
109+
}, [asset]);
110+
104111
// The review sheet is the flow's `confirm` step — the last screen before a
105112
// signature. It is a sheet rather than a step, so the Earn view's step effect
106113
// never sees it. The ref clears on close so correcting the amount and

extension/src/popup/components/earn/PoolDetailsSheet/index.tsx

Lines changed: 74 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -63,78 +63,83 @@ export const PoolDetailsSheet = ({ pool, onClose }: PoolDetailsSheetProps) => {
6363
</button>
6464
</div>
6565

66-
{description && (
67-
<div className="PoolDetailsSheet__description">
68-
<Text as="div" size="xs">
69-
{t("Description")}
70-
</Text>
71-
<Text as="p" size="sm">
72-
{description}
73-
</Text>
74-
</div>
75-
)}
66+
<div
67+
className="PoolDetailsSheet__body"
68+
data-testid="earn-pool-details-body"
69+
>
70+
{description && (
71+
<div className="PoolDetailsSheet__description">
72+
<Text as="div" size="xs">
73+
{t("Description")}
74+
</Text>
75+
<Text as="p" size="sm">
76+
{description}
77+
</Text>
78+
</div>
79+
)}
7680

77-
<Text as="div" size="xs">
78-
{t("Pool Details")}
79-
</Text>
81+
<Text as="div" size="xs">
82+
{t("Pool Details")}
83+
</Text>
8084

81-
<div className="PoolDetailsSheet__group">
82-
<StatRow
83-
label={t("Lending Interest")}
84-
value={formatRate(pool.interestApy)}
85-
testId="earn-pool-interest-apy"
86-
/>
87-
<StatRow
88-
label={t("Current Net APY")}
89-
value={formatRate(pool.netApy)}
90-
isPositive
91-
testId="earn-pool-net-apy"
92-
/>
93-
</div>
85+
<div className="PoolDetailsSheet__group">
86+
<StatRow
87+
label={t("Lending Interest")}
88+
value={formatRate(pool.interestApy)}
89+
testId="earn-pool-interest-apy"
90+
/>
91+
<StatRow
92+
label={t("Current Net APY")}
93+
value={formatRate(pool.netApy)}
94+
isPositive
95+
testId="earn-pool-net-apy"
96+
/>
97+
</div>
9498

95-
<div className="PoolDetailsSheet__group">
96-
<StatRow
97-
label={t("Accepted tokens")}
98-
value={
99-
<div className="PoolDetailsSheet__tokens">
100-
{pool.reserves.map((reserve) => {
101-
// The catalog reports native XLM with no symbol and no name, so
102-
// the identity has to be derived rather than read straight off
103-
// the reserve — AssetIcon needs a code to recognise XLM and an
104-
// issuer to look anything else up.
105-
const { code, issuer } = getCatalogAssetIdentity({
106-
symbol: reserve.symbol,
107-
name: reserve.name,
108-
assetId: reserve.assetId,
109-
networkDetails,
110-
});
111-
return (
112-
<AssetIcon
113-
key={reserve.assetId}
114-
assetIcons={reserveIcons}
115-
code={code}
116-
issuerKey={issuer}
117-
/>
118-
);
119-
})}
120-
</div>
121-
}
122-
/>
123-
<StatRow
124-
label={t("Supplied")}
125-
value={formatCompactUsd(pool.suppliedUsd)}
126-
testId="earn-pool-supplied"
127-
/>
128-
<StatRow
129-
label={t("Borrowed")}
130-
value={formatCompactUsd(pool.borrowedUsd)}
131-
testId="earn-pool-borrowed"
132-
/>
133-
<StatRow
134-
label={t("Backstop")}
135-
value={formatCompactUsd(pool.backstopUsd)}
136-
testId="earn-pool-backstop"
137-
/>
99+
<div className="PoolDetailsSheet__group">
100+
<StatRow
101+
label={t("Accepted tokens")}
102+
value={
103+
<div className="PoolDetailsSheet__tokens">
104+
{pool.reserves.map((reserve) => {
105+
// The catalog reports native XLM with no symbol and no name,
106+
// so the identity has to be derived rather than read straight
107+
// off the reserve — AssetIcon needs a code to recognise XLM
108+
// and an issuer to look anything else up.
109+
const { code, issuer } = getCatalogAssetIdentity({
110+
symbol: reserve.symbol,
111+
name: reserve.name,
112+
assetId: reserve.assetId,
113+
networkDetails,
114+
});
115+
return (
116+
<AssetIcon
117+
key={reserve.assetId}
118+
assetIcons={reserveIcons}
119+
code={code}
120+
issuerKey={issuer}
121+
/>
122+
);
123+
})}
124+
</div>
125+
}
126+
/>
127+
<StatRow
128+
label={t("Supplied")}
129+
value={formatCompactUsd(pool.suppliedUsd)}
130+
testId="earn-pool-supplied"
131+
/>
132+
<StatRow
133+
label={t("Borrowed")}
134+
value={formatCompactUsd(pool.borrowedUsd)}
135+
testId="earn-pool-borrowed"
136+
/>
137+
<StatRow
138+
label={t("Backstop")}
139+
value={formatCompactUsd(pool.backstopUsd)}
140+
testId="earn-pool-backstop"
141+
/>
142+
</div>
138143
</div>
139144

140145
<Button

extension/src/popup/components/earn/PoolDetailsSheet/styles.scss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,33 @@
66
flex-direction: column;
77
gap: pxToRem(12);
88

9+
// The stat rows grow as the pool gains fields, and SlideupModal sizes itself
10+
// to its content's scrollHeight — so without a cap here the sheet runs to the
11+
// top of the popup with nothing to scroll. Capped like `.EarnReview--details`,
12+
// with the header and the Close button pinned and only the body scrolling.
13+
max-height: 85vh;
14+
915
&__header {
1016
display: flex;
1117
align-items: flex-start;
1218
justify-content: space-between;
1319
gap: pxToRem(12);
20+
flex: none;
21+
}
22+
23+
&__body {
24+
flex: 1;
25+
// Without this a flex item refuses to shrink below its content height, and
26+
// the overflow never engages.
27+
min-height: 0;
28+
overflow-y: auto;
29+
display: flex;
30+
flex-direction: column;
31+
gap: pxToRem(12);
32+
}
33+
34+
> .Button {
35+
flex: none;
1436
}
1537

1638
&__identity {

extension/src/popup/views/Earn/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
earnSelector,
2222
resetEarn,
23+
saveCurrentPositionTokens,
2324
saveEarnPool,
2425
saveSelectedAssetApy,
2526
saveSelectedAssetId,
@@ -94,7 +95,7 @@ export const Earn = () => {
9495
const { t } = useTranslation();
9596
const { hasSeenIntro, dismissIntro } = useEarnIntroSeen();
9697
const submission = useSelector(transactionSubmissionSelector);
97-
const { pool } = useSelector(earnSelector);
98+
const { pool, selectedAssetId } = useSelector(earnSelector);
9899

99100
// Start on CHOOSE_TOKEN and only fall back to the interstitial once the
100101
// persisted flag has actually resolved to false. Defaulting to INTRO instead
@@ -220,6 +221,19 @@ export const Earn = () => {
220221
poolId: option.poolId,
221222
apy: option.apy,
222223
});
224+
// Picking a different asset invalidates everything the last
225+
// one configured — the amount, its simulation and prepared XDR,
226+
// the fetched position, a previous failure banner. Only a real
227+
// change clears it, so backing out of the amount screen and
228+
// re-picking the same asset returns to the amount already
229+
// entered. The amount screen clears its own simulation error off
230+
// the same asset change; it stays mounted behind the picker, so
231+
// nothing here can reach its component state.
232+
if (option.assetId !== selectedAssetId) {
233+
dispatch(resetSubmission());
234+
dispatch(saveCurrentPositionTokens("0"));
235+
dispatch(setEarnSubmitFailed(false));
236+
}
223237
dispatch(saveEarnPool(resolved.pool));
224238
dispatch(saveSelectedAssetApy(option.apy));
225239
dispatch(saveSelectedAssetId(option.assetId));

0 commit comments

Comments
 (0)