Skip to content

Commit d576c34

Browse files
authored
feat: add a memo field (#609)
1 parent cb15871 commit d576c34

File tree

31 files changed

+677
-180
lines changed

31 files changed

+677
-180
lines changed
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { BaseError } from '../base';
2+
3+
const ERROR_VALUE = {
4+
MEMO_TOO_LARGE_ERROR: {
5+
status: 1000,
6+
type: 'MEMO_TOO_LARGE_ERROR',
7+
message: 'Memo too large',
8+
},
9+
INSUFFICIENT_NETWORK_FEE: {
10+
status: 1001,
11+
type: 'INSUFFICIENT_NETWORK_FEE',
12+
message: 'Insufficient network fee',
13+
},
14+
};
15+
16+
type ErrorType = keyof typeof ERROR_VALUE;
17+
18+
export class TransactionValidationError extends BaseError {
19+
constructor(errorType: ErrorType) {
20+
super(ERROR_VALUE[errorType]);
21+
Object.setPrototypeOf(this, TransactionValidationError.prototype);
22+
}
23+
}

packages/adena-extension/src/common/utils/string-utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ const isBech32Address = (str: string): boolean => {
4040
return false;
4141
}
4242
};
43+
44+
export function calculateByteSize(str: string): number {
45+
const encoder = new TextEncoder();
46+
const encodedStr = encoder.encode(str);
47+
return encodedStr.length;
48+
}

packages/adena-extension/src/components/molecules/approve-transaction/approve-transaction.spec.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from 'react';
2+
import { GlobalPopupStyle } from '@styles/global-style';
3+
import theme from '@styles/theme';
4+
import { render } from '@testing-library/react';
25
import { RecoilRoot } from 'recoil';
36
import { ThemeProvider } from 'styled-components';
4-
import { render } from '@testing-library/react';
5-
import theme from '@styles/theme';
6-
import { GlobalPopupStyle } from '@styles/global-style';
77
import { ApproveTransaction, ApproveTransactionProps } from '.';
88

