Skip to content

Commit 7f973f6

Browse files
committed
frontend: fix continue btn disabled on choosing device name setup
also added a new device name error state 'tooShort'. Error will only show when user has started typing and deletes everything. Won't show on initial (empty) name.
1 parent c8e3255 commit 7f973f6

File tree

6 files changed

+61
-91
lines changed

6 files changed

+61
-91
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useCallback, useMemo } from 'react';
2+
import { TDeviceNameError } from '../utils/types';
3+
4+
// matches any character that is not a printable ASCII character or space
5+
const regexInvalid = /[^ -~]/g;
6+
7+
export const useValidateDeviceName = (name: string) => {
8+
9+
const getDeviceNameValidationError = useCallback((name: string): TDeviceNameError => {
10+
const trimmed = name.trim();
11+
regexInvalid.lastIndex = 0; // resets lastIndex before each test
12+
13+
if (trimmed.length < 1) {
14+
return 'tooShort';
15+
}
16+
17+
if (trimmed.length > 30) {
18+
return 'tooLong';
19+
}
20+
21+
if (regexInvalid.test(trimmed)) {
22+
return 'invalidChars';
23+
}
24+
25+
}, []);
26+
27+
const getInvalidCharsInDeviceName = useCallback((deviceName: string) => deviceName.match(regexInvalid)?.filter(filterUnique).join(', '), []);
28+
29+
const { error, invalidChars, nameIsTooShort } = useMemo(() => {
30+
const error = getDeviceNameValidationError(name);
31+
const invalidChars = getInvalidCharsInDeviceName(name);
32+
const nameIsTooShort = error === 'tooShort';
33+
return { error, invalidChars, nameIsTooShort };
34+
}, [getDeviceNameValidationError, getInvalidCharsInDeviceName, name]);
35+
36+
37+
const filterUnique = (value: string, index: number, array: string[]) => {
38+
return array.indexOf(value) === index;
39+
};
40+
41+
return { error, invalidChars, nameIsTooShort };
42+
};

frontends/web/src/routes/device/bitbox02/setup/name.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { FormEvent, useEffect, useMemo, useState } from 'react';
17+
import { FormEvent, useEffect, useState } from 'react';
1818
import { useTranslation } from 'react-i18next';
1919
import { View, ViewButtons, ViewContent, ViewHeader } from '../../../../components/view/view';
2020
import { Status } from '../../../../components/status/status';
2121
import { Button, Input } from '../../../../components/forms';
2222
import { checkSDCard } from '../../../../api/bitbox02';
23-
import { TDeviceNameError, getInvalidCharsInDeviceName, getDeviceNameValidationError } from '../../../../utils/device';
2423
import style from './name.module.css';
24+
import { useValidateDeviceName } from '../../../../hooks/devicename';
25+
import { TDeviceNameError } from '../../../../utils/types';
2526

