From 645764de891a4fb169a6931188c795ae5ba02ae2 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Mon, 17 Jul 2023 15:27:36 +0200 Subject: [PATCH 1/8] chore: fixing merge conflict --- .../ApiTokenForm/CreateTokenField/index.tsx | 38 ++++++++++++++++++- .../ApiTokenForm/api-token.form.module.scss | 19 +++++++++- .../ApiTokenForm/api-token.form.tsx | 2 + src/styles/index.scss | 4 ++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx index 1cd53547..23d2f094 100644 --- a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx +++ b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx @@ -45,7 +45,10 @@ const CreateTokenField = ({ return token_names; }, [tokens]); - const token_name_exists = getTokenNames.includes(input_value.toLowerCase()); + const tokens_limit_reached = tokens.length === 30 && Object.keys(errors).length === 0; + const token_name_exists = + getTokenNames.includes(input_value.toLowerCase()) && Object.keys(errors).length === 0; + const has_no_errors = Object.values(errors).length === 0; const disable_button = token_name_exists || Object.keys(errors).length > 0 || input_value === ''; const error_border_active = token_name_exists || errors.name; @@ -67,11 +70,14 @@ const CreateTokenField = ({ type='text' name='name' {...register} - placeholder='Token name' + placeholder=' ' /> + {errors && errors.name && ( @@ -83,6 +89,34 @@ const CreateTokenField = ({

That name is taken. Choose another.

)} + {tokens_limit_reached && input_value !== '' && ( +
+

You've reached 30 tokens creation limit.

+
+ )} + {has_no_errors && ( +
+
    +
  • + Only alphanumeric characters with spaces and underscores are allowed. +
  • +
  • + The name must be between 2 to 32 characters. +
  • +
  • + Duplicate token names aren't allowed. +
  • +
  • + + The name cannot contain “Binary”, “Deriv”, or similar words. + +
  • +
  • + You can create up to 30 tokens for this account. +
  • +
+
+ )} ); }; diff --git a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss index 4571be41..46098ac4 100644 --- a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss +++ b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss @@ -51,7 +51,7 @@ form { .customTextInput { align-items: center; border: 1px solid var(--colors-greyLight400); - border-radius: 4px; + border-radius: rem(1.6); display: flex; position: relative; box-sizing: border-box; @@ -66,6 +66,8 @@ form { border-top-left-radius: 0; border-bottom-left-radius: 0; height: rem(3); + border-top-right-radius: rem(1.6); + border-bottom-right-radius: rem(1.6); } label { position: absolute; @@ -92,16 +94,22 @@ form { background-color: var(--ifm-color-emphasis-0); padding: 0 rem(0.4); transform: translateY(rem(-2)) scale(0.75); + &.tokenInputLabel { + color: var(--smoke); + } } &:focus { outline-color: unset; outline: unset; - border-radius: rem(0.3); + border-radius: rem(1.6); & ~ label { color: var(--colors-blue400); background-color: var(--ifm-color-emphasis-0); padding: 0 rem(0.4); transform: translateY(rem(-2)) scale(0.75); + &.tokenInputLabel { + color: var(--smoke); + } } } &::placeholder { @@ -121,7 +129,14 @@ form { } .helperText { + line-height: 2.2; font-size: rem(1.4); padding-left: rem(1); color: var(--colors-greyLight600); + li { + font-size: rem(1); + span { + font-size: rem(1.4); + } + } } diff --git a/src/features/dashboard/components/ApiTokenForm/api-token.form.tsx b/src/features/dashboard/components/ApiTokenForm/api-token.form.tsx index 060f4868..6fac86be 100644 --- a/src/features/dashboard/components/ApiTokenForm/api-token.form.tsx +++ b/src/features/dashboard/components/ApiTokenForm/api-token.form.tsx @@ -9,6 +9,7 @@ import useCreateToken from '@site/src/features/dashboard/hooks/useCreateToken'; import * as yup from 'yup'; import styles from './api-token.form.module.scss'; import CreateTokenField from './CreateTokenField'; +import useApiToken from '@site/src/hooks/useApiToken'; const schema = yup .object({ @@ -80,6 +81,7 @@ const scopes: TScope[] = [ const ApiTokenForm = (props: HTMLAttributes) => { const { createToken, isCreatingToken } = useCreateToken(); const [form_is_cleared, setFormIsCleared] = useState(false); + const { tokens } = useApiToken(); const { handleSubmit, diff --git a/src/styles/index.scss b/src/styles/index.scss index 89b6d12a..728b099c 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -34,6 +34,7 @@ --schema-array: #ff8fc8; --schema-number: #acb2ff; --schema-integer: #f8c272; + --smoke: #414652; } /* For readability concerns, you should choose a lighter palette in dark mode. */ @@ -97,6 +98,9 @@ h6 { &:focus { outline: var(--colors-coral500) !important; } + label { + color: var(--colors-coral500) !important; + } } /* reset */ From 6293299ccc84a01d9057e50893d78d20470d9af9 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Tue, 18 Jul 2023 10:09:16 +0200 Subject: [PATCH 2/8] chore: fixing some bugs --- .../ApiTokenForm/CreateTokenField/index.tsx | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx index 23d2f094..3785314a 100644 --- a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx +++ b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx @@ -48,9 +48,30 @@ const CreateTokenField = ({ const tokens_limit_reached = tokens.length === 30 && Object.keys(errors).length === 0; const token_name_exists = getTokenNames.includes(input_value.toLowerCase()) && Object.keys(errors).length === 0; - const has_no_errors = Object.values(errors).length === 0; - const disable_button = token_name_exists || Object.keys(errors).length > 0 || input_value === ''; - const error_border_active = token_name_exists || errors.name; + const has_custom_errors = token_name_exists || (tokens_limit_reached && input_value !== ''); + const has_no_errors = Object.values(errors).length === 0 && !has_custom_errors; + const disable_button = + token_name_exists || Object.keys(errors).length > 0 || input_value === '' || has_custom_errors; + const error_border_active = token_name_exists || errors.name || has_custom_errors; + + const CustomErrors = () => { + if (token_name_exists) { + return ( +
+

That name is taken. Choose another.

+
+ ); + } + if (tokens_limit_reached && input_value !== '') { + return ( +
+

You've reached 30 tokens creation limit.

+
+ ); + } + + return <>; + }; return ( @@ -84,16 +105,7 @@ const CreateTokenField = ({ {errors.name.message}
)} - {token_name_exists && ( -
-

That name is taken. Choose another.

-
- )} - {tokens_limit_reached && input_value !== '' && ( -
-

You've reached 30 tokens creation limit.

-
- )} + {has_no_errors && (
    From 8efb90832e1057a0c224367de92c88c6490ebe15 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Tue, 18 Jul 2023 13:58:27 +0200 Subject: [PATCH 3/8] chore: fixing some bugs --- .../ApiTokenForm/api-token.form.module.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss index 46098ac4..3f1976d1 100644 --- a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss +++ b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss @@ -77,6 +77,10 @@ form { transform-origin: top left; transition: all 0.25s ease; white-space: nowrap; + width: calc(100% - 100px); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } input[type='text'], input[type='number'] { @@ -94,6 +98,10 @@ form { background-color: var(--ifm-color-emphasis-0); padding: 0 rem(0.4); transform: translateY(rem(-2)) scale(0.75); + width: unset; + @media screen and (min-width: 320px) and (max-width: 425px) { + font-size: rem(1.4); + } &.tokenInputLabel { color: var(--smoke); } @@ -107,6 +115,10 @@ form { background-color: var(--ifm-color-emphasis-0); padding: 0 rem(0.4); transform: translateY(rem(-2)) scale(0.75); + width: unset; + @media screen and (min-width: 320px) and (max-width: 425px) { + font-size: rem(1.4); + } &.tokenInputLabel { color: var(--smoke); } From 765011a2f93af7ae0a9cadaf37036625399d3fae Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Tue, 18 Jul 2023 15:46:45 +0200 Subject: [PATCH 4/8] chore: adding test coverage --- .../__tests__/api-token.form.test.tsx | 165 ++++++++++-------- 1 file changed, 97 insertions(+), 68 deletions(-) diff --git a/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx b/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx index 16bb238f..fcd3cecc 100644 --- a/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx +++ b/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx @@ -11,25 +11,6 @@ const mockUseApiToken = useApiToken as jest.MockedFunction< () => Partial> >; -mockUseApiToken.mockImplementation(() => ({ - tokens: [ - { - display_name: 'testtoken1', - last_used: '', - scopes: ['read', 'trade', 'payments', 'admin'], - token: 'asdf1234', - valid_for_ip: '', - }, - { - display_name: 'testtoken2', - last_used: '', - scopes: ['read', 'trade', 'payments', 'admin'], - token: 'asdf1235', - valid_for_ip: '', - }, - ], -})); - jest.mock('@site/src/features/dashboard/hooks/useCreateToken'); const mockUseCreateToken = useCreateToken as jest.MockedFunction; @@ -77,74 +58,122 @@ const scopes = [ ]; describe('Home Page', () => { - beforeEach(() => { - render(); - }); + describe('General tests', () => { + beforeEach(() => { + mockUseApiToken.mockImplementation(() => ({ + tokens: [ + { + display_name: 'testtoken1', + last_used: '', + scopes: ['read', 'trade', 'payments', 'admin'], + token: 'asdf1234', + valid_for_ip: '', + }, + { + display_name: 'testtoken2', + last_used: '', + scopes: ['read', 'trade', 'payments', 'admin'], + token: 'asdf1235', + valid_for_ip: '', + }, + ], + })); + + render(); + }); - afterEach(() => { - cleanup(); - jest.clearAllMocks(); - }); + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); - it('Should render first step title', () => { - const firstStep = screen.getByTestId('first-step-title'); - expect(firstStep).toHaveTextContent(/Select scopes based on the access you need./i); - }); + it('Should render first step title', () => { + const firstStep = screen.getByTestId('first-step-title'); + expect(firstStep).toHaveTextContent(/Select scopes based on the access you need./i); + }); - it('Should render all of scopes checkbox cards', () => { - scopes.forEach((item) => { - const apiTokenCard = screen.getByTestId(`api-token-card-${item.name}`); - expect(apiTokenCard).toBeInTheDocument(); + it('Should render all of scopes checkbox cards', () => { + scopes.forEach((item) => { + const apiTokenCard = screen.getByTestId(`api-token-card-${item.name}`); + expect(apiTokenCard).toBeInTheDocument(); + }); }); - }); - it('Should render second step title', () => { - const secondStep = screen.getByTestId('second-step-title'); - expect(secondStep).toHaveTextContent( - /Name your token and click on Create to generate your token./i, - ); - }); + it('Should render second step title', () => { + const secondStep = screen.getByTestId('second-step-title'); + expect(secondStep).toHaveTextContent( + /Name your token and click on Create to generate your token./i, + ); + }); - it('Should check the checkbox when clicked on api token card', async () => { - const adminTokenCard = screen.getByTestId('api-token-card-admin'); - const withinAdminTokenCard = within(adminTokenCard); - const adminCheckbox = withinAdminTokenCard.getByRole('checkbox'); + it('Should check the checkbox when clicked on api token card', async () => { + const adminTokenCard = screen.getByTestId('api-token-card-admin'); + const withinAdminTokenCard = within(adminTokenCard); + const adminCheckbox = withinAdminTokenCard.getByRole('checkbox'); - expect(adminCheckbox.checked).toBeFalsy(); + expect(adminCheckbox.checked).toBeFalsy(); - await userEvent.click(adminTokenCard); + await userEvent.click(adminTokenCard); - expect(adminCheckbox.checked).toBeTruthy(); - }); + expect(adminCheckbox.checked).toBeTruthy(); + }); - it('Should create token on form submit', async () => { - const nameInput = screen.getByRole('textbox'); + it('Should create token on form submit', async () => { + const nameInput = screen.getByRole('textbox'); - await userEvent.type(nameInput, 'test create token'); + await userEvent.type(nameInput, 'test create token'); - const submitButton = screen.getByRole('button', { name: /Create/i }); - await userEvent.click(submitButton); + const submitButton = screen.getByRole('button', { name: /Create/i }); + await userEvent.click(submitButton); - expect(mockCreateToken).toHaveBeenCalledTimes(1); - expect(mockCreateToken).toHaveBeenCalledWith('test create token', []); - }); + expect(mockCreateToken).toHaveBeenCalledTimes(1); + expect(mockCreateToken).toHaveBeenCalledWith('test create token', []); + }); - it('Should not be able to create a token if name already exists', async () => { - const nameInput = screen.getByRole('textbox'); + it('Should not be able to create a token if name already exists', async () => { + const nameInput = screen.getByRole('textbox'); - await userEvent.type(nameInput, 'testtoken1'); + await userEvent.type(nameInput, 'testtoken1'); - const error = screen.getByText(/That name is taken. Choose another./i); - expect(error).toBeVisible; - }); + const error = screen.getByText(/That name is taken. Choose another./i); + expect(error).toBeVisible; + }); - it('Should not create token when name input is empty', async () => { - const nameInput = screen.getByRole('textbox'); + it('Should not create token when name input is empty', async () => { + const nameInput = screen.getByRole('textbox'); - await userEvent.clear(nameInput); + await userEvent.clear(nameInput); - await userEvent.click(nameInput); + await userEvent.click(nameInput); - expect(mockCreateToken).not.toHaveBeenCalled(); + expect(mockCreateToken).not.toHaveBeenCalled(); + }); + }); + describe('Token limit', () => { + const createMaxTokens = () => { + const token_array = []; + for (let i = 0; i < 30; i++) { + token_array.push({ + display_name: `testtoken${i}`, + last_used: '', + scopes: ['read', 'trade', 'payments', 'admin'], + token: 'asdf1234', + valid_for_ip: '', + }); + } + return token_array; + }; + + it('Should show an error when the user tries to create more than 30 tokens', async () => { + mockUseApiToken.mockImplementation(() => ({ tokens: createMaxTokens() })); + render(); + + const nameInput = screen.getByRole('textbox'); + + await userEvent.type(nameInput, 'asdf'); + + const error = screen.getByText(/reached 30 tokens creation limit/i); + expect(error).toBeVisible(); + }); }); }); From 0eaafcc2d3675febba2984d60305437fc85236c9 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Tue, 25 Jul 2023 16:31:36 +0200 Subject: [PATCH 5/8] chore: refactoring code --- .../CreateTokenField/CustomErrors/index.tsx | 28 +++++++++++ .../ApiTokenForm/CreateTokenField/index.tsx | 50 +++---------------- 2 files changed, 34 insertions(+), 44 deletions(-) create mode 100644 src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx diff --git a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx new file mode 100644 index 00000000..34fe04e7 --- /dev/null +++ b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +type TCustomErrors = { + token_name_exists: boolean; + tokens_limit_reached: boolean; + input_value: string; +}; + +const CustomErrors = ({ token_name_exists, tokens_limit_reached, input_value }: TCustomErrors) => { + if (token_name_exists) { + return ( +
    +

    That name is taken. Choose another.

    +
    + ); + } + if (tokens_limit_reached && input_value !== '') { + return ( +
    +

    You've reached 30 tokens creation limit.

    +
    + ); + } + + return <>; +}; + +export default CustomErrors; diff --git a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx index 3785314a..3f3f6ef0 100644 --- a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx +++ b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/index.tsx @@ -3,6 +3,7 @@ import { Text, Button } from '@deriv/ui'; import styles from '../api-token.form.module.scss'; import useApiToken from '@site/src/hooks/useApiToken'; import { FieldErrorsImpl, UseFormRegisterReturn } from 'react-hook-form'; +import CustomErrors from './CustomErrors'; type TCreateTokenField = { register: UseFormRegisterReturn; @@ -49,30 +50,10 @@ const CreateTokenField = ({ const token_name_exists = getTokenNames.includes(input_value.toLowerCase()) && Object.keys(errors).length === 0; const has_custom_errors = token_name_exists || (tokens_limit_reached && input_value !== ''); - const has_no_errors = Object.values(errors).length === 0 && !has_custom_errors; const disable_button = token_name_exists || Object.keys(errors).length > 0 || input_value === '' || has_custom_errors; const error_border_active = token_name_exists || errors.name || has_custom_errors; - const CustomErrors = () => { - if (token_name_exists) { - return ( -
    -

    That name is taken. Choose another.

    -
    - ); - } - if (tokens_limit_reached && input_value !== '') { - return ( -
    -

    You've reached 30 tokens creation limit.

    -
    - ); - } - - return <>; - }; - return (
    @@ -105,30 +86,11 @@ const CreateTokenField = ({ {errors.name.message} )} - - {has_no_errors && ( -
    -
      -
    • - Only alphanumeric characters with spaces and underscores are allowed. -
    • -
    • - The name must be between 2 to 32 characters. -
    • -
    • - Duplicate token names aren't allowed. -
    • -
    • - - The name cannot contain “Binary”, “Deriv”, or similar words. - -
    • -
    • - You can create up to 30 tokens for this account. -
    • -
    -
    - )} + ); }; From 6a4e4e763494138e0a04b63f5c092e1eb2b14ae8 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Tue, 25 Jul 2023 17:40:36 +0200 Subject: [PATCH 6/8] chore: fixing border radius --- .../components/ApiTokenCard/api-token.card.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dashboard/components/ApiTokenCard/api-token.card.module.scss b/src/features/dashboard/components/ApiTokenCard/api-token.card.module.scss index dd0b3881..cc65cea8 100644 --- a/src/features/dashboard/components/ApiTokenCard/api-token.card.module.scss +++ b/src/features/dashboard/components/ApiTokenCard/api-token.card.module.scss @@ -8,7 +8,7 @@ flex-direction: column; gap: rem(1); border: 1px solid var(--ifm-color-emphasis-400); - border-radius: rem(0.4); + border-radius: rem(2); padding: rem(1.6); label { From 47e6151757a6b96ccad1f1b0af10711995879a28 Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Fri, 28 Jul 2023 12:31:01 +0200 Subject: [PATCH 7/8] chore: style improvement --- .../ApiTokenForm/CreateTokenField/CustomErrors/index.tsx | 2 +- .../components/ApiTokenForm/api-token.form.module.scss | 2 +- src/styles/index.scss | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx index 34fe04e7..17c19926 100644 --- a/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx +++ b/src/features/dashboard/components/ApiTokenForm/CreateTokenField/CustomErrors/index.tsx @@ -17,7 +17,7 @@ const CustomErrors = ({ token_name_exists, tokens_limit_reached, input_value }: if (tokens_limit_reached && input_value !== '') { return (
    -

    You've reached 30 tokens creation limit.

    +

    You've created the maximum number of tokens.

    ); } diff --git a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss index 3f1976d1..65c62c47 100644 --- a/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss +++ b/src/features/dashboard/components/ApiTokenForm/api-token.form.module.scss @@ -55,7 +55,7 @@ form { display: flex; position: relative; box-sizing: border-box; - margin: rem(1) 0; + margin: rem(0.5) 0; &:hover { border: 1px solid var(--colors-greyLight600); } diff --git a/src/styles/index.scss b/src/styles/index.scss index 728b099c..50ca987e 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -88,6 +88,7 @@ h6 { .error-message { color: var(--colors-coral500) !important; + margin-left: rem(1.5); } .error-border { From f100dba59c661f19f89029bde5e160969589879e Mon Sep 17 00:00:00 2001 From: Hubert Koster Date: Fri, 28 Jul 2023 13:57:57 +0200 Subject: [PATCH 8/8] chore: improve tests --- .../components/ApiTokenForm/__tests__/api-token.form.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx b/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx index fcd3cecc..281bd094 100644 --- a/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx +++ b/src/features/dashboard/components/ApiTokenForm/__tests__/api-token.form.test.tsx @@ -172,7 +172,7 @@ describe('Home Page', () => { await userEvent.type(nameInput, 'asdf'); - const error = screen.getByText(/reached 30 tokens creation limit/i); + const error = screen.getByText(/created the maximum number of tokens/i); expect(error).toBeVisible(); }); });