99
describe('ApproveTransaction Component', () => {
@@ -13,6 +13,8 @@ describe('ApproveTransaction Component', () => {
1313
loading: true,
1414
logo: '',
1515
title: 'Sign Transaction',
16+
memo: '',
17+
hasMemo: true,
1618
contracts: [
1719
{
1820
type: '/vm.m_call',
@@ -28,6 +30,9 @@ describe('ApproveTransaction Component', () => {
2830
opened: false,
2931
processing: false,
3032
done: false,
33+
changeMemo: () => {
34+
return;
35+
},
3136
onResponse: () => {
3237
return;
3338
},

packages/adena-extension/src/components/molecules/approve-transaction/approve-transaction.styles.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,52 @@ export const ApproveTransactionWrapper = styled.div<{ isErrorNetworkFee: boolean
7878
background-color: ${getTheme('neutral', '_9')};
7979
}
8080
81-
.fee-amount-wrapper {
81+
.memo-wrapper {
8282
width: 100%;
8383
min-height: 48px;
8484
border-radius: 30px;
8585
padding: 10px 18px;
8686
margin-bottom: 8px;
8787
background-color: ${getTheme('neutral', '_9')};
8888
border: 1px solid ${getTheme('neutral', '_8')};
89+
gap: 10px;
90+
${fonts.body2Reg};
91+
92+
span.value {
93+
display: block;
94+
width: 100%;
95+
max-width: 100%;
96+
height: auto;
97+
word-break: break-all;
98+
overflow: hidden;
99+
white-space: nowrap;
100+
text-overflow: ellipsis;
101+
}
102+
103+
input.value {
104+
display: block;
105+
width: 100%;
106+
max-width: 100%;
107+
height: auto;
108+
109+
&::placeholder {
110+
color: ${getTheme('neutral', 'a')};
111+
}
112+
}
113+
114+
&.editable {
115+
border: 1px solid ${getTheme('neutral', '_7')};
116+
}
117+
}
118+
119+
.fee-amount-wrapper {
120+
width: 100%;
121+
min-height: 48px;
122+
border-radius: 30px;
123+
padding: 10px 18px;
124+
margin-bottom: 8px;
125+
background-color: ${getTheme('neutral', '_9')};
126+
border: 1px solid ${getTheme('neutral', '_7')};
89127
${fonts.body2Reg};
90128
${({ isErrorNetworkFee, theme }): string | false =>
91129
isErrorNetworkFee && `border-color: ${theme.red._5};`}

packages/adena-extension/src/components/molecules/approve-transaction/index.tsx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import React from 'react';
1+
import React, { useCallback } from 'react';
22

3-
import { Text, Button } from '@components/atoms';
3+
import { Button, Text } from '@components/atoms';
44
import {
5-
BottomFixedButtonGroup,
65
ApproveInjectionLoading,
76
ApproveLoading,
7+
BottomFixedButtonGroup,
88
} from '@components/molecules';
99

10-
import DefaultFavicon from '@assets/favicon-default.svg';
1110
import IconArraowDown from '@assets/arrowS-down-gray.svg';
1211
import IconArraowUp from '@assets/arrowS-up-gray.svg';
12+
import DefaultFavicon from '@assets/favicon-default.svg';
1313
import { ApproveTransactionWrapper } from './approve-transaction.styles';
1414

1515
export interface ApproveTransactionProps {
@@ -22,6 +22,8 @@ export interface ApproveTransactionProps {
2222
function: string;
2323
value: string;
2424
}[];
25+
memo: string;
26+
hasMemo: boolean;
2527
isErrorNetworkFee?: boolean;
2628
networkFee: {
2729
amount: string;
@@ -31,6 +33,7 @@ export interface ApproveTransactionProps {
3133
opened: boolean;
3234
processing: boolean;
3335
done: boolean;
36+
changeMemo: (memo: string) => void;
3437
onToggleTransactionData: (opened: boolean) => void;
3538
onResponse: () => void;
3639
onTimeout: () => void;
@@ -44,18 +47,33 @@ export const ApproveTransaction: React.FC<ApproveTransactionProps> = ({
4447
logo,
4548
domain,
4649
contracts,
50+
memo,
51+
hasMemo,
4752
isErrorNetworkFee,
4853
networkFee,
4954
transactionData,
5055
opened,
5156
processing,
5257
done,
58+
changeMemo,
5359
onToggleTransactionData,
5460
onResponse,
5561
onTimeout,
5662
onClickConfirm,
5763
onClickCancel,
5864
}) => {
65+
const onChangeMemo = useCallback(
66+
(e: React.ChangeEvent<HTMLInputElement>) => {
67+
if (hasMemo) {
68+
return;
69+
}
70+
71+
const value = e.target.value;
72+
changeMemo(value);
73+
},
74+
[hasMemo, changeMemo],
75+
);
76+
5977
if (loading) {
6078
return <ApproveLoading rightButtonText='Approve' />;
6179
}
@@ -91,14 +109,35 @@ export const ApproveTransaction: React.FC<ApproveTransactionProps> = ({
91109
</div>
92110
))}
93111

112+
<div className={hasMemo ? 'memo-wrapper row' : 'memo-wrapper editable row'}>
113+
<span className='key'>Memo:</span>
114+
{hasMemo ? (
115+
<span className={'value'}>{memo}</span>
116+
) : (
117+
<input
118+
type='text'
119+
className={'value'}
120+
value={memo}
121+
onChange={onChangeMemo}
122+
autoComplete='off'
123+
placeholder='(Optional)'
124+
/>
125+
)}
126+
</div>
127+
94128
<div className='fee-amount-wrapper row'>
95129
<span className='key'>Network Fee:</span>
96130
<span className='value'>{`${networkFee.amount} ${networkFee.denom}`}</span>
97131
</div>
98132
{isErrorNetworkFee && <span className='error-message'>Insufficient network fee</span>}
99133

100134
<div className='transaction-data-wrapper'>
101-
<Button hierarchy='custom' bgColor='transparent' className='visible-button' onClick={(): void => onToggleTransactionData(!opened)}>
135+
<Button
136+
hierarchy='custom'
137+
bgColor='transparent'
138+
className='visible-button'
139+
onClick={(): void => onToggleTransactionData(!opened)}
140+
>
102141
{opened ? (
103142
<>
104143
<>Hide Transaction Data</>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import { GlobalPopupStyle } from '@styles/global-style';
3+
import theme from '@styles/theme';
4+
import { render } from '@testing-library/react';
5+
import { RecoilRoot } from 'recoil';
6+
import { ThemeProvider } from 'styled-components';
7+
import MemoInput, { MemoInputProps } from './memo-input';
8+
9+
describe('MemoInput Component', () => {
10+
it('MemoInput render', () => {
11+
const args: MemoInputProps = {
12+
memo: '132123123123',
13+
onChangeMemo: () => {
14+
return;
15+
},
16+
};
17+
18+
render(
19+
<RecoilRoot>
20+
<GlobalPopupStyle />
21+
<ThemeProvider theme={theme}>
22+
<MemoInput {...args} />
23+
</ThemeProvider>
24+
</RecoilRoot>,
25+
);
26+
});
27+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { action } from '@storybook/addon-actions';
2+
import { Meta, StoryObj } from '@storybook/react';
3+
import MemoInput, { type MemoInputProps } from './memo-input';
4+
5+
export default {
6+
title: 'components/transfer/MemoInput',
7+
component: MemoInput,
8+
} as Meta<typeof MemoInput>;
9+
10+
export const Default: StoryObj<MemoInputProps> = {
11+
args: {
12+
memo: '132123123123',
13+
onChangeMemo: action('change memo'),
14+
},
15+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import mixins from '@styles/mixins';
2+
import { fonts, getTheme } from '@styles/theme';
3+
import styled from 'styled-components';
4+
5+
export const MemoInputWrapper = styled.div`
6+
${mixins.flex({ align: 'normal', justify: 'normal' })};
7+
width: 100%;
8+
height: auto;
9+
gap: 12px;
10+
11+
.memo-input {
12+
${mixins.flex({ direction: 'row', justify: 'normal' })};
13+
width: 100%;
14+
height: auto;
15+
padding: 12px 16px;
16+
background-color: ${getTheme('neutral', '_9')};
17+
border: 1px solid ${getTheme('neutral', '_7')};
18+
border-radius: 30px;
19+
resize: none;
20+
21+
${fonts.body2Reg};
22+
23+
&.error {
24+
border-color: ${getTheme('red', '_5')};
25+
}
26+
}
27+
28+
.warning-wrapper {
29+
${mixins.flex({ direction: 'row', justify: 'normal', align: 'flex-start' })};
30+
width: 100%;
31+
border-radius: 8px;
32+
border: 1px solid ${getTheme('red', '_8')}0d;
33+
background-color: ${getTheme('red', '_8')}1a;
34+
padding: 12px 20px;
35+
gap: 8px;
36+
37+
.icon-warning {
38+
width: 14px;
39+
padding: 4px 0;
40+
}
41+
42+
.warning-text {
43+
width: 100%;
44+
${fonts.body2Reg};
45+
color: ${getTheme('red', '_8')};
46+
}
47+
}
48+
49+
.error-message {
50+
position: relative;
51+
margin-top: -10px;
52+
padding: 0 16px;
53+
${fonts.captionReg};
54+
color: ${getTheme('red', '_5')};
55+
}
56+
`;

0 commit comments

Comments
 (0)