Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3a80f7a
frontend: refactor password input to functional component
thisconnect Sep 6, 2025
42bb338
frontend: refactor use-capslock into a hook
thisconnect Sep 6, 2025
f49a516
frontend: use memo to keep the regex if needed
thisconnect Sep 6, 2025
3279164
frontend: remove idprefix from password component
thisconnect Sep 8, 2025
733ec50
frontend: update id and idprefix on password, passwordrepeat etc
thisconnect Oct 9, 2025
6de0801
frontend: move bitbox01 create.jsx to create.tsx
thisconnect Oct 9, 2025
228f208
frontend: converted bb01 create to functional typescript component
thisconnect Oct 9, 2025
3323f8d
frontend: use dialogbuttons in bb01 create
thisconnect Oct 9, 2025
1f12140
frontend: move bitbox01 changepin.jsx to changepin.tsx
thisconnect Oct 9, 2025
401a418
frontend: converted bb01 changepin to functional typescript component
thisconnect Oct 9, 2025
d0611cd
frontend: remove errorcode state
thisconnect Oct 9, 2025
7979dd0
frontend: move bitbox01 seed-create-new.jsx to .tsx
thisconnect Oct 9, 2025
74d8094
frontend: convert bb01 seed-create-new to functional typescript
thisconnect Oct 9, 2025
c55163a
frontend: fix typescript error
thisconnect Oct 9, 2025
2859ecf
frontend: move bitbox01 check.jsx to .tsx
thisconnect Oct 9, 2025
ff7b70d
frontend: convert bb01 check to functional typescript
thisconnect Oct 9, 2025
25ea7be
frontend: use dialogbuttons in bb01 check
thisconnect Oct 9, 2025
38590d3
frontend: move bitbox01 unlock.jsx to unlock.tsx
thisconnect Oct 9, 2025
8a40b0a
frontend: convert bb01 unlock to functional typescript
thisconnect Oct 9, 2025
565f2c7
frontend: fix eslint errors
thisconnect Oct 9, 2025
555f2c8
frontend: move bitbox01 reset.jsx to .tsx
thisconnect Oct 9, 2025
bd5ab71
frontend: convert bitbox01 reset to functional typescript
thisconnect Oct 9, 2025
72b728f
frontend: move bitbox01 to .tsx
thisconnect Oct 9, 2025
9010b9d
frontend: convert bitbox01 to functional typescript
thisconnect Oct 9, 2025
043348f
frontend: fix password too short message
thisconnect Oct 9, 2025
cfc7aa8
frontend: fix new eslint rules by make webfix
thisconnect Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
480 changes: 209 additions & 271 deletions frontends/web/src/components/password.tsx

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion frontends/web/src/hooks/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';

