on df0c310 help sign in
bridge/src/views/Activate/MasterKeyConfirm.tsx
189 lines 6.7 kB history raw
1import React, { useCallback, useMemo, useState } from 'react';
2import * as need from 'lib/need';
3import cn from 'classnames';
5import ActivateView from './ActivateView';
6import { Box, Text } from '@tlon/indigo-react';
7import { composeValidator, buildPatqValidator } from 'form/validators';
8import { DEFAULT_FADE_TIMEOUT } from 'lib/constants';
9import { FadeableActivateButton as ActivateButton } from './ActivateButton';
10import { FadeableActivateHeader as ActivateHeader } from './ActivateHeader';
11import { FadeableActivateParagraph as ActivateParagraph } from './ActivateParagraph';
12import { FadeableActivateSteps as ActivateSteps } from './ActivateSteps';
13import { isDevelopment } from 'lib/flags';
14import { ticketFromSegments, ticketToSegments } from 'form/formatters';
15import { timeout } from 'lib/timeout';
16import { useActivateFlow } from './useActivateFlow';
17import { useLocalRouter } from 'lib/LocalRouter';
18import { validateExactly } from 'lib/validators';
19import BridgeForm from 'form/BridgeForm';
20import FormError from 'form/FormError';
21import useFadeIn from './useFadeIn';
22import { HiddenInput, TicketSegmentInput } from 'form/Inputs';
23import View from 'components/View';
25import './MasterKeyConfirm.scss';
27const MasterKeyConfirm = () => {
28 const { inviteMasterTicketWallet, setIsIn }: any = useActivateFlow();
29 const { ticket } = need.wallet(inviteMasterTicketWallet);
30 const { push, names }: any = useLocalRouter();
31 const STUB_VERIFY_TICKET = isDevelopment;
32 const ticketSegments = useMemo(() => ticketToSegments(ticket), [ticket]);
33 const [showError, setShowError] = useState<boolean>(false);
35 const onValues = useCallback(({ valid, values, form }) => {
36 const { ticket0, ticket1, ticket2, ticket3 } = values;
37 const tickets = [ticket0, ticket1, ticket2, ticket3];
38 const allTicketsPopulated = tickets.every(t => t?.length === 6);
40 setShowError(!valid && allTicketsPopulated);
42 form.change('ticket', ticketFromSegments(tickets));
43 }, []);
45 const validate = useMemo(
46 () =>
47 composeValidator({
48 ticket: buildPatqValidator([
49 validateExactly(ticket, 'Does not match expected master ticket.'),
50 ]),
51 }),
52 [ticket]
53 );
55 const initialValues = useMemo(
56 () => ({
57 ticket: STUB_VERIFY_TICKET ? ticket : undefined,
58 ticket0: STUB_VERIFY_TICKET ? ticketSegments[0] : undefined,
59 ticket1: STUB_VERIFY_TICKET ? ticketSegments[1] : undefined,
60 ticket2: STUB_VERIFY_TICKET ? ticketSegments[2] : undefined,
61 ticket3: STUB_VERIFY_TICKET ? ticketSegments[3].slice(0, 5) : undefined,
62 }),
63 [STUB_VERIFY_TICKET, ticket, ticketSegments]
64 );
66 const goToTransfer = useCallback(async () => {
67 setIsIn(false);
68 await timeout(DEFAULT_FADE_TIMEOUT); // Pause for UI fade animation
69 push(names.TRANSFER);
70 }, [names.TRANSFER, push, setIsIn]);
72 const goBack = useCallback(async () => {
73 setIsIn(false);
74 await timeout(DEFAULT_FADE_TIMEOUT); // Pause for UI fade animation
75 push(names.DOWNLOAD, { skipAnimationDelay: true });
76 }, [names.DOWNLOAD, push, setIsIn]);
78 const header = useMemo(() => {
79 return (
80 <Box>
81 <ActivateHeader content={'Confirm Backup'} />
82 <ActivateParagraph
83 copy={
84 'To confirm that you stored your Master Ticket somewhere safe, enter it below.'
85 }
86 />
87 </Box>
88 );
89 }, []);
91 useFadeIn();
93 return (
94 <View inset id={'master-key-confirm'} hideBack>
95 <ActivateView
96 onBack={goBack}
97 header={header}
98 gridRows={'20% 80%'}
99 gridAreas={"'header' 'content'"}>
100 <Box className={'content'}>
101 <BridgeForm
102 style={{
103 display: 'flex',
104 flexFlow: 'column nowrap',
105 height: '100%',
106 width: '100%',
107 justifyContent: 'center',
108 alignItems: 'center',
109 }}
110 validate={validate}
111 afterSubmit={goToTransfer}
112 onValues={onValues}
113 initialValues={initialValues}>
114 {({ handleSubmit, valid }: any) => (
115 <Box className="form-inner">
116 <Box className="inputs-outer">
117 {showError && (
118 <Box height={'24px'}>
119 {/* this is a flexbox placeholder to balance the error message below*/}
120 </Box>
121 )}
122 <Box className="inputs-inner">
123 {ticketSegments.map((segment, i) => {
124 return (
125 <Box
126 key={i}
127 display={'flex'}
128 flexDirection={'row'}
129 flexWrap={'nowrap'}>
130 <TicketSegmentInput
131 className={cn('ticket-input', {
132 'has-error': showError,
133 })}
134 name={`ticket${i}`}
135 autoFocus={i === 0}
136 />
137 {i < ticketSegments.length - 1 ? (
138 <Text
139 key={i}
140 alignSelf="center"
141 color="gray"
142 mx={1}>
143 —
144 </Text>
145 ) : null}
146 </Box>
147 );
148 })}
149 </Box>
150 {showError && (
151 <Box>
152 <Text className="error-message">
153 The Master Ticket you entered is incorrect.
154 </Text>
155 </Box>
156 )}
157 </Box>
159 <Box
160 full
161 style={{ display: 'none' }}
162 as={HiddenInput}
163 name="ticket"
164 />
166 <Box as={FormError} />
168 <Box
169 display="flex"
170 flexDirection="column"
171 flexWrap="nowrap"
172 height={'100%'}
173 justifyContent="flex-end"
174 gridArea="submit">
175 <ActivateButton disabled={!valid} onClick={handleSubmit}>
176 Confirm Backup
177 </ActivateButton>
178 </Box>
179 </Box>
180 )}
181 </BridgeForm>
182 </Box>
183 </ActivateView>
184 <ActivateSteps currentStep={2} totalSteps={4} overrideFadeIn={true} />
185 </View>
186 );
187};
189export default MasterKeyConfirm;