Skip to content
This repository was archived by the owner on May 13, 2024. It is now read-only.

Commit 3ae0058

Browse files
authored
Merge pull request #215 from hubert-deriv/manage_token_design2
2 parents 043cdc8 + 1d053e3 commit 3ae0058

File tree

7 files changed

+200
-108
lines changed

7 files changed

+200
-108
lines changed

src/features/dashboard/components/ApiTokenCard/api-token.card.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
flex-direction: column;
99
gap: rem(1);
1010
border: 1px solid var(--ifm-color-emphasis-400);
11-
border-radius: rem(0.4);
11+
border-radius: rem(2);
1212
padding: rem(1.6);
1313

1414
label {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
3+
type TCustomErrors = {
4+
token_name_exists: boolean;
5+
tokens_limit_reached: boolean;
6+
input_value: string;
7+
};
8+
9+
const CustomErrors = ({ token_name_exists, tokens_limit_reached, input_value }: TCustomErrors) => {
10+
if (token_name_exists) {
11+
return (
12+
<div className='error-message'>
13+
<p>That name is taken. Choose another.</p>
14+
</div>
15+
);
16+
}
17+
if (tokens_limit_reached && input_value !== '') {
18+
return (
19+
<div className='error-message'>
20+
<p>You&apos;ve created the maximum number of tokens.</p>
21+
</div>
22+
);
23+
}
24+
25+
return <></>;
26+
};
27+
28+
export default CustomErrors;

src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Text, Button } from '@deriv/ui';
33
import styles from '../api-token.form.module.scss';
44
import useApiToken from '@site/src/hooks/useApiToken';
55
import { FieldErrorsImpl, UseFormRegisterReturn } from 'react-hook-form';
6+
import CustomErrors from './CustomErrors';
67
import TokenCreationDialogSuccess from '../../Dialogs/TokenCreationDialogSuccess';
78

