Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions frontends/web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const App = () => {
deviceIDs.length === 0
&& (
currentURL.startsWith('/settings/device-settings/')
|| currentURL.startsWith('/settings/backup-functionality/')
|| currentURL.startsWith('/manage-backups/')
)
) {
Expand Down
4 changes: 3 additions & 1 deletion frontends/web/src/routes/device/bitbox01/backups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Restore } from './restore';
type BackupsProps = {
deviceID: string;
showCreate?: boolean;
showCheck?: boolean;
showRestore?: boolean;
requireConfirmation?: boolean;
onRestore?: () => void;
Expand Down Expand Up @@ -89,6 +90,7 @@ class Backups extends Component<Props, State> {
t,
children,
showCreate = false,
showCheck = showCreate,
showRestore = true,
deviceID,
requireConfirmation = true,
Expand Down Expand Up @@ -146,7 +148,7 @@ class Backups extends Component<Props, State> {
)
}
{
showCreate && (
showCheck && (
<Check
selectedBackup={selectedBackup}
deviceID={deviceID} />
Expand Down
20 changes: 18 additions & 2 deletions frontends/web/src/routes/device/bitbox02/backups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type TProps = {
deviceID: string;
showRestore?: boolean;
showCreate?: boolean;
showCheck?: boolean;
autoStartCreate?: boolean;
autoStartCheck?: boolean;
autoStartID?: string;
showRadio: boolean;
onSelectBackup?: (backup: Backup) => void;
onRestoreBackup?: (success: boolean) => void;
Expand All @@ -30,6 +34,10 @@ export const BackupsV2 = ({
deviceID,
showRestore,
showCreate,
showCheck,
autoStartCreate,
autoStartCheck,
autoStartID,
showRadio,
onSelectBackup,
onRestoreBackup,
Expand Down Expand Up @@ -144,15 +152,23 @@ export const BackupsV2 = ({
}
{
showCreate && (
<Create deviceID={deviceID} />
<Create
deviceID={deviceID}
autoStart={autoStartCreate}
autoStartID={autoStartCreate ? autoStartID : undefined}
showButton={!autoStartCreate}
/>
)
}
{
showCreate && (
showCheck && (
<Check
deviceID={deviceID}
backups={backups.backups ? backups.backups : []}
disabled={backups.backups.length === 0}
autoStart={autoStartCheck}
autoStartID={autoStartCheck ? autoStartID : undefined}
showButton={!autoStartCheck}
/>
)
}
Expand Down
14 changes: 13 additions & 1 deletion frontends/web/src/routes/device/bitbox02/bitbox02.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { getStatus, statusChanged } from '@/api/bitbox02';
import type { TDevices } from '@/api/devices';
import { useSync } from '@/hooks/api';
import { BB02Settings } from '@/routes/settings/bb02-settings';
import { BB02BackupSettings, BB02Settings } from '@/routes/settings/bb02-settings';

type TProps = {
deviceID: string;
Expand All @@ -21,3 +21,15 @@ export const BitBox02 = ({ deviceID, devices, hasAccounts }: TProps) => {
}
return <BB02Settings deviceID={deviceID} devices={devices} hasAccounts={hasAccounts} />;
};

export const BitBox02Backup = ({ deviceID, devices, hasAccounts }: TProps) => {
const isBitBox02 = devices[deviceID] === 'bitbox02';
const status = useSync(
isBitBox02 ? () => getStatus(deviceID) : async () => 'connected' as const,
isBitBox02 ? cb => statusChanged(deviceID, cb) : () => () => undefined
);
if (!isBitBox02 || status !== 'initialized') {
return null;
}
return <BB02BackupSettings deviceID={deviceID} devices={devices} hasAccounts={hasAccounts} />;
};
57 changes: 44 additions & 13 deletions frontends/web/src/routes/device/bitbox02/checkbackup.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import * as bitbox02API from '@/api/bitbox02';
import { BackupsListItem } from '@/routes/device/components/backup';
import { Backup } from '@/api/backup';
import { alertUser } from '@/components/alert/Alert';
import { Backup, getBackupList } from '@/api/backup';
import { Dialog, DialogButtons } from '@/components/dialog/dialog';
import { Button } from '@/components/forms';
import { useTranslation } from 'react-i18next';

const startedCheckBackupFlows = new Set<string>();

type TProps = {
deviceID: string;
backups: Backup[];
disabled: boolean;
autoStart?: boolean;
autoStartID?: string;
showButton?: boolean;
};

export const Check = ({ deviceID, backups, disabled }: TProps) => {
export const Check = ({
deviceID,
backups,
disabled,
autoStart = false,
autoStartID,
showButton = true,
}: TProps) => {
const [activeDialog, setActiveDialog] = useState(false);
const [message, setMessage] = useState('');
const [foundBackup, setFoundBackup] = useState<Backup>();
const [userVerified, setUserVerified] = useState(false);
const checkBackupRef = useRef<() => Promise<void>>();
const { t } = useTranslation();

const checkBackup = async () => {
setMessage('');
setFoundBackup(undefined);
setUserVerified(false);
try {
const result = await bitbox02API.checkBackup(deviceID, true);
if (result.success) {
const { backupID } = result;
const foundBackup = backups.find((backup: Backup) => backup.id === backupID);
let foundBackup = backups.find((backup: Backup) => backup.id === backupID);
if (!foundBackup) {
alertUser(t('unknownError', { errorMessage: 'Not found' }));
return;
const backupListResult = await getBackupList(deviceID);
if (backupListResult.success) {
foundBackup = backupListResult.backups.find((backup: Backup) => backup.id === backupID);
}
}
setActiveDialog(true);
setFoundBackup(foundBackup);
Expand All @@ -54,15 +70,30 @@ export const Check = ({ deviceID, backups, disabled }: TProps) => {
console.error(error);
}
};
checkBackupRef.current = checkBackup;

useEffect(() => {
if (!autoStart || disabled) {
return;
}
const id = autoStartID || `check-${deviceID}`;
if (startedCheckBackupFlows.has(id)) {
return;
}
startedCheckBackupFlows.add(id);
void checkBackupRef.current?.();
}, [autoStart, disabled, autoStartID, deviceID]);

return (
<>
<Button
primary
disabled={disabled}
onClick={checkBackup}>
{t('button.check')}
</Button>
{showButton && (
<Button
primary
disabled={disabled}
onClick={checkBackup}>
{t('button.check')}
</Button>
)}
<Dialog
open={activeDialog}
title={t('backup.check.confirmTitle')}>
Expand Down
42 changes: 34 additions & 8 deletions frontends/web/src/routes/device/bitbox02/createbackup.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { checkBackup, createBackup as createBackupAPI } from '@/api/bitbox02';
import { alertUser } from '@/components/alert/Alert';
import { confirmation } from '@/components/confirm/Confirm';
import { Button } from '@/components/forms';
import { WaitDialog } from '@/components/wait-dialog/wait-dialog';
import { useTranslation } from 'react-i18next';

const startedCreateBackupFlows = new Set<string>();

type TProps = {
deviceID: string;
autoStart?: boolean;
autoStartID?: string;
showButton?: boolean;
};

export const Create = ({ deviceID }: TProps) => {
export const Create = ({
deviceID,
autoStart = false,
autoStartID,
showButton = true,
}: TProps) => {
const [creatingBackup, setCreatingBackup] = useState(false);
const [disabled, setDisabled] = useState(false);
const maybeCreateBackupRef = useRef<() => Promise<void>>();
const { t } = useTranslation();

const createBackup = () => {
Expand Down Expand Up @@ -49,15 +60,30 @@ export const Create = ({ deviceID }: TProps) => {
console.error(error);
}
};
maybeCreateBackupRef.current = maybeCreateBackup;

useEffect(() => {
if (!autoStart) {
return;
}
const id = autoStartID || `create-${deviceID}`;
if (startedCreateBackupFlows.has(id)) {
return;
}
startedCreateBackupFlows.add(id);
void maybeCreateBackupRef.current?.();
}, [autoStart, autoStartID, deviceID]);

return (
<>
<Button
primary
disabled={disabled}
onClick={maybeCreateBackup}>
{t('backup.create.title')}
</Button>
{showButton && (
<Button
primary
disabled={disabled}
onClick={maybeCreateBackup}>
{t('backup.create.title')}
</Button>
)}
{ creatingBackup && (
<WaitDialog title={t('backup.create.title')}>
{t('bitbox02Interact.followInstructions')}
Expand Down
Loading