/**
* gets fired on each keydown and executes the provided callback.
Expand Down Expand Up @@ -54,6 +54,31 @@ export const useEsc = (
});
};

const excludeKeys = /^(Shift|Alt|Backspace|CapsLock|Tab)$/i;

const hasCaps = (event: KeyboardEvent) => {
const key = event.key;
// will return null, when we cannot clearly detect if capsLock is active or not
if (key.length > 1 || key.toUpperCase() === key.toLowerCase() || excludeKeys.test(key)) {
return null;
}
// ideally we return event.getModifierState('CapsLock')) but this currently does always return false in Qt
return key.toUpperCase() === key && key.toLowerCase() !== key && !event.shiftKey;
};

export const useCapsLock = () => {
const [capsLock, setCapsLock] = useState(false);

useKeydown((event) => {
const result = hasCaps(event);
if (result !== null) {
setCapsLock(result);
}
});

return capsLock;
};

const FOCUSABLE_SELECTOR = `
a:not(:disabled),
button:not(:disabled),
Expand Down
6 changes: 3 additions & 3 deletions frontends/web/src/routes/device/bitbox01/backups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import { SimpleMarkup } from '../../../utils/markup';
import { alertUser } from '../../../components/alert/Alert';
import { Button } from '../../../components/forms';
import { BackupsListItem } from '../components/backup';
import style from '../components/backups.module.css';
import Check from './check';
import Create from './create';
import { Check } from './check';
import { Create } from './create';
import { Restore } from './restore';
import style from '../components/backups.module.css';

type BackupsProps = {
deviceID: string;
Expand Down
162 changes: 0 additions & 162 deletions frontends/web/src/routes/device/bitbox01/bitbox01.jsx

This file was deleted.

161 changes: 161 additions & 0 deletions frontends/web/src/routes/device/bitbox01/bitbox01.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Copyright 2018 Shift Devices AG
* Copyright 2025 Shift Crypto AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useState, useEffect, useCallback } from 'react';
import { AppUpgradeRequired } from '@/components/appupgraderequired';
import { apiGet } from '@/utils/request';
import { apiWebsocket } from '@/utils/websocket';
import { Unlock } from './unlock';
import Bootloader from './upgrade/bootloader';
import RequireUpgrade from './upgrade/require_upgrade';
import Goal from './setup/goal';
import { SecurityInformation } from './setup/security-information';
import { SeedCreateNew } from './setup/seed-create-new';
import SeedRestore from './setup/seed-restore';
import { Initialize } from './setup/initialize';
import Success from './setup/success';
import Settings from './settings/settings';

const DeviceStatus = Object.freeze({
BOOTLOADER: 'bootloader',
INITIALIZED: 'initialized',
UNINITIALIZED: 'uninitialized',
LOGGED_IN: 'logged_in',
SEEDED: 'seeded',
REQUIRE_FIRMWARE_UPGRADE: 'require_firmware_upgrade',
REQUIRE_APP_UPGRADE: 'require_app_upgrade',
});

const GOAL = Object.freeze({
CREATE: 'create',
RESTORE: 'restore',
});

type DeviceStatusType = (typeof DeviceStatus)[keyof typeof DeviceStatus];
type GoalType = (typeof GOAL)[keyof typeof GOAL];

type Props = {
deviceID: string;
};

export const BitBox01 = ({ deviceID }: Props) => {
const [deviceStatus, setDeviceStatus] = useState<DeviceStatusType | ''>('');
const [goal, setGoal] = useState<GoalType | null>(null);
const [success, setSuccess] = useState<boolean | null>(null);

// --- Fetch device status ---
const onDeviceStatusChanged = useCallback(() => {
apiGet(`devices/${deviceID}/status`).then((status: DeviceStatusType) => {
setDeviceStatus(status);
});
}, [deviceID]);

useEffect(() => {
onDeviceStatusChanged();
const unsubscribe = apiWebsocket((data) => {
if (
'type' in data // check if TEventLegacy
&& data.type === 'device'
&& 'data' in data
&& data.data === 'statusChanged'
&& data.deviceID === deviceID) {
onDeviceStatusChanged();
}
});

return () => {
if (unsubscribe) {
unsubscribe();
}
};
}, [deviceID, onDeviceStatusChanged]);

const handleCreate = () => setGoal(GOAL.CREATE);
const handleRestore = () => setGoal(GOAL.RESTORE);
const handleBack = () => setGoal(null);
const handleSuccess = () => setSuccess(true);
const handleHideSuccess = () => setSuccess(null);

if (!deviceStatus) {
return null;
}

if (success) {
return (
<Success goal={goal} handleHideSuccess={handleHideSuccess} />
);
}

switch (deviceStatus) {
case DeviceStatus.BOOTLOADER:
return (
<Bootloader deviceID={deviceID} />
);
case DeviceStatus.REQUIRE_FIRMWARE_UPGRADE:
return (
<RequireUpgrade deviceID={deviceID} />
);
case DeviceStatus.REQUIRE_APP_UPGRADE:
return (
<AppUpgradeRequired />
);
case DeviceStatus.INITIALIZED:
return (
<Unlock deviceID={deviceID} />
);
case DeviceStatus.UNINITIALIZED:
if (!goal) {
return (
<Goal onCreate={handleCreate} onRestore={handleRestore} />
);
}
return (
<SecurityInformation goal={goal} goBack={handleBack}>
<Initialize goBack={handleBack} deviceID={deviceID} />
</SecurityInformation>
);
case DeviceStatus.LOGGED_IN:
switch (goal) {
case GOAL.CREATE:
return (
<SeedCreateNew
goBack={handleBack}
onSuccess={handleSuccess}
deviceID={deviceID}
/>
);
case GOAL.RESTORE:
return (
<SeedRestore
goBack={handleBack}
onSuccess={handleSuccess}
deviceID={deviceID}
/>
);
default:
return (
<Goal onCreate={handleCreate} onRestore={handleRestore} />
);
}
case DeviceStatus.SEEDED:
return (
<Settings deviceID={deviceID} />
);
default:
return null;
}
};
Loading