Skip to content

Commit b3e188a

Browse files
kleysccoderabbitai[bot]alexlwn123
authored
Backup file after dkg (fedibtc#587)
* feat: trim whitespace in verification codes * feat: trim leading/trailing whitespace in verification codes * feat: trim leading/trailing whitespace in verification codes/rebase * feat: update backup modal * feat: improve backup modal UI and download flow * fix: use correct setup action type in modal close * fix: update hook usrTrimmedInput * Update apps/router/src/hooks/custom/useTrimmedInput.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: enhance BackupModal functionality * refactor: remove redundant BackupModal from FederationSetup * feat: update downloaded translations for multiple languages * refactor: move @chakra-ui/icons to apps/router/package.json * chore: revert registry change to yarn.lock --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Alex Lewin <[email protected]>
1 parent 6e3c4c1 commit b3e188a

File tree

18 files changed

+214
-43
lines changed

18 files changed

+214
-43
lines changed

apps/router/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@codemirror/lang-json": "^6.0.1",
1616
"@fedimint/types": "*",
1717
"@fedimint/ui": "*",
18+
"@chakra-ui/icons": "^2.2.4",
1819
"@fedimint/utils": "*",
1920
"@uiw/codemirror-theme-github": "^4.23.2",
2021
"@uiw/react-codemirror": "^4.23.2",
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React, { useState, useEffect, useCallback } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import {
4+
Modal,
5+
ModalOverlay,
6+
ModalContent,
7+
ModalHeader,
8+
ModalBody,
9+
Button,
10+
Flex,
11+
Alert,
12+
AlertIcon,
13+
AlertTitle,
14+
Text,
15+
} from '@chakra-ui/react';
16+
import { useGuardianAdminApi, useGuardianDispatch } from '../../hooks';
17+
import { hexToBlob } from '../utils/api';
18+
import { GUARDIAN_APP_ACTION_TYPE, GuardianStatus } from '../../types/guardian';
19+
20+
interface BackupModalProps {
21+
isOpen: boolean;
22+
onClose: () => void;
23+
}
24+
25+
export const BackupModal: React.FC<BackupModalProps> = ({
26+
isOpen,
27+
onClose,
28+
}) => {
29+
const { t } = useTranslation();
30+
const [hasDownloaded, setHasDownloaded] = useState(false);
31+
const [isDownloading, setIsDownloading] = useState(false);
32+
const [downloadError, setDownloadError] = useState<string | null>(null);
33+
const api = useGuardianAdminApi();
34+
const dispatch = useGuardianDispatch();
35+
36+
useEffect(() => {
37+
if (isOpen) {
38+
setHasDownloaded(false);
39+
setDownloadError(null);
40+
}
41+
}, [isOpen]);
42+
43+
const handleDownload = useCallback(async () => {
44+
setIsDownloading(true);
45+
setDownloadError(null);
46+
47+
try {
48+
const response = await api.downloadGuardianBackup();
49+
const blob = hexToBlob(response.tar_archive_bytes, 'application/x-tar');
50+
const url = window.URL.createObjectURL(blob);
51+
const link = document.createElement('a');
52+
link.href = url;
53+
link.download = 'guardianBackup.tar';
54+
link.click();
55+
56+
setTimeout(() => URL.revokeObjectURL(url), 100);
57+
setHasDownloaded(true);
58+
} catch (error) {
59+
console.error('Error in handleDownload:', error);
60+
setDownloadError(
61+
t('federation-dashboard.danger-zone.backup.error-download')
62+
);
63+
} finally {
64+
setIsDownloading(false);
65+
}
66+
}, [api, t]);
67+
68+
const handleContinue = useCallback(() => {
69+
if (hasDownloaded) {
70+
dispatch({
71+
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
72+
payload: GuardianStatus.Admin,
73+
});
74+
onClose();
75+
}
76+
}, [dispatch, hasDownloaded, onClose]);
77+
78+
return (
79+
<Modal
80+
isOpen={isOpen}
81+
onClose={onClose}
82+
closeOnOverlayClick={false}
83+
isCentered
84+
>
85+
<ModalOverlay />
86+
<ModalContent>
87+
<ModalHeader alignSelf='center'>
88+
{t('federation-dashboard.danger-zone.backup.title')}
89+
</ModalHeader>
90+
<ModalBody pb={6}>
91+
<Flex direction='column' gap={4}>
92+
<Alert status='warning'>
93+
<AlertIcon />
94+
<AlertTitle>
95+
{t('federation-dashboard.danger-zone.backup.warning-title')}
96+
</AlertTitle>
97+
</Alert>
98+
<Text mb={4}>
99+
{t('federation-dashboard.danger-zone.backup.warning-text')}
100+
</Text>
101+
{downloadError && (
102+
<Alert status='error'>
103+
<AlertIcon />
104+
<AlertTitle>{downloadError}</AlertTitle>
105+
</Alert>
106+
)}
107+
<Flex justifyContent='center' gap={4} direction={['column', 'row']}>
108+
<Button
109+
variant='ghost'
110+
size={['sm', 'md']}
111+
onClick={handleDownload}
112+
isDisabled={hasDownloaded || isDownloading}
113+
isLoading={isDownloading}
114+
bg='red.500'
115+
color='white'
116+
_hover={{ bg: 'red.600' }}
117+
_active={{ bg: 'red.700' }}
118+
>
119+
{hasDownloaded
120+
? t('federation-dashboard.danger-zone.backup.downloaded') +
121+
' ✓'
122+
: t(
123+
'federation-dashboard.danger-zone.acknowledge-and-download'
124+
)}
125+
</Button>
126+
<Button
127+
colorScheme='blue'
128+
size={['sm', 'md']}
129+
onClick={handleContinue}
130+
isDisabled={!hasDownloaded}
131+
>
132+
{t('common.close')}
133+
</Button>
134+
</Flex>
135+
</Flex>
136+
</ModalBody>
137+
</ModalContent>
138+
</Modal>
139+
);
140+
};
Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { Flex, Heading, Text, Spinner } from '@chakra-ui/react';
33
import { useTranslation } from '@fedimint/utils';
44
import {
@@ -7,6 +7,7 @@ import {
77
GuardianStatus,
88
} from '../../../../../types/guardian';
99
import { useGuardianDispatch } from '../../../../../hooks';
10+
import { BackupModal } from '../../../BackupModal';
1011

1112
interface SetupCompleteProps {
1213
role: GuardianRole;
@@ -15,40 +16,50 @@ interface SetupCompleteProps {
1516
export const SetupComplete: React.FC<SetupCompleteProps> = ({ role }) => {
1617
const { t } = useTranslation();
1718
const dispatch = useGuardianDispatch();
19+
const [showBackupModal, setShowBackupModal] = useState(true);
1820

1921
useEffect(() => {
20-
const timer = setTimeout(() => {
21-
dispatch({
22-
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
23-
payload: GuardianStatus.Admin,
24-
});
25-
}, 3000);
22+
if (!showBackupModal) {
23+
const timer = setTimeout(() => {
24+
dispatch({
25+
type: GUARDIAN_APP_ACTION_TYPE.SET_STATUS,
26+
payload: GuardianStatus.Admin,
27+
});
28+
}, 3000);
2629

27-
return () => clearTimeout(timer);
28-
}, [dispatch]);
30+
return () => clearTimeout(timer);
31+
}
32+
}, [dispatch, showBackupModal]);
2933

3034
return (
31-
<Flex
32-
direction='column'
33-
justify='center'
34-
align='center'
35-
textAlign='center'
36-
pt={10}
37-
>
38-
<Heading size='sm' fontSize='42px' mb={8}>
39-
{t('setup-complete.header')}
40-
</Heading>
41-
<Heading size='md' fontWeight='medium' mb={2}>
42-
{t('setup-complete.congratulations')}
43-
</Heading>
44-
<Text mb={16} fontWeight='medium'>
45-
{role === GuardianRole.Follower
46-
? t(`setup-complete.follower-message`)
47-
: t(`setup-complete.leader-message`)}
48-
</Text>
49-
<Flex direction='column' align='center'>
50-
<Spinner size='xl' mb={4} />
35+
<>
36+
<Flex
37+
direction='column'
38+
justify='center'
39+
align='center'
40+
textAlign='center'
41+
pt={10}
42+
>
43+
<Heading size='sm' fontSize='42px' mb={8}>
44+
{t('setup-complete.header')}
45+
</Heading>
46+
<Heading size='md' fontWeight='medium' mb={2}>
47+
{t('setup-complete.congratulations')}
48+
</Heading>
49+
<Text mb={16} fontWeight='medium'>
50+
{role === GuardianRole.Follower
51+
? t(`setup-complete.follower-message`)
52+
: t(`setup-complete.leader-message`)}
53+
</Text>
54+
<Flex direction='column' align='center'>
55+
<Spinner size='xl' mb={4} />
56+
</Flex>
5157
</Flex>
52-
</Flex>
58+
59+
<BackupModal
60+
isOpen={showBackupModal}
61+
onClose={() => setShowBackupModal(false)}
62+
/>
63+
</>
5364
);
5465
};

apps/router/src/hooks/custom/useTrimmedInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
22

3-
const cleanInput = (value: string) => value.trim();
3+
const cleanInput = (value: string | undefined) => value?.trim() ?? '';
44

55
export const useTrimmedInput = (initialValue = '') => {
66
const [value, setValue] = useState(initialValue);

apps/router/src/languages/ca.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"warning-title": "Advertència",
170170
"warning-text": "La còpia de seguretat conté claus privades i material secret per al guardià de la federació i s'ha de mantenir segura. La recuperació utilitzant aquesta còpia de seguretat requereix la vostra contrasenya d'administrador!",
171171
"cancelButton": "Cancel·la",
172-
"acknowledgeButton": "Reconeix i descarrega"
172+
"acknowledgeButton": "Reconeix i descarrega",
173+
"downloaded": "Descarregat"
173174
},
174175
"sign-api-announcement": {
175176
"label": "Anunci de l'API de signatura",

apps/router/src/languages/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"warning-title": "Warnung",
170170
"warning-text": "Die Sicherung enthält private Schlüssel und geheimes Material für den Föderationswächter und muss sicher aufbewahrt werden. Die Wiederherstellung mit dieser Sicherung erfordert Ihr Admin-Passwort!",
171171
"cancelButton": "Stornieren",
172-
"acknowledgeButton": "Bestätigen & Herunterladen"
172+
"acknowledgeButton": "Bestätigen & Herunterladen",
173+
"downloaded": "Heruntergeladen"
173174
},
174175
"sign-api-announcement": {
175176
"label": "Ankündigung der Sign API",

apps/router/src/languages/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@
172172
"warning-title": "Warning",
173173
"warning-text": "The backup contains private keys and secret material for the federation guardian and must be kept secure. Recovery using this backup requires your admin password!",
174174
"cancelButton": "Cancel",
175-
"acknowledgeButton": "Acknowledge & Download"
175+
"acknowledgeButton": "Acknowledge & Download",
176+
"downloaded": "Downloaded"
176177
},
177178
"sign-api-announcement": {
178179
"label": "Sign API Announcement",

apps/router/src/languages/es.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"warning-title": "Advertencia",
170170
"warning-text": "La copia de seguridad contiene claves privadas y material secreto para el guardián de la federación y debe mantenerse segura. ¡La recuperación usando esta copia de seguridad requiere tu contraseña de administrador!",
171171
"cancelButton": "Cancelar",
172-
"acknowledgeButton": "Reconocer y Descargar"
172+
"acknowledgeButton": "Reconocer y Descargar",
173+
"downloaded": "Descargado"
173174
},
174175
"sign-api-announcement": {
175176
"label": "Anuncio de API de Firma",

apps/router/src/languages/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"warning-title": "Avertissement",
170170
"warning-text": "La sauvegarde contient des clés privées et des matériaux secrets pour le gardien de la fédération et doit être conservée en toute sécurité. La récupération à l'aide de cette sauvegarde nécessite votre mot de passe administrateur !",
171171
"cancelButton": "Annuler",
172-
"acknowledgeButton": "Reconnaître & Télécharger"
172+
"acknowledgeButton": "Reconnaître & Télécharger",
173+
"downloaded": "Téléchargé"
173174
},
174175
"sign-api-announcement": {
175176
"label": "Annonce de l'API Sign",

apps/router/src/languages/hu.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
"warning-title": "Figyelmeztetés",
170170
"warning-text": "A biztonsági mentés tartalmazza a szövetségi őrző privát kulcsait és titkos anyagait, és biztonságban kell tartani. A helyreállítás ehhez a biztonsági mentéshez az adminisztrátori jelszavadat igényli!",
171171
"cancelButton": "Mégsem",
172-
"acknowledgeButton": "Elfogad és Letölt"
172+
"acknowledgeButton": "Elfogad és Letölt",
173+
"downloaded": "Letöltve"
173174
},
174175
"sign-api-announcement": {
175176
"label": "API bejelentés aláírása",

0 commit comments

Comments
 (0)