2627
type TProps = {
2728
onDeviceName: (name: string) => void;
@@ -39,17 +40,7 @@ export const SetDeviceName = ({
3940
}: TSetDeviceNameProps) => {
4041
const { t } = useTranslation();
4142
const [deviceName, setDeviceName] = useState('');
42-
const [error, setError] = useState<TDeviceNameError>();
43-
const invalidChars = useMemo(() => getInvalidCharsInDeviceName(deviceName), [deviceName]);
44-
45-
const handleDeviceNameInput = (event: Event) => {
46-
const target = (event.target as HTMLInputElement);
47-
const value: string = target.value;
48-
const validationError = getDeviceNameValidationError(value);
49-
setError(validationError);
50-
setDeviceName(value);
51-
};
52-
43+
const { error, invalidChars, nameIsTooShort } = useValidateDeviceName(deviceName);
5344

5445
return (
5546
<form
@@ -74,9 +65,9 @@ export const SetDeviceName = ({
7465
<ViewContent textAlign="left" minHeight="140px">
7566
<Input
7667
autoFocus
77-
className={`${style.wizardLabel} ${error ? style.inputError : ''}`}
68+
className={`${style.wizardLabel} ${error && !nameIsTooShort ? style.inputError : ''}`}
7869
label={t('bitbox02Wizard.stepCreate.nameLabel')}
79-
onInput={handleDeviceNameInput}
70+
onInput={(e) => setDeviceName(e.target.value)}
8071
placeholder={t('bitbox02Wizard.stepCreate.namePlaceholder')}
8172
value={deviceName}
8273
id="deviceName">
@@ -85,7 +76,7 @@ export const SetDeviceName = ({
8576
</ViewContent>
8677
<ViewButtons>
8778
<Button
88-
disabled={!!error || error !== false}
79+
disabled={!!error}
8980
primary
9081
type="submit">
9182
{t('button.continue')}
@@ -109,6 +100,9 @@ type TDeviceNameErrorMessageProps = {
109100

110101
export const DeviceNameErrorMessage = ({ error, invalidChars }: TDeviceNameErrorMessageProps) => {
111102
const { t } = useTranslation();
103+
if (error === 'tooShort') {
104+
return null;
105+
}
112106
return (<span hidden={!error} className={style.errorMessage}>
113107
{t(`bitbox02Wizard.stepCreate.error.${error}`, {
114108
invalidChars

frontends/web/src/routes/settings/components/device-settings/device-name-setting.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useState, ChangeEvent, useMemo } from 'react';
17+
import { useState } from 'react';
1818
import { useTranslation } from 'react-i18next';
1919
import { SettingsItem } from '../settingsItem/settingsItem';
2020
import { ChevronRightDark } from '../../../../components/icon';
@@ -23,8 +23,8 @@ import { Dialog, DialogButtons } from '../../../../components/dialog/dialog';
2323
import { getDeviceInfo, setDeviceName } from '../../../../api/bitbox02';
2424
import { alertUser } from '../../../../components/alert/Alert';
2525
import { WaitDialog } from '../../../../components/wait-dialog/wait-dialog';
26-
import { TDeviceNameError, getInvalidCharsInDeviceName, getDeviceNameValidationError } from '../../../../utils/device';
2726
import { DeviceNameErrorMessage } from '../../../device/bitbox02/setup/name';
27+
import { useValidateDeviceName } from '../../../../hooks/devicename';
2828
import nameStyle from '../../../device/bitbox02/setup/name.module.css';
2929

3030
type TDeviceNameSettingProps = {
@@ -104,16 +104,7 @@ const DeviceNameSetting = ({ deviceName, deviceID }: TDeviceNameSettingProps) =>
104104

105105
const SetDeviceNameDialog = ({ open, onClose, currentName, onInputChange, name, handleUpdateName }: TDialogProps) => {
106106
const { t } = useTranslation();
107-
const [validationNameError, setValidationNameError] = useState<TDeviceNameError>();
108-
const invalidChars = useMemo(() => getInvalidCharsInDeviceName(name), [name]);
109-
110-
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
111-
const value = e.target.value;
112-
const error = getDeviceNameValidationError(value);
113-
setValidationNameError(error);
114-
onInputChange(value);
115-
};
116-
107+
const { error, invalidChars, nameIsTooShort } = useValidateDeviceName(name);
117108

118109
return (
119110
<Dialog
@@ -133,21 +124,21 @@ const SetDeviceNameDialog = ({ open, onClose, currentName, onInputChange, name,
133124
</div>
134125
<div className="column">
135126
<Input
136-
className={`m-none ${validationNameError ? nameStyle.inputError : ''}`}
127+
className={`m-none ${error && !nameIsTooShort ? nameStyle.inputError : ''}`}
137128
label={t('bitbox02Settings.deviceName.input')}
138-
onInput={handleInputChange}
129+
onInput={(e) => onInputChange(e.target.value)}
139130
placeholder={t('bitbox02Settings.deviceName.placeholder')}
140131
value={name}
141132
id="deviceName"
142133
/>
143-
<DeviceNameErrorMessage error={validationNameError} invalidChars={invalidChars} />
134+
<DeviceNameErrorMessage error={error} invalidChars={invalidChars} />
144135
</div>
145136
</div>
146137
</div>
147138
<DialogButtons>
148139
<Button
149140
primary
150-
disabled={!(name && !validationNameError)}
141+
disabled={!!error}
151142
onClick={handleUpdateName}
152143
>
153144
{t('button.ok')}

frontends/web/src/utils/device.test.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

frontends/web/src/utils/device.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

frontends/web/src/utils/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ export type KeysOf<T> = Array<keyof T>;
2626
* At the moment, we are using version 2.9.2 (yarn run tsc -version).
2727
*/
2828
export type ObjectButNotFunction = object & { prototype?: never; };
29+
30+
export type TDeviceNameError = undefined | 'tooShort' | 'tooLong' | 'invalidChars'

0 commit comments

Comments
 (0)