89
type TCreateTokenField = {
@@ -52,9 +53,13 @@ const CreateTokenField = ({
5253
return token_names;
5354
}, [tokens]);
5455

55-
const token_name_exists = getTokenNames.includes(input_value.toLowerCase());
56-
const disable_button = token_name_exists || Object.keys(errors).length > 0 || input_value === '';
57-
const error_border_active = token_name_exists || errors.name;
56+
const tokens_limit_reached = tokens.length === 30 && Object.keys(errors).length === 0;
57+
const token_name_exists =
58+
getTokenNames.includes(input_value.toLowerCase()) && Object.keys(errors).length === 0;
59+
const has_custom_errors = token_name_exists || (tokens_limit_reached && input_value !== '');
60+
const disable_button =
61+
token_name_exists || Object.keys(errors).length > 0 || input_value === '' || has_custom_errors;
62+
const error_border_active = token_name_exists || errors.name || has_custom_errors;
5863

5964
useEffect(() => {
6065
if (error_border_active) {
@@ -79,7 +84,7 @@ const CreateTokenField = ({
7984
type='text'
8085
name='name'
8186
{...register}
82-
placeholder='Token name'
87+
placeholder=' '
8388
/>
8489
<Button disabled={disable_button} type='submit'>
8590
Create
@@ -91,11 +96,11 @@ const CreateTokenField = ({
9196
{errors.name.message}
9297
</Text>
9398
)}
94-
{token_name_exists && (
95-
<div className='error-message'>
96-
<p>That name is taken. Choose another.</p>
97-
</div>
98-
)}
99+
<CustomErrors
100+
token_name_exists={token_name_exists}
101+
tokens_limit_reached={tokens_limit_reached}
102+
input_value={input_value}
103+
/>
99104
</React.Fragment>
100105
);
101106
};

src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx

Lines changed: 121 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,13 @@ import userEvent from '@testing-library/user-event';
44
import ApiTokenForm from '../api-token.form';
55
import useCreateToken from '../../../hooks/useCreateToken';
66
import useApiToken from '@site/src/hooks/useApiToken';
7-
import TokenNameRestrictions from '../../TokenNameRestrictions/TokenNameRestrictions';
87

98
jest.mock('@site/src/hooks/useApiToken');
109

1110
const mockUseApiToken = useApiToken as jest.MockedFunction<
1211
() => Partial<ReturnType<typeof useApiToken>>
1312
>;
1413

15-
mockUseApiToken.mockImplementation(() => ({
16-
tokens: [
17-
{
18-
display_name: 'testtoken1',
19-
last_used: '',
20-
scopes: ['read', 'trade', 'payments', 'admin'],
21-
token: 'asdf1234',
22-
valid_for_ip: '',
23-
},
24-
{
25-
display_name: 'testtoken2',
26-
last_used: '',
27-
scopes: ['read', 'trade', 'payments', 'admin'],
28-
token: 'asdf1235',
29-
valid_for_ip: '',
30-
},
31-
],
32-
}));
33-
3414
jest.mock('@site/src/features/dashboard/hooks/useCreateToken');
3515

3616
const mockUseCreateToken = useCreateToken as jest.MockedFunction<typeof useCreateToken>;
@@ -76,110 +56,156 @@ const scopes = [
7656
label: 'Admin',
7757
},
7858
];
79-
const preventDefault = jest.fn();
8059

8160
describe('Home Page', () => {
82-
beforeEach(() => {
83-
render(<ApiTokenForm />);
84-
});
85-
86-
afterEach(() => {
87-
cleanup();
88-
jest.clearAllMocks();
89-
});
61+
describe('General tests', () => {
62+
beforeEach(() => {
63+
mockUseApiToken.mockImplementation(() => ({
64+
tokens: [
65+
{
66+
display_name: 'testtoken1',
67+
last_used: '',
68+
scopes: ['read', 'trade', 'payments', 'admin'],
69+
token: 'asdf1234',
70+
valid_for_ip: '',
71+
},
72+
{
73+
display_name: 'testtoken2',
74+
last_used: '',
75+
scopes: ['read', 'trade', 'payments', 'admin'],
76+
token: 'asdf1235',
77+
valid_for_ip: '',
78+
},
79+
],
80+
}));
81+
82+
render(<ApiTokenForm />);
83+
});
9084

91-
it('Should render first step title', () => {
92-
const firstStep = screen.getByTestId('first-step-title');
93-
expect(firstStep).toHaveTextContent(/Select scopes based on the access you need./i);
94-
});
85+
afterEach(() => {
86+
cleanup();
87+
jest.clearAllMocks();
88+
});
9589

96-
it('Should render all of scopes checkbox cards', () => {
97-
scopes.forEach((item) => {
98-
const apiTokenCard = screen.getByTestId(`api-token-card-${item.name}`);
99-
expect(apiTokenCard).toBeInTheDocument();
90+
it('Should render first step title', () => {
91+
const firstStep = screen.getByTestId('first-step-title');
92+
expect(firstStep).toHaveTextContent(/Select scopes based on the access you need./i);
10093
});
101-
});
10294

103-
it('Should render second step title', () => {
104-
const secondStep = screen.getByTestId('second-step-title');
105-
expect(secondStep).toHaveTextContent(
106-
/Name your token and click on Create to generate your token./i,
107-
);
108-
});
95+
it('Should render all of scopes checkbox cards', () => {
96+
scopes.forEach((item) => {
97+
const apiTokenCard = screen.getByTestId(`api-token-card-${item.name}`);
98+
expect(apiTokenCard).toBeInTheDocument();
99+
});
100+
});
109101

110-
it('Should check the checkbox when clicked on api token card', async () => {
111-
const adminTokenCard = screen.getByTestId('api-token-card-admin');
112-
const withinAdminTokenCard = within(adminTokenCard);
113-
const adminCheckbox = withinAdminTokenCard.getByRole<HTMLInputElement>('checkbox');
102+
it('Should render second step title', () => {
103+
const secondStep = screen.getByTestId('second-step-title');
104+
expect(secondStep).toHaveTextContent(
105+
/Name your token and click on Create to generate your token./i,
106+
);
107+
});
114108

115-
expect(adminCheckbox.checked).toBeFalsy();
109+
it('Should check the checkbox when clicked on api token card', async () => {
110+
const adminTokenCard = screen.getByTestId('api-token-card-admin');
111+
const withinAdminTokenCard = within(adminTokenCard);
112+
const adminCheckbox = withinAdminTokenCard.getByRole<HTMLInputElement>('checkbox');
116113

117-
await userEvent.click(adminTokenCard);
114+
expect(adminCheckbox.checked).toBeFalsy();
118115

119-
expect(adminCheckbox.checked).toBeTruthy();
120-
});
116+
await userEvent.click(adminTokenCard);
121117

122-
it('Should create token on form submit', async () => {
123-
const nameInput = screen.getByRole('textbox');
118+
expect(adminCheckbox.checked).toBeTruthy();
119+
});
124120

125-
await userEvent.type(nameInput, 'test create token');
121+
it('Should create token on form submit', async () => {
122+
const nameInput = screen.getByRole('textbox');
126123

127-
const submitButton = screen.getByRole('button', { name: /Create/i });
128-
await userEvent.click(submitButton);
124+
await userEvent.type(nameInput, 'test create token');
129125

130-
expect(mockCreateToken).toHaveBeenCalledTimes(1);
131-
expect(mockCreateToken).toHaveBeenCalledWith('test create token', []);
132-
});
126+
const submitButton = screen.getByRole('button', { name: /Create/i });
127+
await userEvent.click(submitButton);
133128

134-
it('Should not be able to create a token if name already exists', async () => {
135-
const nameInput = screen.getByRole('textbox');
129+
expect(mockCreateToken).toHaveBeenCalledTimes(1);
130+
expect(mockCreateToken).toHaveBeenCalledWith('test create token', []);
131+
});
136132

137-
await userEvent.type(nameInput, 'testtoken1');
133+
it('Should not be able to create a token if name already exists', async () => {
134+
const nameInput = screen.getByRole('textbox');
138135

139-
const error = screen.getByText(/That name is taken. Choose another./i);
140-
expect(error).toBeVisible;
141-
});
136+
await userEvent.type(nameInput, 'testtoken1');
142137

143-
it('should hide restrictions if error is present', async () => {
144-
const nameInput = screen.getByRole('textbox');
145-
const restrictions = screen.getByRole('list');
146-
expect(restrictions).toBeVisible();
147-
await userEvent.type(nameInput, 'testtoken1');
148-
expect(restrictions).not.toBeVisible();
149-
});
138+
const error = screen.getByText(/That name is taken. Choose another./i);
139+
expect(error).toBeVisible;
140+
});
150141

151-
it('Should not create token when name input is empty', async () => {
152-
const nameInput = screen.getByRole('textbox');
142+
it('should hide restrictions if error is present', async () => {
143+
const nameInput = screen.getByRole('textbox');
144+
const restrictions = screen.getByRole('list');
145+
expect(restrictions).toBeVisible();
146+
await userEvent.type(nameInput, 'testtoken1');
147+
expect(restrictions).not.toBeVisible();
148+
});
153149

154-
await userEvent.clear(nameInput);
150+
it('Should not create token when name input is empty', async () => {
151+
const nameInput = screen.getByRole('textbox');
155152

156-
await userEvent.click(nameInput);
153+
await userEvent.clear(nameInput);
157154

158-
expect(mockCreateToken).not.toHaveBeenCalled();
159-
});
155+
await userEvent.click(nameInput);
160156

161-
it('Should open success dialog when token is created ', async () => {
162-
const nameInput = screen.getByRole('textbox');
157+
expect(mockCreateToken).not.toHaveBeenCalled();
158+
});
159+
it('Should open success dialog when token is created ', async () => {
160+
const nameInput = screen.getByRole('textbox');
163161

164-
await userEvent.type(nameInput, 'test create token');
162+
await userEvent.type(nameInput, 'test create token');
165163

166-
const submitButton = screen.getByRole('button', { name: /Create/i });
167-
await userEvent.click(submitButton);
164+
const submitButton = screen.getByRole('button', { name: /Create/i });
165+
await userEvent.click(submitButton);
168166

169-
const modal = await screen.getByText('Your API token is ready to be used.');
170-
expect(modal).toBeVisible();
171-
});
167+
const modal = screen.getByText('Your API token is ready to be used.');
168+
expect(modal).toBeVisible();
169+
});
172170

173-
it('Should have create button disabled in case of empty input or error message', async () => {
174-
const submitButton = screen.getByRole('button', { name: /Create/i });
175-
expect(submitButton).toBeDisabled();
171+
it('Should have create button disabled in case of empty input or error message', async () => {
172+
const submitButton = screen.getByRole('button', { name: /Create/i });
173+
expect(submitButton).toBeDisabled();
176174

177-
const nameInput = screen.getByRole('textbox');
175+
const nameInput = screen.getByRole('textbox');
178176

179-
await userEvent.type(nameInput, 'token-text');
180-
expect(submitButton).toBeDisabled();
177+
await userEvent.type(nameInput, 'token-text');
178+
expect(submitButton).toBeDisabled();
181179

182-
await userEvent.clear(nameInput);
183-
expect(submitButton).toBeDisabled();
180+
await userEvent.clear(nameInput);
181+
expect(submitButton).toBeDisabled();
182+
});
183+
});
184+
describe('Token limit', () => {
185+
const createMaxTokens = () => {
186+
const token_array = [];
187+
for (let i = 0; i < 30; i++) {
188+
token_array.push({
189+
display_name: `testtoken${i}`,
190+
last_used: '',
191+
scopes: ['read', 'trade', 'payments', 'admin'],
192+
token: 'asdf1234',
193+
valid_for_ip: '',
194+
});
195+
}
196+
return token_array;
197+
};
198+
199+
it('Should show an error when the user tries to create more than 30 tokens', async () => {
200+
mockUseApiToken.mockImplementation(() => ({ tokens: createMaxTokens() }));
201+
render(<ApiTokenForm />);
202+
203+
const nameInput = screen.getByRole('textbox');
204+
205+
await userEvent.type(nameInput, 'asdf');
206+
207+
const error = screen.getByText(/created the maximum number of tokens/i);
208+
expect(error).toBeVisible();
209+
});
184210
});
185211
});

0 commit comments

Comments
 (0)