on master help sign in
WIP: refactor fade animation behavior
754a6actomholford
src/lib/constants.ts+5−1
@@ -118,7 +118,10 @@ const POINT_PROXIES = {
118 TRANSFER: 'transfer',
118 TRANSFER: 'transfer',
119 VOTE: 'vote',
119 VOTE: 'vote',
120 SPAWN: 'spawn',
120 SPAWN: 'spawn',
121−}
121+};
122+
123+// In ms
124+const DEFAULT_FADE_TIMEOUT = 300;
122
125
123export {
126export {
124 CHECK_BLOCK_EVERY_MS,
127 CHECK_BLOCK_EVERY_MS,
@@ -150,4 +153,5 @@ export {
150 ROLLER_HOSTS,
153 ROLLER_HOSTS,
151 POINT_DOMINIONS,
154 POINT_DOMINIONS,
152 POINT_PROXIES,
155 POINT_PROXIES,
156+ DEFAULT_FADE_TIMEOUT,
153};
157};
src/style/activate.scss+10
@@ -17,3 +17,13 @@
17 transition: opacity 300ms;
17 transition: opacity 300ms;
18 transition-timing-function: ease-in;
18 transition-timing-function: ease-in;
19}
19}
20+
21+.fadeable-appear {
22+ opacity: 0;
23+}
24+
25+.fadeable-appear-active {
26+ opacity: 1;
27+ transition: opacity 300ms;
28+ transition-timing-function: ease-in;
29+}
src/style/master-key.scss+12−17
@@ -1,12 +1,7 @@
1−// Note the 20% - 100% range is a fake set of keyframes to simulate a delay between iterations
2−// See: https://css-tricks.com/css-keyframe-animation-delay-iterations/
3@keyframes opacity-fill {
1@keyframes opacity-fill {
4 0% {
2 0% {
5 fill-opacity: 0.2;
3 fill-opacity: 0.2;
6 }
4 }
7− 20% {
8− fill-opacity: 1.0;
9− }
10 100% {
5 100% {
11 fill-opacity: 1.0;
6 fill-opacity: 1.0;
12 }
7 }
@@ -14,60 +9,60 @@
14
9
15svg.master-key {
10svg.master-key {
16 .first-one {
11 .first-one {
17− animation: opacity-fill 2000ms linear forwards infinite;
12+ animation: opacity-fill 200ms linear forwards;
18 animation-delay: 100ms;
13 animation-delay: 100ms;
19 }
14 }
20
15
21 .first-two {
16 .first-two {
22− animation: opacity-fill 2000ms linear forwards infinite;
17+ animation: opacity-fill 200ms linear forwards;
23 animation-delay: 200ms;
18 animation-delay: 200ms;
24 }
19 }
25
20
26 .first-three {
21 .first-three {
27− animation: opacity-fill 2000ms linear forwards infinite;
22+ animation: opacity-fill 200ms linear forwards;
28 animation-delay: 300ms;
23 animation-delay: 300ms;
29
24
30 }
25 }
31
26
32 .second-one {
27 .second-one {
33− animation: opacity-fill 2000ms linear forwards infinite;
28+ animation: opacity-fill 200ms linear forwards;
34 animation-delay: 400ms;
29 animation-delay: 400ms;
35 }
30 }
36 .second-two {
31 .second-two {
37− animation: opacity-fill 2000ms linear forwards infinite;
32+ animation: opacity-fill 200ms linear forwards;
38 animation-delay: 500ms;
33 animation-delay: 500ms;
39 }
34 }
40
35
41 .second-three {
36 .second-three {
42− animation: opacity-fill 2000ms linear forwards infinite;
37+ animation: opacity-fill 200ms linear forwards;
43 animation-delay: 600ms;
38 animation-delay: 600ms;
44 }
39 }
45
40
46 .third-one {
41 .third-one {
47− animation: opacity-fill 2000ms linear forwards infinite;
42+ animation: opacity-fill 200ms linear forwards;
48 animation-delay: 700ms;
43 animation-delay: 700ms;
49 }
44 }
50 .third-two {
45 .third-two {
51− animation: opacity-fill 2000ms linear forwards infinite;
46+ animation: opacity-fill 200ms linear forwards;
52 animation-delay: 800ms;
47 animation-delay: 800ms;
53 }
48 }
54 .third-three {
49 .third-three {
55− animation: opacity-fill 2000ms linear forwards infinite;
50+ animation: opacity-fill 200ms linear forwards;
56 animation-delay: 900ms;
51 animation-delay: 900ms;
57 }
52 }
58
53
59 .fourth-one {
54 .fourth-one {
60− animation: opacity-fill 2000ms linear forwards infinite;
55+ animation: opacity-fill 200ms linear forwards;
61 animation-delay: 1s;
56 animation-delay: 1s;
62 }
57 }
63
58
64 .fourth-two {
59 .fourth-two {
65− animation: opacity-fill 2000ms linear forwards infinite;
60+ animation: opacity-fill 200ms linear forwards;
66 animation-delay: 1.1s;
61 animation-delay: 1.1s;
67 }
62 }
68
63
69 .fourth-three {
64 .fourth-three {
70− animation: opacity-fill 2000ms linear forwards infinite;
65+ animation: opacity-fill 200ms linear forwards;
71 animation-delay: 1.2s;
66 animation-delay: 1.2s;
72 }
67 }
73
68
src/views/Activate/ActivateButton.tsxnew file+35
@@ -0,0 +1,35 @@
1+import { Box, Button } from '@tlon/indigo-react';
2+import { MouseEventHandler } from 'react';
3+import withFadeable from './withFadeable';
4+
5+interface ActivateButtonProps {
6+ children: React.ReactNode | string;
7+ onClick: MouseEventHandler;
8+}
9+
10+const ActivateButton = ({ children, onClick }: ActivateButtonProps) => {
11+ return (
12+ <Box
13+ display="flex"
14+ flexDirection="column"
15+ flexWrap="nowrap"
16+ height={'100%'}
17+ justifyContent="flex-end">
18+ <Button
19+ onClick={onClick}
20+ backgroundColor="black"
21+ color={'white'}
22+ padding={'16px'}
23+ fontFamily="Inter"
24+ height={'50px'}
25+ fontWeight={'400'}
26+ fontSize={'18px'}>
27+ {children}
28+ </Button>
29+ </Box>
30+ );
31+};
32+
33+export default ActivateButton;
34+
35+export const FadeableActivateButton = withFadeable(ActivateButton);
src/views/Activate/ActivateCode.tsx+8−13
@@ -1,4 +1,4 @@
1−import { useCallback, useEffect } from 'react';
1+import { useCallback } from 'react';
2
2
3import View from 'components/View';
3import View from 'components/View';
4import { ForwardButton } from 'components/Buttons';
4import { ForwardButton } from 'components/Buttons';
@@ -12,18 +12,17 @@ import { useLocalRouter } from 'lib/LocalRouter';
12import useImpliedTicket from 'lib/useImpliedTicket';
12import useImpliedTicket from 'lib/useImpliedTicket';
13import useHasDisclaimed from 'lib/useHasDisclaimed';
13import useHasDisclaimed from 'lib/useHasDisclaimed';
14
14
15−import { useActivateFlow } from './ActivateFlow';
15+import { FadeablePointPresenter as PointPresenter } from './PointPresenter';
16−import PointPresenter from './PointPresenter';
17import { Box, Text } from '@tlon/indigo-react';
16import { Box, Text } from '@tlon/indigo-react';
18−import ActivateHeader from './ActivateHeader';
17+import { FadeableActivateHeader as ActivateHeader } from './ActivateHeader';
19−import ActivateCodeForm from './ActivateCodeForm';
18+import { FadeableActivateCodeForm as ActivateCodeForm } from './ActivateCodeForm';
19+import useFadeIn from './useFadeIn';
20
20
21export default function ActivateCode() {
21export default function ActivateCode() {
22 const history = useHistory();
22 const history = useHistory();
23 const { names, push } = useLocalRouter();
23 const { names, push } = useLocalRouter();
24 const { impliedPatp, impliedTicket } = useImpliedTicket();
24 const { impliedPatp, impliedTicket } = useImpliedTicket();
25 const [hasDisclaimed] = useHasDisclaimed();
25 const [hasDisclaimed] = useHasDisclaimed();
26− const { isIn, setIsIn } = useActivateFlow();
27
26
28 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
27 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
29 history,
28 history,
@@ -38,16 +37,12 @@ export default function ActivateCode() {
38 }, [hasDisclaimed, names.DISCLAIMER, names.MASTER_KEY, push]);
37 }, [hasDisclaimed, names.DISCLAIMER, names.MASTER_KEY, push]);
39
38
40 // Fade in on load
39 // Fade in on load
41− useEffect(() => {
40+ useFadeIn();
42− setIsIn(true);
43− }, [setIsIn]);
44
41
45 return (
42 return (
46 <View inset>
43 <View inset>
47 <ActivateView
44 <ActivateView
48− header={
49− <ActivateHeader copy={'Welcome. This is your Urbit.'} isIn={isIn} />
45+ header={<ActivateHeader copy={'Welcome. This is your Urbit.'} />}
50− }
51 footer={<ActivateCodeForm afterSubmit={goToMasterKey} />}>
46 footer={<ActivateCodeForm afterSubmit={goToMasterKey} />}>
52 <Box
47 <Box
53 alignItems={'center'}
48 alignItems={'center'}
@@ -59,7 +54,7 @@ export default function ActivateCode() {
59 {!impliedTicket && (
54 {!impliedTicket && (
60 <Text className="mb2">Enter your activation code to continue.</Text>
55 <Text className="mb2">Enter your activation code to continue.</Text>
61 )}
56 )}
62− {impliedPatp && <PointPresenter patp={impliedPatp} isIn={isIn} />}
57+ {impliedPatp && <PointPresenter patp={impliedPatp} />}
63 </Box>
58 </Box>
64 </ActivateView>
59 </ActivateView>
65
60
src/views/Activate/ActivateCodeForm.tsx+68−64
@@ -32,6 +32,7 @@ import { Ship } from '@urbit/roller-api';
32import { convertToInt } from 'lib/convertToInt';
32import { convertToInt } from 'lib/convertToInt';
33import { Box } from '@tlon/indigo-react';
33import { Box } from '@tlon/indigo-react';
34import { timeout } from 'lib/timeout';
34import { timeout } from 'lib/timeout';
35+import withFadeable from './withFadeable';
35
36
36interface ActivateCodeFormProps {
37interface ActivateCodeFormProps {
37 afterSubmit: VoidFunction;
38 afterSubmit: VoidFunction;
@@ -107,6 +108,9 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
107 // derive and set our state on submission
108 // derive and set our state on submission
108 const onSubmit = useCallback(
109 const onSubmit = useCallback(
109 async values => {
110 async values => {
111+ // Allow UI animation to start
112+ await timeout(42);
113+
110 // Derive wallet
114 // Derive wallet
111 const ticket = impliedTicket || values.ticket;
115 const ticket = impliedTicket || values.ticket;
112 const { seed } = await generateTemporaryOwnershipWallet(ticket);
116 const { seed } = await generateTemporaryOwnershipWallet(ticket);
@@ -153,7 +157,6 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
153 },
157 },
154 [
158 [
155 api,
159 api,
156− didWarn,
157 impliedAzimuthPoint,
160 impliedAzimuthPoint,
158 impliedTicket,
161 impliedTicket,
159 loadL1Points,
162 loadL1Points,
@@ -161,6 +164,7 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
161 setDerivedPoint,
164 setDerivedPoint,
162 setDerivedWallet,
165 setDerivedWallet,
163 setInviteWallet,
166 setInviteWallet,
167+ setIsIn,
164 ]
168 ]
165 );
169 );
166
170
@@ -170,71 +174,71 @@ const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
170 );
174 );
171
175
172 return (
176 return (
173− <CSSTransition classNames="fadeable" timeout={300} in={isIn}>
174− <Box
177+ <Box
175− display="flex"
178+ display="flex"
176− flexDirection="column"
179+ flexDirection="column"
177− flexWrap="nowrap"
180+ flexWrap="nowrap"
178− height={'100%'}
181+ height={'100%'}
179− justifyContent="flex-end">
182+ justifyContent="flex-end">
180− <Grid>
183+ <Grid>
181− <BridgeForm
184+ <BridgeForm
182− validate={validate}
185+ validate={validate}
183− onSubmit={onSubmit}
186+ onSubmit={onSubmit}
184− afterSubmit={afterSubmit}
187+ afterSubmit={afterSubmit}
185− initialValues={initialValues}>
188+ initialValues={initialValues}>
186− {({ validating, values, submitting, handleSubmit }) => (
189+ {({ validating, values, submitting, handleSubmit }) => (
187− <>
190+ <>
188− {!impliedTicket && (
191+ {!impliedTicket && (
189− <>
192+ <>
190− <Grid.Item
193+ <Grid.Item
191− full
194+ full
192− as={TicketInput}
195+ as={TicketInput}
193− type={values.showTicket ? 'text' : 'password'}
196+ type={values.showTicket ? 'text' : 'password'}
194− name="ticket"
197+ name="ticket"
195− label="Activation Code"
198+ label="Activation Code"
196− disabled={!activationAllowed}
199+ disabled={!activationAllowed}
197− />
200+ />
198−
201+
199− <Grid.Item
202+ <Grid.Item
200− full
203+ full
201− as={CheckboxInput}
204+ as={CheckboxInput}
202− name="showTicket"
205+ name="showTicket"
203− label="Show"
206+ label="Show"
204− />
207+ />
205− </>
208+ </>
206− )}
209+ )}
207−
210+
208− <Grid.Item full as={FormError} />
211+ <Grid.Item full as={FormError} />
209−
212+
210− <Grid.Item
213+ <Grid.Item
211− full
214+ full
212− as={SubmitButton}
215+ as={SubmitButton}
213− className="mt4"
216+ className="mt4"
214− handleSubmit={handleSubmit}>
217+ handleSubmit={handleSubmit}>
215− {isWarning =>
218+ {isWarning =>
216− validating
219+ validating
217− ? 'Deriving...'
220+ ? 'Deriving...'
218− : submitting
221+ : submitting
219− ? 'Generating...'
222+ ? 'Generating...'
220− : isWarning
223+ : isWarning
221− ? 'Continue Activation'
224+ ? 'Continue Activation'
222− : 'Claim'
225+ : 'Claim'
223− }
226+ }
227+ </Grid.Item>
228+
229+ {!activationAllowed && (
230+ <Grid.Item full as={WarningBox} className="mt4">
231+ For your security, please access Bridge on a desktop device.
224 </Grid.Item>
232 </Grid.Item>
225−
233+ )}
226− {!activationAllowed && (
234+ </>
227− <Grid.Item full as={WarningBox} className="mt4">
235+ )}
228− For your security, please access Bridge on a desktop device.
229− </Grid.Item>
230− )}
231− </>
232− )}
233− </BridgeForm>
236+ </BridgeForm>
234− </Grid>
237+ </Grid>
235− </Box>
238+ </Box>
236− </CSSTransition>
237 );
239 );
238};
240};
239
241
240export default ActivateCodeForm;
242export default ActivateCodeForm;
243+
244+export const FadeableActivateCodeForm = withFadeable(ActivateCodeForm);
src/views/Activate/ActivateHeader.tsx+22−16
@@ -1,28 +1,34 @@
1import { Box, Text } from '@tlon/indigo-react';
1import { Box, Text } from '@tlon/indigo-react';
2+import { DEFAULT_FADE_TIMEOUT } from 'lib/constants';
2import { CSSTransition } from 'react-transition-group';
3import { CSSTransition } from 'react-transition-group';
4+import { useActivateFlow } from './ActivateFlow';
5+import withFadeable from './withFadeable';
3
6
4type ActivateHeaderProps = {
7type ActivateHeaderProps = {
5 copy: string;
8 copy: string;
9+ fadeTimeout?: number;
6− isIn: boolean;
10+ isInOverride?: boolean;
7};
11};
8
12
9−const ActivateHeader = ({ copy, isIn = false }: ActivateHeaderProps) => {
13+const ActivateHeader = ({ copy }: ActivateHeaderProps) => {
14+ const { isIn } = useActivateFlow();
15+
10 return (
16 return (
11− <CSSTransition in={isIn} classNames="fadeable" timeout={300}>
12− <Box>
17+ <Box>
13− <Text
18+ <Text
14− display="block"
19+ display="block"
15− fontFamily="Inter"
20+ fontFamily="Inter"
16− fontSize={3}
21+ fontSize={3}
17− fontWeight="medium"
22+ fontWeight="medium"
18− mx={3}
23+ mx={3}
19− my={3}
24+ my={3}
20− textAlign="center">
25+ textAlign="center">
21− {copy}
26+ {copy}
22− </Text>
27+ </Text>
23− </Box>
28+ </Box>
24− </CSSTransition>
25 );
29 );
26};
30};
27
31
28export default ActivateHeader;
32export default ActivateHeader;
33+
34+export const FadeableActivateHeader = withFadeable(ActivateHeader);
src/views/Activate/ActivateMasterKey.tsx−7
@@ -1,18 +1,12 @@
1−import React, { useMemo } from 'react';
2−
3import { LocalRouterProvider, useLocalRouter } from 'lib/LocalRouter';
1import { LocalRouterProvider, useLocalRouter } from 'lib/LocalRouter';
4
2
5import View from 'components/View';
3import View from 'components/View';
6
4
7−import { useActivateFlow } from './ActivateFlow';
8import useRouter from 'lib/useRouter';
5import useRouter from 'lib/useRouter';
9
6
10import MasterKeyDownload from './MasterKeyDownload';
7import MasterKeyDownload from './MasterKeyDownload';
11import PassportVerify from './PassportVerify';
8import PassportVerify from './PassportVerify';
12import PassportTransfer from './PassportTransfer';
9import PassportTransfer from './PassportTransfer';
13−import { walletFromMnemonic } from 'lib/wallet';
14−import { DEFAULT_HD_PATH } from 'lib/constants';
15−import { publicToAddress } from 'lib/utils/address';
16import MasterKeyReveal from './MasterKeyReveal';
10import MasterKeyReveal from './MasterKeyReveal';
17
11
18const NAMES = {
12const NAMES = {
@@ -30,7 +24,6 @@ const VIEWS = {
30};
24};
31
25
32export default function ActivateMasterKey() {
26export default function ActivateMasterKey() {
33− const { derivedPoint, generated, derivedWallet } = useActivateFlow();
34 // pull the reset function out of the 'activate' router
27 // pull the reset function out of the 'activate' router
35 const { reset: resetActivateRouter } = useLocalRouter();
28 const { reset: resetActivateRouter } = useLocalRouter();
36 const { Route, ...router } = useRouter({
29 const { Route, ...router } = useRouter({
src/views/Activate/ActivateParagraph.tsx+4
@@ -1,7 +1,9 @@
1import { Box, Text } from '@tlon/indigo-react';
1import { Box, Text } from '@tlon/indigo-react';
2+import withFadeable from './withFadeable';
2
3
3type ActivateParagraphProps = {
4type ActivateParagraphProps = {
4 copy: string;
5 copy: string;
6+ fadeTimeout?: number;
5};
7};
6
8
7const ActivateParagraph = ({ copy }: ActivateParagraphProps) => {
9const ActivateParagraph = ({ copy }: ActivateParagraphProps) => {
@@ -27,3 +29,5 @@ const ActivateParagraph = ({ copy }: ActivateParagraphProps) => {
27};
29};
28
30
29export default ActivateParagraph;
31export default ActivateParagraph;
32+
33+export const FadeableActivateParagraph = withFadeable(ActivateParagraph);
src/views/Activate/ActivateSteps.tsx+11−6
@@ -1,4 +1,5 @@
1import { Box } from '@tlon/indigo-react';
1import { Box } from '@tlon/indigo-react';
2+import withFadeable from './withFadeable';
2
3
3type ActivateStepsProps = {
4type ActivateStepsProps = {
4 currentStep: number;
5 currentStep: number;
@@ -14,18 +15,20 @@ export const ActivateSteps = ({
14 const renderSteps = () => {
15 const renderSteps = () => {
15 let steps = [];
16 let steps = [];
16 for (let i = 0; i < totalSteps; i++) {
17 for (let i = 0; i < totalSteps; i++) {
18+ const isFirstStep = i === 0;
19+ const isLastStep = i === totalSteps - 1;
17 steps.push(
20 steps.push(
18 <Box
21 <Box
19− borderTopLeftRadius={i === 0 ? '5px' : null}
22+ borderTopLeftRadius={isFirstStep ? '5px' : null}
20− borderBottomLeftRadius={i === 0 ? '5px' : null}
23+ borderBottomLeftRadius={isFirstStep ? '5px' : null}
21− borderTopRightRadius={i === totalSteps - 1 ? '5px' : null}
24+ borderTopRightRadius={isLastStep ? '5px' : null}
22− borderBottomRightRadius={i === totalSteps - 1 ? '5px' : null}
25+ borderBottomRightRadius={isLastStep ? '5px' : null}
23 key={i}
26 key={i}
24 backgroundColor={i <= currentStep ? 'black' : 'gray'}
27 backgroundColor={i <= currentStep ? 'black' : 'gray'}
25 height={'10px'}
28 height={'10px'}
26 width={`${100 / totalSteps}%`}
29 width={`${100 / totalSteps}%`}
27− marginLeft={'2px'}
30+ marginLeft={isFirstStep ? null : '2px'}
28− marginRight={'2px'}></Box>
31+ marginRight={isLastStep ? null : '2px'}></Box>
29 );
32 );
30 }
33 }
31 return steps;
34 return steps;
@@ -42,3 +45,5 @@ export const ActivateSteps = ({
42 </Box>
45 </Box>
43 );
46 );
44};
47};
48+
49+export const FadeableActivateSteps = withFadeable(ActivateSteps);
src/views/Activate/MasterKeyDownload.tsx+13−41
@@ -1,22 +1,18 @@
1−import React, { useCallback, useEffect, useMemo, useState } from 'react';
1+import React, { useMemo } from 'react';
2
2
3import ActivateView from './ActivateView';
3import ActivateView from './ActivateView';
4−import ActivateHeader from './ActivateHeader';
4+import { FadeableActivateHeader as ActivateHeader } from './ActivateHeader';
5−import { MasterKey } from './MasterKey';
6−import { Box, Button, Icon } from '@tlon/indigo-react';
5+import { Box } from '@tlon/indigo-react';
7import ActivateParagraph from './ActivateParagraph';
6import ActivateParagraph from './ActivateParagraph';
8−import { ActivateSteps } from './ActivateSteps';
7+import { FadeableActivateSteps as ActivateSteps } from './ActivateSteps';
9−import { useActivateFlow } from './ActivateFlow';
10−import { timeout } from 'lib/timeout';
11import MasterKeyPresenter from './MasterKeyPresenter';
8import MasterKeyPresenter from './MasterKeyPresenter';
12−import { CopyButton } from 'components/Buttons';
9+import { FadeableActivateButton as ActivateButton } from './ActivateButton';
10+import useFadeIn from './useFadeIn';
13
11
14const MasterKeyDownload = () => {
12const MasterKeyDownload = () => {
15− const { isFaded, setIsFaded } = useActivateFlow();
16−
17 const header = useMemo(() => {
13 const header = useMemo(() => {
18 return (
14 return (
19− <Box className={isFaded ? 'faded-in' : 'faded-out'}>
15+ <Box>
20 <ActivateHeader copy={'Backup your Master Key.'} />
16 <ActivateHeader copy={'Backup your Master Key.'} />
21 <ActivateParagraph
17 <ActivateParagraph
22 copy={
18 copy={
@@ -25,41 +21,17 @@ const MasterKeyDownload = () => {
25 />
21 />
26 </Box>
22 </Box>
27 );
23 );
28− }, [isFaded]);
24+ }, []);
29
25
30 const footer = useMemo(() => {
26 const footer = useMemo(() => {
31 return (
27 return (
32− <Box
33− display="flex"
34− flexDirection="column"
35− flexWrap="nowrap"
36− height={'100%'}
37− className={isFaded ? 'faded-in' : 'faded-out'}
38− justifyContent="flex-end">
39− <Button
40− onClick={() => console.log('reveal')}
28+ <ActivateButton onClick={() => console.log('download')}>
41− backgroundColor="black"
42− color={'white'}
43− padding={'16px'}
44− fontFamily="Inter"
45− height={'50px'}
46− fontWeight={'400'}
47− fontSize={'18px'}>
48− Download Backup
29+ Download Backup
49− </Button>
30+ </ActivateButton>
50− </Box>
51 );
31 );
52− }, [isFaded]);
32+ }, []);
53−
54− const onViewTransition = useCallback(async () => {
55− setIsFaded(true);
56− await timeout(500); // Pause for UI fade animation
57− }, [setIsFaded]);
58
33
59− useEffect(() => {
34+ useFadeIn();
60− // Fade in content
61− onViewTransition();
62− }, [onViewTransition]);
63
35
64 return (
36 return (
65 <>
37 <>
src/views/Activate/MasterKeyReveal.tsx+39−52
@@ -1,32 +1,35 @@
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
1import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
3import ActivateView from './ActivateView';
3import ActivateView from './ActivateView';
4−import ActivateHeader from './ActivateHeader';
4+import { FadeableActivateHeader as ActivateHeader } from './ActivateHeader';
5import { MasterKey } from './MasterKey';
5import { MasterKey } from './MasterKey';
6−import { Box, Button, Icon } from '@tlon/indigo-react';
6+import { Box, Icon } from '@tlon/indigo-react';
7−import ActivateParagraph from './ActivateParagraph';
7+import { FadeableActivateParagraph as ActivateParagraph } from './ActivateParagraph';
8−import { ActivateSteps } from './ActivateSteps';
8+import { FadeableActivateSteps as ActivateSteps } from './ActivateSteps';
9import { useActivateFlow } from './ActivateFlow';
9import { useActivateFlow } from './ActivateFlow';
10import { timeout } from 'lib/timeout';
10import { timeout } from 'lib/timeout';
11import { useLocalRouter } from 'lib/LocalRouter';
11import { useLocalRouter } from 'lib/LocalRouter';
12+import { FadeableActivateButton as ActivateButton } from './ActivateButton';
12
13
13const MasterKeyReveal = () => {
14const MasterKeyReveal = () => {
14− const { isFaded, setIsFaded } = useActivateFlow();
15+ const { isIn, setIsIn } = useActivateFlow();
15 const { push, names } = useLocalRouter();
16 const { push, names } = useLocalRouter();
17+ const [showMasterKey, setShowMasterKey] = useState<boolean>(false);
18+ const [triggerAnimation, setTriggerAnimation] = useState<boolean>(false);
16
19
17 const goToDownload = useCallback(() => {
20 const goToDownload = useCallback(() => {
18 push(names.DOWNLOAD);
21 push(names.DOWNLOAD);
19 }, [names.DOWNLOAD, push]);
22 }, [names.DOWNLOAD, push]);
20
23
21 const onRevealClick = useCallback(async () => {
24 const onRevealClick = useCallback(async () => {
22− setIsFaded(false);
25+ setShowMasterKey(true);
23 await timeout(500); // Pause for UI fade animation
26 await timeout(500); // Pause for UI fade animation
24 goToDownload();
27 goToDownload();
25− }, [goToDownload, setIsFaded]);
28+ }, [goToDownload]);
26
29
27 const header = useMemo(() => {
30 const header = useMemo(() => {
28− return (
31+ return triggerAnimation ? (
29− <Box className={isFaded ? 'faded-in' : 'faded-out'}>
32+ <Box>
30 <ActivateHeader copy={'Here is your Master Key.'} />
33 <ActivateHeader copy={'Here is your Master Key.'} />
31 <ActivateParagraph
34 <ActivateParagraph
32 copy={
35 copy={
@@ -34,49 +37,37 @@ const MasterKeyReveal = () => {
34 }
37 }
35 />
38 />
36 </Box>
39 </Box>
37− );
40+ ) : null;
38− }, [isFaded]);
41+ }, [triggerAnimation]);
39
42
40 const footer = useMemo(() => {
43 const footer = useMemo(() => {
41− return (
44+ return triggerAnimation ? (
42− <Box
43− display="flex"
44− flexDirection="column"
45− flexWrap="nowrap"
46− height={'100%'}
47− className={isFaded ? 'faded-in' : 'faded-out'}
48− justifyContent="flex-end">
49− <Button
50− onClick={onRevealClick}
45+ <ActivateButton onClick={onRevealClick}>
51− backgroundColor="black"
46+ <Icon
47+ display="inline-block"
48+ icon="Visible"
49+ size="18px"
52 color={'white'}
50 color={'white'}
53− padding={'16px'}
54− fontFamily="Inter"
55− height={'50px'}
56− fontWeight={'400'}
57− fontSize={'18px'}>
58− <Icon
59− display="inline-block"
60− icon="Visible"
61− size="18px"
62− color={'white'}
63− />
51+ />
64− &nbsp; Reveal
52+ &nbsp; Reveal
65− </Button>
53+ </ActivateButton>
66− </Box>
67− );
54+ ) : null;
68− }, [isFaded, onRevealClick]);
55+ }, [onRevealClick, triggerAnimation]);
69
56
70− const onViewTransition = useCallback(async () => {
57+ const delayedFadeIn = useCallback(async () => {
71− await timeout(500); // Pause for UI fade animation
58+ setTimeout(() => {
72− setIsFaded(true);
59+ setTriggerAnimation(true);
73− await timeout(500); // Pause for UI fade animation
60+ setIsIn(true);
74− }, [setIsFaded]);
61+ }, 1200);
62+ }, [setIsIn]);
75
63
76 useEffect(() => {
64 useEffect(() => {
77− // Fade in content
65+ delayedFadeIn();
78− onViewTransition();
66+
79− }, [onViewTransition]);
67+ return () => {
68+ setIsIn(false);
69+ };
70+ }, [delayedFadeIn, setIsIn]);
80
71
81 return (
72 return (
82 <>
73 <>
@@ -95,15 +86,11 @@ const MasterKeyReveal = () => {
95 width={'80%'}
86 width={'80%'}
96 height={'min-content'}
87 height={'min-content'}
97 justifyContent={'center'}>
88 justifyContent={'center'}>
98− <MasterKey paused={true} />
89+ <MasterKey />
99 </Box>
90 </Box>
100 </Box>
91 </Box>
101 </ActivateView>
92 </ActivateView>
102− <ActivateSteps
93+ {triggerAnimation && <ActivateSteps currentStep={0} totalSteps={4} />}
103− currentStep={0}
104− totalSteps={4}
105− className={isFaded ? 'faded-in' : 'faded-out'}
106− />
107 </>
94 </>
108 );
95 );
109};
96};
src/views/Activate/PointPresenter.tsx+41−48
@@ -1,61 +1,54 @@
1import { Box, Text } from '@tlon/indigo-react';
1import { Box, Text } from '@tlon/indigo-react';
2−import { CSSTransition } from 'react-transition-group';
3import Sigil from 'components/Sigil';
2import Sigil from 'components/Sigil';
3+import withFadeable from './withFadeable';
4
4
5type PointPresenterArgs = {
5type PointPresenterArgs = {
6 patp: string;
6 patp: string;
7− isIn: boolean;
8 className?: string;
7 className?: string;
9};
8};
10
9
11−const PointPresenter = ({
10+const PointPresenter = ({ patp, className }: PointPresenterArgs) => {
12− patp,
13− isIn = false,
14− className,
15−}: PointPresenterArgs) => {
16 return patp ? (
11 return patp ? (
17− <>
18− <CSSTransition in={isIn} classNames="fadeable" timeout={300}>
19− <Box
12+ <Box
20− width={256}
13+ width={256}
21− height={256}
14+ height={256}
22− display="flex"
15+ display="flex"
23− flexDirection="column"
16+ flexDirection="column"
24− flexWrap="nowrap"
17+ flexWrap="nowrap"
25− justifyContent="center"
18+ justifyContent="center"
26− alignItems="center"
19+ alignItems="center"
27− background="white"
20+ background="white"
28− className={className}>
21+ className={className}>
29− <Box
22+ <Box
30− background="black"
23+ background="black"
31− borderRadius={5}
24+ borderRadius={5}
32− padding={32}
25+ padding={32}
33− mb={3}
26+ mb={3}
34− width={64}
27+ width={64}
35− height={64}>
28+ height={64}>
36− <Sigil
29+ <Sigil
37− patp={patp}
30+ patp={patp}
38− size={64}
31+ size={64}
39− display="block"
32+ display="block"
40− colors={['black', 'white']}
33+ colors={['black', 'white']}
41− icon
34+ icon
42− padding={32}
35+ padding={32}
43− borderRadius={5}
36+ borderRadius={5}
44− />
37+ />
45− </Box>
38+ </Box>
46− <Box>
39+ <Box>
47− <Text
40+ <Text
48− display="block"
41+ display="block"
49− fontFamily={'Source Code Pro'}
42+ fontFamily={'Source Code Pro'}
50− fontSize={'18px'}
43+ fontSize={'18px'}
51− textAlign={'center'}>
44+ textAlign={'center'}>
52− {patp}
45+ {patp}
53− </Text>
46+ </Text>
54− </Box>
47+ </Box>
55− </Box>
48+ </Box>
56− </CSSTransition>
57− </>
58 ) : null;
49 ) : null;
59};
50};
60
51
61export default PointPresenter;
52export default PointPresenter;
53+
54+export const FadeablePointPresenter = withFadeable(PointPresenter);
src/views/Activate/useFadeIn.tsnew file+16
@@ -0,0 +1,16 @@
1+import { useEffect } from 'react';
2+import { useActivateFlow } from './ActivateFlow';
3+
4+const useFadeIn = () => {
5+ const { setIsIn } = useActivateFlow();
6+
7+ useEffect(() => {
8+ setIsIn(true);
9+
10+ return () => {
11+ setIsIn(false);
12+ };
13+ }, [setIsIn]);
14+};
15+
16+export default useFadeIn;
src/views/Activate/withFadeable.tsxnew file+24
@@ -0,0 +1,24 @@
1+import { useActivateFlow } from './ActivateFlow';
2+import { CSSTransition } from 'react-transition-group';
3+import { DEFAULT_FADE_TIMEOUT } from 'lib/constants';
4+
5+interface withFadeableProps {
6+ fadeTimeout?: number;
7+}
8+
9+const withFadeable = <P extends object>(
10+ Component: React.ComponentType<P>
11+): React.FC<P & withFadeableProps> => (props: withFadeableProps) => {
12+ const { isIn } = useActivateFlow();
13+
14+ return (
15+ <CSSTransition
16+ in={isIn}
17+ classNames="fadeable"
18+ timeout={props.fadeTimeout || DEFAULT_FADE_TIMEOUT}>
19+ <Component {...(props as P)} />
20+ </CSSTransition>
21+ );
22+};
23+
24+export default withFadeable;