on master help sign in
WIP: Refactor animations based on sync with Jimmy

- add react-transition-group - upgrade @urbit/roller-api - extract `ActivateCodeForm`

commita0de25b53738051ebd3a1ef5a0fbadaf46e7e545 authortomholford <tomholford@users.noreply.github.com> dateTue 7 Sep 2021, 20:57 (5 years ago) parent3509775
11 files changed+370−320
package-lock.json+29 package.json+2 src/style/activate.scss+19 src/style/index.scss+3 src/views/Activate/ActivateCode.tsx+10−271 src/views/Activate/ActivateCodeForm.tsx+240 src/views/Activate/ActivateHeader.tsx+17−13 src/views/Activate/MasterKeyDownload.tsx+3 src/views/Activate/MasterKeyPresenter.tsx+1−1 src/views/Activate/PointPresenter.tsx+43−35 src/views/Activate/useActivateFlowState.js+3
package-lock.json+29
@@ -7437,6 +7437,15 @@
74377437 "@types/react": "*"
74387438 }
74397439 },
7440+ "@types/react-transition-group": {
7441+ "version": "4.4.2",
7442+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz",
7443+ "integrity": "sha512-KibDWL6nshuOJ0fu8ll7QnV/LVTo3PzQ9aCPnRUYPfX7eZohHwLIdNHj7pftanREzHNP4/nJa8oeM73uSiavMQ==",
7444+ "dev": true,
7445+ "requires": {
7446+ "@types/react": "*"
7447+ }
7448+ },
74407449 "@types/resolve": {
74417450 "version": "0.0.8",
74427451 "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
@@ -12817,6 +12826,15 @@
1281712826 "utila": "~0.4"
1281812827 }
1281912828 },
12829+ "dom-helpers": {
12830+ "version": "5.2.1",
12831+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
12832+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
12833+ "requires": {
12834+ "@babel/runtime": "^7.8.7",
12835+ "csstype": "^3.0.2"
12836+ }
12837+ },
1282012838 "dom-serializer": {
1282112839 "version": "0.1.1",
1282212840 "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
@@ -28013,6 +28031,17 @@
2801328031 "resolved": "https://registry.npmjs.org/react-teleporter/-/react-teleporter-2.2.1.tgz",
2801428032 "integrity": "sha512-mGoyg0WICb/kBk5Vl0TUSV+lXSouHb/pvefj/1Z4UHlr++4D/QeSiorqAHnJyr54twcN8ggW/PXwst1toJzBCw=="
2801528033 },
28034+ "react-transition-group": {
28035+ "version": "4.4.2",
28036+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
28037+ "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
28038+ "requires": {
28039+ "@babel/runtime": "^7.5.5",
28040+ "dom-helpers": "^5.0.1",
28041+ "loose-envify": "^1.4.0",
28042+ "prop-types": "^15.6.2"
28043+ }
28044+ },
2801628045 "read-pkg": {
2801728046 "version": "1.1.0",
2801828047 "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
package.json+2
@@ -70,6 +70,7 @@
7070 "react-final-form-arrays": "^3.1.0",
7171 "react-scripts": "4.0.3",
7272 "react-teleporter": "^2.2.0",
73+ "react-transition-group": "^4.4.2",
7374 "secp256k1": "^4.0.2",
7475 "styled-components": "^5.3.0",
7576 "styled-system": "^5.1.5",
@@ -87,6 +88,7 @@
8788 "@testing-library/react": "^12.0.0",
8889 "@types/keccak": "^3.0.1",
8990 "@types/lodash": "^4.14.170",
91+ "@types/react-transition-group": "^4.4.2",
9092 "eslint-config-prettier": "^5.0.0",
9193 "eslint-plugin-prettier": "^3.1.0",
9294 "eslint-plugin-react-hooks": "^1.6.0",
src/style/activate.scssnew file+19
@@ -0,0 +1,19 @@
1+.fadeable-enter {
2+ opacity: 0;
3+}
4+
5+.fadeable-enter-active {
6+ opacity: 1;
7+ transition: opacity 300ms;
8+ transition-timing-function: ease-in;
9+}
10+
11+.fadeable-exit {
12+ opacity: 1;
13+}
14+
15+.fadeable-exit-active {
16+ opacity: 0;
17+ transition: opacity 300ms;
18+ transition-timing-function: ease-in;
19+}
src/style/index.scss+3
@@ -12,3 +12,6 @@
1212
1313/* Bridge Styles */
1414@import './bridge.scss';
15+
16+/* Activate Flow animations */
17+@import './activate.scss';
src/views/Activate/ActivateCode.tsx+10−271
@@ -1,78 +1,29 @@
1−import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2−import { Just } from 'folktale/maybe';
3−import * as azimuth from 'azimuth-js';
4−import { Grid, CheckboxInput } from 'indigo-react';
5−import { FORM_ERROR } from 'final-form';
1+import { useCallback, useEffect } from 'react';
62
73import View from 'components/View';
84import { ForwardButton } from 'components/Buttons';
9−import WarningBox from 'components/WarningBox';
105import FooterButton from 'components/FooterButton';
116import ActivateView from './ActivateView';
127
13−import { useNetwork } from 'store/network';
148import { useHistory } from 'store/history';
159
16−import * as need from 'lib/need';
1710import { ROUTE_NAMES } from 'lib/routeNames';
18−import { walletFromMnemonic } from 'lib/wallet';
19−import { DEFAULT_HD_PATH, POINT_DOMINIONS, POINT_PROXIES } from 'lib/constants';
20−import { generateWallet } from 'lib/invite';
21−import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
2211import { useLocalRouter } from 'lib/LocalRouter';
2312import useImpliedTicket from 'lib/useImpliedTicket';
24−import { timeout } from 'lib/timeout';
2513import useHasDisclaimed from 'lib/useHasDisclaimed';
26−import useBreakpoints from 'lib/useBreakpoints';
27−
28−import BridgeForm from 'form/BridgeForm';
29−import SubmitButton from 'form/SubmitButton';
30−import { TicketInput } from 'form/Inputs';
31−import {
32− composeValidator,
33− buildPatqValidator,
34− hasErrors,
35− buildCheckboxValidator,
36−} from 'form/validators';
37−import FormError from 'form/FormError';
3814
3915import { useActivateFlow } from './ActivateFlow';
40−import { WARNING } from 'form/helpers';
41−import { convertToInt } from 'lib/convertToInt';
4216import PointPresenter from './PointPresenter';
43−import useRoller from 'lib/useRoller';
44−import { Ship } from '@urbit/roller-api';
4517import { Box, Text } from '@tlon/indigo-react';
4618import ActivateHeader from './ActivateHeader';
47−import { MasterKey } from './MasterKey';
19+import ActivateCodeForm from './ActivateCodeForm';
4820
4921export default function ActivateCode() {
5022 const history = useHistory();
5123 const { names, push } = useLocalRouter();
52− const { contracts } = useNetwork();
53− const {
54− impliedAzimuthPoint,
55− impliedPatp,
56− impliedTicket,
57− } = useImpliedTicket();
24+ const { impliedPatp, impliedTicket } = useImpliedTicket();
5825 const [hasDisclaimed] = useHasDisclaimed();
59− const didWarn = useRef(false);
60− const { api, getPoints } = useRoller();
61− const [isGenerating, setIsGenerating] = useState<boolean>(false);
62−
63− const {
64− isFaded,
65− setDerivedPoint,
66− setDerivedWallet,
67− setInviteWallet,
68− setIsFaded,
69− } = useActivateFlow();
70− // this is a pretty naive way to detect if we're on a mobile device
71− // (i.e. we're checking the width of the screen)
72− // but it will suffice for the 99% case and if someone wants to get around it
73− // well by golly they're allowed to turn their phone into landscape mode
74− // for this screen
75− const activationAllowed = useBreakpoints([false, true, true]);
26+ const { isIn, setIsIn } = useActivateFlow();
7627
7728 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
7829 history,
@@ -86,215 +37,18 @@ export default function ActivateCode() {
8637 }
8738 }, [hasDisclaimed, names.DISCLAIMER, names.MASTER_KEY, push]);
8839
89− const validateForm = useCallback((values, errors) => {
90− didWarn.current = false;
91−
92− if (hasErrors(errors)) {
93− return errors;
94− }
95− }, []);
96−
97− const validate = useMemo(
98− () =>
99− composeValidator(
100− { ticket: buildPatqValidator(), showTicket: buildCheckboxValidator() },
101− validateForm
102− ),
103− [validateForm]
104− );
105−
106− const loadL1Points = useCallback(
107− async (address: string): Promise<number[]> => {
108− const _contracts = need.contracts(contracts);
109− const owned = await azimuth.azimuth.getOwnedPoints(_contracts, address);
110− const transferring = await azimuth.azimuth.getTransferringFor(
111− _contracts,
112− address
113− );
114− const incoming = [...owned, ...transferring];
115− console.log('incoming L1 points', incoming);
116− return incoming;
117− },
118− [contracts]
119− );
120−
121− const loadl2points = useCallback(
122− async (address: string): Promise<Ship[]> => {
123− const owned = await getPoints(POINT_PROXIES.OWN, address);
124− const transferring = await getPoints(POINT_PROXIES.TRANSFER, address);
125− const incoming = [...owned, ...transferring];
126− console.log('incoming L2 points', incoming);
127− return incoming;
128− },
129− [getPoints]
130− );
131−
132− // derive and set our state on submission
133− const onSubmit = useCallback(
134− async values => {
135− setIsGenerating(true);
136− setIsFaded(true);
137− await timeout(1900); // Allow the Master Key animation to run for one cycle
138−
139− // Derive wallet
140− // TODO: fallback logic to first use implied values if present, otherwise form values?
141− const ticket = impliedTicket || values.ticket;
142− const { seed } = await generateTemporaryOwnershipWallet(ticket);
143− const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
144− setInviteWallet(inviteWallet);
145− const _inviteWallet = need.wallet(inviteWallet);
146− const inviteAddress = _inviteWallet.address;
147−
148− // Query for points depending on point's dominion
149− const rollerPoint = await api.getPoint(impliedAzimuthPoint!);
150− let incoming: number[] | Ship[];
151− if (rollerPoint.dominion === POINT_DOMINIONS.L2) {
152− incoming = await loadl2points(inviteAddress);
153− } else {
154− incoming = await loadL1Points(inviteAddress);
155− }
156−
157− // Set derived
158− if (incoming.length > 0) {
159− if (incoming.length > 1 && !didWarn.current) {
160− didWarn.current = true;
161− setIsGenerating(false);
162−
163− return {
164− [WARNING]:
165− 'This invite code has multiple points available. ' +
166− "Once you've activated this point, " +
167− 'activate the next with the same process.',
168− };
169− }
170−
171− const point = convertToInt(incoming[0], 10);
172−
173− setDerivedPoint(Just(point));
174− setDerivedWallet(Just(await generateWallet(point, true)));
175− } else {
176− setIsGenerating(false);
177−
178− return {
179− [FORM_ERROR]:
180− 'Invite code has no claimable point.\n' +
181− 'Check your invite code and try again?',
182− };
183− }
184− },
185− [
186− api,
187− impliedAzimuthPoint,
188− impliedTicket,
189− loadL1Points,
190− loadl2points,
191− setDerivedPoint,
192− setDerivedWallet,
193− setInviteWallet,
194− setIsFaded,
195− ]
196− );
197−
198− const initialValues = useMemo(
199− () => ({ ticket: impliedTicket || '', showTicket: true }),
200− [impliedTicket]
201− );
202−
40+ // Fade in on load
20341 useEffect(() => {
204− const fetchRollerPoint = async () => {
205− if (!impliedAzimuthPoint) {
206− return;
207− }
208− const p = await api.getPoint(impliedAzimuthPoint!);
209− console.log(p);
210− };
211−
212− fetchRollerPoint();
213− }, [api, impliedAzimuthPoint]);
214−
215− const footer = useMemo(() => {
216− return (
217− <Box
218− display="flex"
219− flexDirection="column"
220− flexWrap="nowrap"
221− height={'100%'}
222− justifyContent="flex-end">
223− <Grid>
224− <BridgeForm
225− validate={validate}
226− onSubmit={onSubmit}
227− afterSubmit={goToMasterKey}
228− initialValues={initialValues}>
229− {({ validating, values, submitting, handleSubmit }) => (
230− <>
231− {!impliedTicket && (
232− <>
233− <Grid.Item
234− full
235− as={TicketInput}
236− type={values.showTicket ? 'text' : 'password'}
237− name="ticket"
238− label="Activation Code"
239− disabled={!activationAllowed}
240− />
241−
242− <Grid.Item
243− full
244− as={CheckboxInput}
245− name="showTicket"
246− label="Show"
247− />
248− </>
249− )}
250−
251− <Grid.Item full as={FormError} />
252−
253− <Grid.Item
254− full
255− as={SubmitButton}
256− className="mt4"
257− handleSubmit={handleSubmit}>
258− {isWarning =>
259− validating
260− ? 'Deriving...'
261− : submitting
262− ? 'Generating...'
263− : isWarning
264− ? 'Continue Activation'
265− : 'Claim'
266− }
267− </Grid.Item>
268−
269− {!activationAllowed && (
270− <Grid.Item full as={WarningBox} className="mt4">
271− For your security, please access Bridge on a desktop device.
272− </Grid.Item>
273− )}
274− </>
275− )}
276− </BridgeForm>
277− </Grid>
278− </Box>
279− );
280− }, [
281− activationAllowed,
282− goToMasterKey,
283− impliedTicket,
284− initialValues,
285− onSubmit,
286− validate,
287− ]);
42+ setIsIn(true);
43+ }, [setIsIn]);
28844
28945 return (
29046 <View inset>
29147 <ActivateView
29248 header={
293− !isGenerating && (
294− <ActivateHeader copy={'Welcome. This is your Urbit.'} />
295− )
49+ <ActivateHeader copy={'Welcome. This is your Urbit.'} isIn={isIn} />
29650 }
297− footer={!isGenerating && footer}>
51+ footer={<ActivateCodeForm afterSubmit={goToMasterKey} />}>
29852 <Box
29953 alignItems={'center'}
30054 display={'flex'}
@@ -305,22 +59,7 @@ export default function ActivateCode() {
30559 {!impliedTicket && (
30660 <Text className="mb2">Enter your activation code to continue.</Text>
30761 )}
308− {!isGenerating && impliedPatp && (
309− <PointPresenter
310− patp={impliedPatp}
311− className={!isFaded ? 'faded-in' : 'faded-out'}
312− />
313− )}
314− <Box
315− display={'flex'}
316− flexDirection={'row'}
317− flexWrap={'nowrap'}
318− className={isFaded ? 'faded-in' : 'faded-out'}
319− width={'80%'}
320− height={'min-content'}
321− justifyContent={'center'}>
322− <MasterKey />
323− </Box>
62+ {impliedPatp && <PointPresenter patp={impliedPatp} isIn={isIn} />}
32463 </Box>
32564 </ActivateView>
32665
src/views/Activate/ActivateCodeForm.tsxnew file+240
@@ -0,0 +1,240 @@
1+import * as azimuth from 'azimuth-js';
2+import * as need from 'lib/need';
3+import { WARNING } from 'form/helpers';
4+import { FORM_ERROR } from 'final-form';
5+import { Just } from 'folktale/maybe';
6+import BridgeForm from 'form/BridgeForm';
7+import SubmitButton from 'form/SubmitButton';
8+import { TicketInput } from 'form/Inputs';
9+import {
10+ composeValidator,
11+ buildPatqValidator,
12+ hasErrors,
13+ buildCheckboxValidator,
14+} from 'form/validators';
15+import FormError from 'form/FormError';
16+import { CheckboxInput, Grid } from 'indigo-react';
17+import { useCallback, useMemo, useRef } from 'react';
18+import { useActivateFlow } from './ActivateFlow';
19+import WarningBox from 'components/WarningBox';
20+import { CSSTransition } from 'react-transition-group';
21+import useRoller from 'lib/useRoller';
22+import useImpliedTicket from 'lib/useImpliedTicket';
23+import {
24+ generateTemporaryOwnershipWallet,
25+ generateWallet,
26+} from 'lib/walletgen';
27+import { walletFromMnemonic } from 'lib/wallet';
28+import { useNetwork } from 'store/network';
29+import { DEFAULT_HD_PATH, POINT_DOMINIONS, POINT_PROXIES } from 'lib/constants';
30+import useBreakpoints from 'lib/useBreakpoints';
31+import { Ship } from '@urbit/roller-api';
32+import { convertToInt } from 'lib/convertToInt';
33+import { Box } from '@tlon/indigo-react';
34+import { timeout } from 'lib/timeout';
35+
36+interface ActivateCodeFormProps {
37+ afterSubmit: VoidFunction;
38+}
39+
40+const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
41+ const { contracts } = useNetwork();
42+ const { api, getPoints } = useRoller();
43+ const { impliedAzimuthPoint, impliedTicket } = useImpliedTicket();
44+ const didWarn = useRef<boolean>(false);
45+
46+ const {
47+ isIn,
48+ setDerivedPoint,
49+ setDerivedWallet,
50+ setInviteWallet,
51+ setIsIn,
52+ } = useActivateFlow();
53+
54+ // this is a pretty naive way to detect if we're on a mobile device
55+ // (i.e. we're checking the width of the screen)
56+ // but it will suffice for the 99% case and if someone wants to get around it
57+ // well by golly they're allowed to turn their phone into landscape mode
58+ // for this screen
59+ const activationAllowed = useBreakpoints([false, true, true]);
60+
61+ const validateForm = useCallback(
62+ (values, errors) => {
63+ didWarn.current = false;
64+
65+ if (hasErrors(errors)) {
66+ return errors;
67+ }
68+ },
69+ [didWarn]
70+ );
71+
72+ const validate = useMemo(
73+ () =>
74+ composeValidator(
75+ { ticket: buildPatqValidator(), showTicket: buildCheckboxValidator() },
76+ validateForm
77+ ),
78+ [validateForm]
79+ );
80+
81+ const loadL1Points = useCallback(
82+ async (address: string): Promise<number[]> => {
83+ const _contracts = need.contracts(contracts);
84+ const owned = await azimuth.azimuth.getOwnedPoints(_contracts, address);
85+ const transferring = await azimuth.azimuth.getTransferringFor(
86+ _contracts,
87+ address
88+ );
89+ const incoming = [...owned, ...transferring];
90+ console.log('incoming L1 points', incoming);
91+ return incoming;
92+ },
93+ [contracts]
94+ );
95+
96+ const loadl2points = useCallback(
97+ async (address: string): Promise<Ship[]> => {
98+ const owned = await getPoints(POINT_PROXIES.OWN, address);
99+ const transferring = await getPoints(POINT_PROXIES.TRANSFER, address);
100+ const incoming = [...owned, ...transferring];
101+ console.log('incoming L2 points', incoming);
102+ return incoming;
103+ },
104+ [getPoints]
105+ );
106+
107+ // derive and set our state on submission
108+ const onSubmit = useCallback(
109+ async values => {
110+ // Derive wallet
111+ const ticket = impliedTicket || values.ticket;
112+ const { seed } = await generateTemporaryOwnershipWallet(ticket);
113+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
114+ setInviteWallet(inviteWallet);
115+ const _inviteWallet = need.wallet(inviteWallet);
116+ const inviteAddress = _inviteWallet.address;
117+
118+ // Query for points depending on point's dominion
119+ const rollerPoint = await api.getPoint(impliedAzimuthPoint!);
120+ let incoming: number[] | Ship[];
121+ if (rollerPoint.dominion === POINT_DOMINIONS.L2) {
122+ incoming = await loadl2points(inviteAddress);
123+ } else {
124+ incoming = await loadL1Points(inviteAddress);
125+ }
126+
127+ // Set derived
128+ if (incoming.length > 0) {
129+ if (incoming.length > 1 && !didWarn.current) {
130+ didWarn.current = true;
131+
132+ return {
133+ [WARNING]:
134+ 'This invite code has multiple points available. ' +
135+ "Once you've activated this point, " +
136+ 'activate the next with the same process.',
137+ };
138+ }
139+
140+ const point = convertToInt(incoming[0], 10);
141+
142+ setDerivedPoint(Just(point));
143+ setDerivedWallet(Just(await generateWallet(point, ticket, true)));
144+ setIsIn(false);
145+ await timeout(100);
146+ } else {
147+ return {
148+ [FORM_ERROR]:
149+ 'Invite code has no claimable point.\n' +
150+ 'Check your invite code and try again?',
151+ };
152+ }
153+ },
154+ [
155+ api,
156+ didWarn,
157+ impliedAzimuthPoint,
158+ impliedTicket,
159+ loadL1Points,
160+ loadl2points,
161+ setDerivedPoint,
162+ setDerivedWallet,
163+ setInviteWallet,
164+ ]
165+ );
166+
167+ const initialValues = useMemo(
168+ () => ({ ticket: impliedTicket || '', showTicket: true }),
169+ [impliedTicket]
170+ );
171+
172+ return (
173+ <CSSTransition classNames="fadeable" timeout={300} in={isIn}>
174+ <Box
175+ display="flex"
176+ flexDirection="column"
177+ flexWrap="nowrap"
178+ height={'100%'}
179+ justifyContent="flex-end">
180+ <Grid>
181+ <BridgeForm
182+ validate={validate}
183+ onSubmit={onSubmit}
184+ afterSubmit={afterSubmit}
185+ initialValues={initialValues}>
186+ {({ validating, values, submitting, handleSubmit }) => (
187+ <>
188+ {!impliedTicket && (
189+ <>
190+ <Grid.Item
191+ full
192+ as={TicketInput}
193+ type={values.showTicket ? 'text' : 'password'}
194+ name="ticket"
195+ label="Activation Code"
196+ disabled={!activationAllowed}
197+ />
198+
199+ <Grid.Item
200+ full
201+ as={CheckboxInput}
202+ name="showTicket"
203+ label="Show"
204+ />
205+ </>
206+ )}
207+
208+ <Grid.Item full as={FormError} />
209+
210+ <Grid.Item
211+ full
212+ as={SubmitButton}
213+ className="mt4"
214+ handleSubmit={handleSubmit}>
215+ {isWarning =>
216+ validating
217+ ? 'Deriving...'
218+ : submitting
219+ ? 'Generating...'
220+ : isWarning
221+ ? 'Continue Activation'
222+ : 'Claim'
223+ }
224+ </Grid.Item>
225+
226+ {!activationAllowed && (
227+ <Grid.Item full as={WarningBox} className="mt4">
228+ For your security, please access Bridge on a desktop device.
229+ </Grid.Item>
230+ )}
231+ </>
232+ )}
233+ </BridgeForm>
234+ </Grid>
235+ </Box>
236+ </CSSTransition>
237+ );
238+};
239+
240+export default ActivateCodeForm;
src/views/Activate/ActivateHeader.tsx+17−13
@@ -1,23 +1,27 @@
11import { Box, Text } from '@tlon/indigo-react';
2+import { CSSTransition } from 'react-transition-group';
23
34type ActivateHeaderProps = {
45 copy: string;
6+ isIn: boolean;
57};
68
7−const ActivateHeader = ({ copy }: ActivateHeaderProps) => {
9+const ActivateHeader = ({ copy, isIn = false }: ActivateHeaderProps) => {
810 return (
9− <Box>
10− <Text
11− display="block"
12− fontFamily="Inter"
13− fontSize={3}
14− fontWeight="medium"
15− mx={3}
16− my={3}
17− textAlign="center">
18− {copy}
19− </Text>
20− </Box>
11+ <CSSTransition in={isIn} classNames="fadeable" timeout={300}>
12+ <Box>
13+ <Text
14+ display="block"
15+ fontFamily="Inter"
16+ fontSize={3}
17+ fontWeight="medium"
18+ mx={3}
19+ my={3}
20+ textAlign="center">
21+ {copy}
22+ </Text>
23+ </Box>
24+ </CSSTransition>
2125 );
2226};
2327
src/views/Activate/MasterKeyDownload.tsx+3
@@ -9,6 +9,7 @@ import { ActivateSteps } from './ActivateSteps';
99import { useActivateFlow } from './ActivateFlow';
1010import { timeout } from 'lib/timeout';
1111import MasterKeyPresenter from './MasterKeyPresenter';
12+import { CopyButton } from 'components/Buttons';
1213
1314const MasterKeyDownload = () => {
1415 const { isFaded, setIsFaded } = useActivateFlow();
@@ -70,6 +71,7 @@ const MasterKeyDownload = () => {
7071 flexWrap={'nowrap'}
7172 height={'100%'}
7273 justifyContent={'center'}>
74+ {/* AlertBox */}
7375 <Box
7476 display={'flex'}
7577 flexDirection={'row'}
@@ -79,6 +81,7 @@ const MasterKeyDownload = () => {
7981 justifyContent={'center'}>
8082 <MasterKeyPresenter />
8183 </Box>
84+ {/* <CopyButton text={'test'} /> */}
8285 </Box>
8386 </ActivateView>
8487 <ActivateSteps currentStep={1} totalSteps={4} />
src/views/Activate/MasterKeyPresenter.tsx+1−1
@@ -37,7 +37,7 @@ const MasterKeyPresenter = ({ className }: MasterKeyPresenterArgs) => {
3737 {segment}
3838 </Box>
3939 {i < ticketSegments.length - 1 ? (
40− <Text alignSelf="center" color="gray" mx={1}>
40+ <Text key={i} alignSelf="center" color="gray" mx={1}>
4141 —
4242 </Text>
4343 ) : null}
src/views/Activate/PointPresenter.tsx+43−35
@@ -1,51 +1,59 @@
11import { Box, Text } from '@tlon/indigo-react';
2+import { CSSTransition } from 'react-transition-group';
23import Sigil from 'components/Sigil';
34
45type PointPresenterArgs = {
56 patp: string;
7+ isIn: boolean;
68 className?: string;
79};
810
9−const PointPresenter = ({ patp, className }: PointPresenterArgs) => {
11+const PointPresenter = ({
12+ patp,
13+ isIn = false,
14+ className,
15+}: PointPresenterArgs) => {
1016 return patp ? (
1117 <>
12− <Box
13− width={256}
14− height={256}
15− display="flex"
16− flexDirection="column"
17− flexWrap="nowrap"
18− justifyContent="center"
19− alignItems="center"
20− background="white"
21− className={className}>
18+ <CSSTransition in={isIn} classNames="fadeable" timeout={300}>
2219 <Box
23− background="black"
24− borderRadius={5}
25− padding={32}
26− mb={3}
27− width={64}
28− height={64}>
29− <Sigil
30− patp={patp}
31− size={64}
32− display="block"
33− colors={['black', 'white']}
34− icon
35− padding={32}
20+ width={256}
21+ height={256}
22+ display="flex"
23+ flexDirection="column"
24+ flexWrap="nowrap"
25+ justifyContent="center"
26+ alignItems="center"
27+ background="white"
28+ className={className}>
29+ <Box
30+ background="black"
3631 borderRadius={5}
37− />
38− </Box>
39− <Box>
40− <Text
41− display="block"
42− fontFamily={'Source Code Pro'}
43− fontSize={'18px'}
44− textAlign={'center'}>
45− {patp}
46− </Text>
32+ padding={32}
33+ mb={3}
34+ width={64}
35+ height={64}>
36+ <Sigil
37+ patp={patp}
38+ size={64}
39+ display="block"
40+ colors={['black', 'white']}
41+ icon
42+ padding={32}
43+ borderRadius={5}
44+ />
45+ </Box>
46+ <Box>
47+ <Text
48+ display="block"
49+ fontFamily={'Source Code Pro'}
50+ fontSize={'18px'}
51+ textAlign={'center'}>
52+ {patp}
53+ </Text>
54+ </Box>
4755 </Box>
48− </Box>
56+ </CSSTransition>
4957 </>
5058 ) : null;
5159};
src/views/Activate/useActivateFlowState.js+3
@@ -8,6 +8,7 @@ export default function useActivateFlowState() {
88 const [generated, setGenerated] = useState(false);
99 const [incomingPoints, setIncomingPoints] = useState(Nothing());
1010 const [isFaded, setIsFaded] = useState(false);
11+ const [isIn, setIsIn] = useState(false);
1112
1213 const reset = useCallback(() => {
1314 setDerivedWallet(Nothing());
@@ -37,6 +38,8 @@ export default function useActivateFlowState() {
3738 setIncomingPoints,
3839 isFaded,
3940 setIsFaded,
41+ isIn,
42+ setIsIn,
4043 reset,
4144 };
4245}