Skip to content

Commit 301a840

Browse files
committed
pool+accounts: add renewal account flow
1 parent 9d271b5 commit 301a840

File tree

12 files changed

+377
-24
lines changed

12 files changed

+377
-24
lines changed

app/src/__tests__/components/pool/AccountSection.spec.tsx

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ describe('AccountSection', () => {
260260

261261
expect(getByText('Expires in ~2 days')).toBeInTheDocument();
262262
expect(getByText(/Orders will no longer be matched/)).toBeInTheDocument();
263-
expect(getByText('Close Account')).toBeInTheDocument();
263+
expect(getByText('Close')).toBeInTheDocument();
264264
});
265265

266266
it('should close an expired account', async () => {
@@ -271,8 +271,8 @@ describe('AccountSection', () => {
271271

272272
const { getByText, changeInput } = render();
273273

274-
expect(getByText('Close Account')).toBeInTheDocument();
275-
fireEvent.click(getByText('Close Account'));
274+
expect(getByText('Close')).toBeInTheDocument();
275+
fireEvent.click(getByText('Close'));
276276

277277
changeInput('Destination Address', 'abc123');
278278
changeInput('Fee', '10');
@@ -284,9 +284,6 @@ describe('AccountSection', () => {
284284

285285
// capture the request that is sent to the API
286286
let req: POOL.CloseAccountRequest.AsObject;
287-
// injectIntoGrpcUnary((desc, props) => {
288-
// req = props.request.toObject() as any;
289-
// }, 'CloseAccount');
290287
grpcMock.unary.mockImplementation((desc, props) => {
291288
if (desc.methodName === 'CloseAccount') {
292289
req = props.request.toObject() as any;
@@ -314,4 +311,57 @@ describe('AccountSection', () => {
314311
expect(req!.outputWithFee?.feeRateSatPerKw).toBe(2500);
315312
expect(req!.outputWithFee?.address).toBe('abc123');
316313
});
314+
315+
it('should renew an expired account', async () => {
316+
// set the account to expire in less than 3 days
317+
runInAction(() => {
318+
const currHeight = store.nodeStore.blockHeight;
319+
store.accountStore.activeAccount.expirationHeight = currHeight + 144 * 2;
320+
});
321+
322+
const { getByText, changeInput } = render();
323+
324+
expect(getByText('Renew Account')).toBeInTheDocument();
325+
fireEvent.click(getByText('Renew Account'));
326+
327+
changeInput('New Expiration', '2016');
328+
changeInput('Fee Rate', '125');
329+
fireEvent.click(getByText('Renew'));
330+
331+
// check confirmation values
332+
expect(getByText('288 blocks')).toBeInTheDocument();
333+
expect(getByText('~2 days')).toBeInTheDocument();
334+
expect(getByText('2016 blocks')).toBeInTheDocument();
335+
expect(getByText('~2 weeks')).toBeInTheDocument();
336+
expect(getByText('125 sats/vByte')).toBeInTheDocument();
337+
338+
// capture the request that is sent to the API
339+
let req: POOL.RenewAccountRequest.AsObject;
340+
grpcMock.unary.mockImplementation((desc, props) => {
341+
if (desc.methodName === 'RenewAccount') {
342+
req = props.request.toObject() as any;
343+
}
344+
const path = `${desc.service.serviceName}.${desc.methodName}`;
345+
const toObject = () => sampleApiResponses[path];
346+
// return a response by calling the onEnd function
347+
props.onEnd({
348+
status: grpc.Code.OK,
349+
// the message returned should have a toObject function
350+
message: { toObject } as any,
351+
} as any);
352+
return undefined as any;
353+
});
354+
355+
fireEvent.click(getByText('Confirm'));
356+
357+
// wait until the account view is displayed
358+
await waitFor(() => {
359+
expect(req).toBeDefined();
360+
expect(getByText('Account')).toBeInTheDocument();
361+
});
362+
363+
expect(req!.accountKey).toBe(b64(store.accountStore.activeAccount.traderKey));
364+
expect(req!.feeRateSatPerKw).toBe(31250);
365+
expect(req!.relativeExpiry).toBe(2016);
366+
});
317367
});

app/src/components/pool/AccountSection.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
FundNewAccountConfirm,
1313
FundNewAccountForm,
1414
NoAccount,
15+
RenewAccountConfirm,
16+
RenewAccountForm,
1517
} from './account';
1618

1719
const AccountSection: React.FC = () => {
@@ -46,6 +48,12 @@ const AccountSection: React.FC = () => {
4648
case 'close-confirm':
4749
view = <CloseAccountConfirm />;
4850
break;
51+
case 'renew':
52+
view = <RenewAccountForm />;
53+
break;
54+
case 'renew-confirm':
55+
view = <RenewAccountConfirm />;
56+
break;
4957
}
5058

5159
return <Section>{view}</Section>;

app/src/components/pool/account/AccountSummary.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const Styled = {
2929
Actions: styled.div`
3030
margin: 30px auto 10px;
3131
text-align: center;
32+
33+
button {
34+
min-width: auto;
35+
}
3236
`,
3337
ExpiresSoon: styled.p`
3438
font-size: ${props => props.theme.sizes.xs};
@@ -98,14 +102,24 @@ const AccountSummary: React.FC = () => {
98102
{account.expiresSoon && account.stateLabel === 'Open' ? (
99103
<>
100104
<ExpiresSoon>{l('expiresSoon')}</ExpiresSoon>
101-
<Button
102-
danger
103-
ghost
104-
disabled={account.stateLabel !== 'Open'}
105-
onClick={accountSectionView.showCloseAccount}
106-
>
107-
{l('close')}
108-
</Button>
105+
<SummaryItem>
106+
<Button
107+
danger
108+
ghost
109+
disabled={account.stateLabel !== 'Open'}
110+
onClick={accountSectionView.showCloseAccount}
111+
>
112+
{l('close')}
113+
</Button>
114+
<Button
115+
primary
116+
ghost
117+
disabled={account.stateLabel !== 'Open'}
118+
onClick={accountSectionView.showRenewAccount}
119+
>
120+
{l('renewAccount')}
121+
</Button>
122+
</SummaryItem>
109123
</>
110124
) : (
111125
<Button

app/src/components/pool/account/ExpiredAccount.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { observer } from 'mobx-react-lite';
33
import { usePrefixedTranslation } from 'hooks';
44
import { useStore } from 'store';
5-
import { AlertTriangle, Button, HeaderFour } from 'components/base';
5+
import { AlertTriangle, Button, HeaderFour, SummaryItem } from 'components/base';
66
import { styled } from 'components/theme';
77

88
const Styled = {
@@ -25,6 +25,10 @@ const Styled = {
2525
`,
2626
Actions: styled.div`
2727
margin: 30px auto;
28+
29+
button {
30+
min-width: auto;
31+
}
2832
`,
2933
};
3034

@@ -43,9 +47,14 @@ const ExpiredAccount: React.FC = () => {
4347
<Title>{l('title')}</Title>
4448
<div>{l('message')}</div>
4549
<Actions>
46-
<Button primary ghost onClick={accountSectionView.showCloseAccount}>
47-
{l('closeAccount')}
48-
</Button>
50+
<SummaryItem>
51+
<Button danger ghost onClick={accountSectionView.showCloseAccount}>
52+
{l('close')}
53+
</Button>
54+
<Button primary ghost onClick={accountSectionView.showRenewAccount}>
55+
{l('renewAccount')}
56+
</Button>
57+
</SummaryItem>
4958
</Actions>
5059
</Content>
5160
</>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React from 'react';
2+
import { observer } from 'mobx-react-lite';
3+
import { usePrefixedTranslation } from 'hooks';
4+
import { useStore } from 'store';
5+
import { Button, HeaderFour, Small, SummaryItem } from 'components/base';
6+
import BlockTime from 'components/common/BlockTime';
7+
import Unit from 'components/common/Unit';
8+
import { styled } from 'components/theme';
9+
10+
const Styled = {
11+
Summary: styled.div`
12+
margin: 20px 0 30px;
13+
`,
14+
Small: styled(Small)`
15+
color: ${props => props.theme.colors.gray};
16+
`,
17+
SummaryActions: styled(SummaryItem)`
18+
margin: 30px auto;
19+
`,
20+
};
21+
22+
const RenewAccountConfirm: React.FC = () => {
23+
const { l } = usePrefixedTranslation('cmps.pool.account.RenewAccountForm');
24+
25+
const { renewAccountView } = useStore();
26+
27+
const { Summary, Small, SummaryActions } = Styled;
28+
return (
29+
<>
30+
<HeaderFour>{l('renewAccount')}</HeaderFour>
31+
<Summary>
32+
<SummaryItem>
33+
<span>{l('accountBalance')}</span>
34+
<Unit sats={renewAccountView.accountBalance} />
35+
</SummaryItem>
36+
<SummaryItem>
37+
<span>{l('currentExpiry')}</span>
38+
<span className="text-right">
39+
{renewAccountView.currentExpiry} blocks
40+
<br />
41+
<Small>
42+
<BlockTime blocks={renewAccountView.currentExpiry} />
43+
</Small>
44+
</span>
45+
</SummaryItem>
46+
<SummaryItem>
47+
<span>{l('expiryBlocks')}</span>
48+
<span className="text-right">
49+
{renewAccountView.expiryBlocks} blocks
50+
<br />
51+
<Small>
52+
<BlockTime blocks={renewAccountView.expiryBlocks} />
53+
</Small>
54+
</span>
55+
</SummaryItem>
56+
<SummaryItem>
57+
<span>{l('satsPerVbyteLabel')}</span>
58+
<span>{renewAccountView.satsPerVbyte} sats/vByte</span>
59+
</SummaryItem>
60+
<SummaryActions>
61+
<Button ghost borderless onClick={renewAccountView.cancel}>
62+
{l('common.cancel')}
63+
</Button>
64+
<Button
65+
primary
66+
ghost
67+
disabled={!renewAccountView.isValid}
68+
onClick={renewAccountView.renewAccount}
69+
>
70+
{l('common.confirm')}
71+
</Button>
72+
</SummaryActions>
73+
</Summary>
74+
</>
75+
);
76+
};
77+
78+
export default observer(RenewAccountConfirm);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React from 'react';
2+
import { observer } from 'mobx-react-lite';
3+
import { usePrefixedTranslation } from 'hooks';
4+
import { useStore } from 'store';
5+
import { Button, HeaderFour, Small, SummaryItem } from 'components/base';
6+
import BlockTime from 'components/common/BlockTime';
7+
import FormField from 'components/common/FormField';
8+
import FormInputNumber from 'components/common/FormInputNumber';
9+
import Unit from 'components/common/Unit';
10+
import { styled } from 'components/theme';
11+
12+
const Styled = {
13+
Summary: styled.div`
14+
margin: 20px 0 30px;
15+
`,
16+
Small: styled(Small)`
17+
color: ${props => props.theme.colors.gray};
18+
`,
19+
};
20+
21+
const RenewAccountForm: React.FC = () => {
22+
const { l } = usePrefixedTranslation('cmps.pool.account.RenewAccountForm');
23+
const { renewAccountView } = useStore();
24+
25+
const { Summary, Small } = Styled;
26+
return (
27+
<>
28+
<HeaderFour>{l('renewAccount')}</HeaderFour>
29+
<Summary>
30+
<SummaryItem>
31+
<span>{l('accountBalance')}</span>
32+
<Unit sats={renewAccountView.accountBalance} />
33+
</SummaryItem>
34+
<SummaryItem>
35+
<span>{l('currentExpiry')}</span>
36+
<span className="text-right">
37+
{renewAccountView.currentExpiry} blocks
38+
<br />
39+
<Small>
40+
<BlockTime blocks={renewAccountView.currentExpiry} />
41+
</Small>
42+
</span>
43+
</SummaryItem>
44+
</Summary>
45+
<FormField
46+
label={l('expiryBlocks')}
47+
info={<BlockTime blocks={renewAccountView.expiryBlocks} />}
48+
>
49+
<FormInputNumber
50+
label={l('expiryBlocks')}
51+
extra="blocks"
52+
value={renewAccountView.expiryBlocks}
53+
onChange={renewAccountView.setExpiryBlocks}
54+
/>
55+
</FormField>
56+
<FormField label={l(`satsPerVbyteLabel`)}>
57+
<FormInputNumber
58+
label={l(`satsPerVbyteLabel`)}
59+
placeholder={l('satsPerVbytePlaceholder')}
60+
extra="sats/vByte"
61+
value={renewAccountView.satsPerVbyte}
62+
onChange={renewAccountView.setSatsPerVbyte}
63+
/>
64+
</FormField>
65+
<SummaryItem>
66+
<Button ghost borderless onClick={renewAccountView.cancel}>
67+
{l('common.cancel')}
68+
</Button>
69+
<Button
70+
primary
71+
ghost
72+
disabled={!renewAccountView.isValid}
73+
onClick={renewAccountView.confirm}
74+
>
75+
{l('renew')}
76+
</Button>
77+
</SummaryItem>
78+
</>
79+
);
80+
};
81+
82+
export default observer(RenewAccountForm);

app/src/components/pool/account/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export { default as AccountSummary } from './AccountSummary';
22
export { default as CloseAccountForm } from './CloseAccountForm';
33
export { default as CloseAccountConfirm } from './CloseAccountConfirm';
4+
export { default as RenewAccountForm } from './RenewAccountForm';
5+
export { default as RenewAccountConfirm } from './RenewAccountConfirm';
46
export { default as ExpiredAccount } from './ExpiredAccount';
57
export { default as FundNewAccountForm } from './FundNewAccountForm';
68
export { default as FundNewAccountConfirm } from './FundNewAccountConfirm';

app/src/i18n/locales/en-US.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@
100100
"cmps.pool.account.AccountSummary.openOrdersCount": "Open Orders",
101101
"cmps.pool.account.AccountSummary.pendingBalance": "Pending Balance",
102102
"cmps.pool.account.AccountSummary.availableBalance": "Available Balance",
103-
"cmps.pool.account.AccountSummary.expiresSoon": "Orders will no longer be matched once your account is below 144 blocks until expiration. You should close and reopen your account soon.",
103+
"cmps.pool.account.AccountSummary.expiresSoon": "Orders will no longer be matched once your account is below 144 blocks until expiration. You should renew your account soon.",
104104
"cmps.pool.account.AccountSummary.fundAccount": "Fund Account",
105-
"cmps.pool.account.AccountSummary.close": "Close Account",
105+
"cmps.pool.account.AccountSummary.renewAccount": "Renew Account",
106+
"cmps.pool.account.AccountSummary.close": "Close",
106107
"cmps.pool.account.CloseAccountForm.closeAccount": "Close Account",
107108
"cmps.pool.account.CloseAccountForm.walletBalance": "LND Wallet Balance",
108109
"cmps.pool.account.CloseAccountForm.accountBalance": "Pool Account Balance",
@@ -113,10 +114,18 @@
113114
"cmps.pool.account.CloseAccountForm.satsPerVbytePlaceholder": "100",
114115
"cmps.pool.account.CloseAccountForm.close": "Close Account",
115116
"cmps.pool.account.CloseAccountForm.lndWallet": "LND Wallet",
117+
"cmps.pool.account.RenewAccountForm.renewAccount": "Renew Account",
118+
"cmps.pool.account.RenewAccountForm.accountBalance": "Pool Account Balance",
119+
"cmps.pool.account.RenewAccountForm.currentExpiry": "Current Expiration",
120+
"cmps.pool.account.RenewAccountForm.expiryBlocks": "New Expiration",
121+
"cmps.pool.account.RenewAccountForm.satsPerVbyteLabel": "Fee Rate",
122+
"cmps.pool.account.RenewAccountForm.satsPerVbytePlaceholder": "100",
123+
"cmps.pool.account.RenewAccountForm.renew": "Renew",
116124
"cmps.pool.account.ExpiredAccount.account": "Account",
117125
"cmps.pool.account.ExpiredAccount.title": "Your account expired!",
118-
"cmps.pool.account.ExpiredAccount.message": "Please close your account and fund a new one. Any open orders will be cancelled when your account it closed.",
119-
"cmps.pool.account.ExpiredAccount.closeAccount": "Close Account",
126+
"cmps.pool.account.ExpiredAccount.message": "Please renew your account. Any open orders will remain open once the renewal transaction is confirmed.",
127+
"cmps.pool.account.ExpiredAccount.close": "Close",
128+
"cmps.pool.account.ExpiredAccount.renewAccount": "Renew Account",
120129
"cmps.pool.account.FundNewAccountForm.fundAccount": "Fund Account",
121130
"cmps.pool.account.FundNewAccountForm.walletBalance": "LND Wallet Balance",
122131
"cmps.pool.account.FundNewAccountForm.amountLabel": "Amount",

0 commit comments

Comments
 (0)