on master help sign in
WIP: Reveal Master Key
c543e68tomholford
src/style/bridge.scss+12
@@ -28,3 +28,15 @@ div.copy-button-wide {
28 font-size: 0.75rem;
28 font-size: 0.75rem;
29 }
29 }
30}
30}
31+
32+// For fading content in and out
33+
34+.faded-out{
35+ opacity:0;
36+ transition: opacity 0.5s;
37+
38+}
39+.faded-in{
40+ opacity:1;
41+ transition: opacity 0.5s;
42+}
src/style/index.scss−3
@@ -12,6 +12,3 @@
12
12
13/* Bridge Styles */
13/* Bridge Styles */
14@import './bridge.scss';
14@import './bridge.scss';
15−
16−/* View Styles */
17−@import './views/activate.scss';
src/style/master-key.scssnew file+81
@@ -0,0 +1,81 @@
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 {
4+ 0% {
5+ fill-opacity: 0.2;
6+ }
7+ 20% {
8+ fill-opacity: 1.0;
9+ }
10+ 100% {
11+ fill-opacity: 1.0;
12+ }
13+}
14+
15+svg.master-key {
16+ .first-one {
17+ animation: opacity-fill 2000ms linear forwards infinite;
18+ animation-delay: 100ms;
19+ }
20+
21+ .first-two {
22+ animation: opacity-fill 2000ms linear forwards infinite;
23+ animation-delay: 200ms;
24+ }
25+
26+ .first-three {
27+ animation: opacity-fill 2000ms linear forwards infinite;
28+ animation-delay: 300ms;
29+
30+ }
31+
32+ .second-one {
33+ animation: opacity-fill 2000ms linear forwards infinite;
34+ animation-delay: 400ms;
35+ }
36+ .second-two {
37+ animation: opacity-fill 2000ms linear forwards infinite;
38+ animation-delay: 500ms;
39+ }
40+
41+ .second-three {
42+ animation: opacity-fill 2000ms linear forwards infinite;
43+ animation-delay: 600ms;
44+ }
45+
46+ .third-one {
47+ animation: opacity-fill 2000ms linear forwards infinite;
48+ animation-delay: 700ms;
49+ }
50+ .third-two {
51+ animation: opacity-fill 2000ms linear forwards infinite;
52+ animation-delay: 800ms;
53+ }
54+ .third-three {
55+ animation: opacity-fill 2000ms linear forwards infinite;
56+ animation-delay: 900ms;
57+ }
58+
59+ .fourth-one {
60+ animation: opacity-fill 2000ms linear forwards infinite;
61+ animation-delay: 1s;
62+ }
63+
64+ .fourth-two {
65+ animation: opacity-fill 2000ms linear forwards infinite;
66+ animation-delay: 1.1s;
67+ }
68+
69+ .fourth-three {
70+ animation: opacity-fill 2000ms linear forwards infinite;
71+ animation-delay: 1.2s;
72+ }
73+
74+ &.paused g {
75+ animation-play-state: paused;
76+ }
77+
78+ &.paused path {
79+ animation-play-state: paused;
80+ }
81+}
src/style/views/activate.scssdeleted−76
@@ -1,76 +0,0 @@
1−div.activate-view {
2− border: 1px solid rgb(204, 204, 204);
3− border-radius: 5px;
4− padding: 10px;
5− min-width: 600px;
6− min-height: 500px;
7−
8− @keyframes opacity-fill {
9− 0% {
10− fill-opacity: 0.2;
11− }
12− 100% {
13− fill-opacity: 1.0;
14− }
15− }
16−
17− svg.master-key {
18− .first-one {
19− animation: opacity-fill 200ms linear forwards infinite;
20− animation-delay: 100ms;
21− }
22−
23− .first-two {
24− animation: opacity-fill 200ms linear forwards infinite;
25− animation-delay: 200ms;
26− }
27−
28− .first-three {
29− animation: opacity-fill 200ms linear forwards infinite;
30− animation-delay: 300ms;
31−
32− }
33−
34− .second-one {
35− animation: opacity-fill 200ms linear forwards infinite;
36− animation-delay: 400ms;
37− }
38− .second-two {
39− animation: opacity-fill 200ms linear forwards infinite;
40− animation-delay: 500ms;
41− }
42−
43− .second-three {
44− animation: opacity-fill 200ms linear forwards infinite;
45− animation-delay: 600ms;
46− }
47−
48− .third-one {
49− animation: opacity-fill 200ms linear forwards infinite;
50− animation-delay: 700ms;
51− }
52− .third-two {
53− animation: opacity-fill 200ms linear forwards infinite;
54− animation-delay: 800ms;
55− }
56− .third-three {
57− animation: opacity-fill 200ms linear forwards infinite;
58− animation-delay: 900ms;
59− }
60−
61− .fourth-one {
62− animation: opacity-fill 200ms linear forwards infinite;
63− animation-delay: 1s;
64− }
65−
66− .fourth-two {
67− animation: opacity-fill 200ms linear forwards infinite;
68− animation-delay: 1.1s;
69− }
70−
71− .fourth-three {
72− animation: opacity-fill 200ms linear forwards infinite;
73− animation-delay: 1.2s;
74− }
75− }
76−}
src/views/Activate.js+3
@@ -8,18 +8,21 @@ import { ActivateFlowProvider } from './Activate/ActivateFlow';
8import useActivateFlowState from './Activate/useActivateFlowState';
8import useActivateFlowState from './Activate/useActivateFlowState';
9import ActivateCode from './Activate/ActivateCode';
9import ActivateCode from './Activate/ActivateCode';
10import ActivatePassport from './Activate/ActivatePassport';
10import ActivatePassport from './Activate/ActivatePassport';
11+import ActivateMasterKey from './Activate/ActivateMasterKey';
11import Disclaimer from './Disclaimer';
12import Disclaimer from './Disclaimer';
12
13
13const NAMES = {
14const NAMES = {
14 CODE: 'CODE',
15 CODE: 'CODE',
15 DISCLAIMER: 'DISCLAIMER',
16 DISCLAIMER: 'DISCLAIMER',
16 PASSPORT: 'PASSPORT',
17 PASSPORT: 'PASSPORT',
18+ MASTER_KEY: 'MASTER_KEY',
17};
19};
18
20
19const VIEWS = {
21const VIEWS = {
20 [NAMES.CODE]: ActivateCode,
22 [NAMES.CODE]: ActivateCode,
21 [NAMES.PASSPORT]: ActivatePassport,
23 [NAMES.PASSPORT]: ActivatePassport,
22 [NAMES.DISCLAIMER]: Disclaimer,
24 [NAMES.DISCLAIMER]: Disclaimer,
25+ [NAMES.MASTER_KEY]: ActivateMasterKey,
23};
26};
24
27
25export default function Activate() {
28export default function Activate() {
src/views/Activate/ActivateCode.tsx+24−19
@@ -61,9 +61,11 @@ export default function ActivateCode() {
61 const [isGenerating, setIsGenerating] = useState<boolean>(false);
61 const [isGenerating, setIsGenerating] = useState<boolean>(false);
62
62
63 const {
63 const {
64+ isFaded,
64 setDerivedPoint,
65 setDerivedPoint,
65 setDerivedWallet,
66 setDerivedWallet,
66 setInviteWallet,
67 setInviteWallet,
68+ setIsFaded,
67 } = useActivateFlow();
69 } = useActivateFlow();
68 // this is a pretty naive way to detect if we're on a mobile device
70 // this is a pretty naive way to detect if we're on a mobile device
69 // (i.e. we're checking the width of the screen)
71 // (i.e. we're checking the width of the screen)
@@ -76,13 +78,13 @@ export default function ActivateCode() {
76 history,
78 history,
77 ]);
79 ]);
78
80
79− const goToPassport = useCallback(() => {
81+ const goToMasterKey = useCallback(() => {
80 if (!hasDisclaimed) {
82 if (!hasDisclaimed) {
81− push(names.DISCLAIMER, { next: names.PASSPORT });
83+ push(names.DISCLAIMER, { next: names.MASTER_KEY });
82 } else {
84 } else {
83− push(names.PASSPORT);
85+ push(names.MASTER_KEY);
84 }
86 }
85− }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
87+ }, [hasDisclaimed, names.DISCLAIMER, names.MASTER_KEY, push]);
86
88
87 const validateForm = useCallback((values, errors) => {
89 const validateForm = useCallback((values, errors) => {
88 didWarn.current = false;
90 didWarn.current = false;
@@ -131,7 +133,8 @@ export default function ActivateCode() {
131 const onSubmit = useCallback(
133 const onSubmit = useCallback(
132 async values => {
134 async values => {
133 setIsGenerating(true);
135 setIsGenerating(true);
134− await timeout(16); // allow the ui changes to flush before we lag it out
136+ setIsFaded(true);
137+ await timeout(1400); // Allow the Master Key animation to run for one cycle
135
138
136 // Derive wallet
139 // Derive wallet
137 // TODO: fallback logic to first use implied values if present, otherwise form values?
140 // TODO: fallback logic to first use implied values if present, otherwise form values?
@@ -220,7 +223,7 @@ export default function ActivateCode() {
220 <BridgeForm
223 <BridgeForm
221 validate={validate}
224 validate={validate}
222 onSubmit={onSubmit}
225 onSubmit={onSubmit}
223− afterSubmit={goToPassport}
226+ afterSubmit={goToMasterKey}
224 initialValues={initialValues}>
227 initialValues={initialValues}>
225 {({ validating, values, submitting, handleSubmit }) => (
228 {({ validating, values, submitting, handleSubmit }) => (
226 <>
229 <>
@@ -275,7 +278,7 @@ export default function ActivateCode() {
275 );
278 );
276 }, [
279 }, [
277 activationAllowed,
280 activationAllowed,
278− goToPassport,
281+ goToMasterKey,
279 impliedTicket,
282 impliedTicket,
280 initialValues,
283 initialValues,
281 onSubmit,
284 onSubmit,
@@ -302,19 +305,21 @@ export default function ActivateCode() {
302 <Text className="mb2">Enter your activation code to continue.</Text>
305 <Text className="mb2">Enter your activation code to continue.</Text>
303 )}
306 )}
304 {!isGenerating && impliedPatp && (
307 {!isGenerating && impliedPatp && (
308+ <PointPresenter
305− <PointPresenter patp={impliedPatp} />
309+ patp={impliedPatp}
306− )}
310+ className={!isFaded ? 'faded-in' : 'faded-out'}
307− {isGenerating && (
311+ />
308− <Box
309− display={'flex'}
310− flexDirection={'row'}
311− flexWrap={'nowrap'}
312− width={'80%'}
313− height={'min-content'}
314− justifyContent={'center'}>
315− <MasterKey />
316− </Box>
317 )}
312 )}
313+ <Box
314+ display={'flex'}
315+ flexDirection={'row'}
316+ flexWrap={'nowrap'}
317+ className={isFaded ? 'faded-in' : 'faded-out'}
318+ width={'80%'}
319+ height={'min-content'}
320+ justifyContent={'center'}>
321+ <MasterKey />
322+ </Box>
318 </Box>
323 </Box>
319 </ActivateView>
324 </ActivateView>
320
325
src/views/Activate/ActivateMasterKey.tsxnew file+71
@@ -0,0 +1,71 @@
1+import React, { useMemo } from 'react';
2+import cn from 'classnames';
3+import { Grid } from 'indigo-react';
4+
5+import { LocalRouterProvider, useLocalRouter } from 'lib/LocalRouter';
6+
7+import View from 'components/View';
8+import Passport from 'components/Passport';
9+
10+import { useActivateFlow } from './ActivateFlow';
11+import useBreakpoints from 'lib/useBreakpoints';
12+import useRouter from 'lib/useRouter';
13+
14+import PassportDownload from './PassportDownload';
15+import PassportVerify from './PassportVerify';
16+import PassportTransfer from './PassportTransfer';
17+import { walletFromMnemonic } from 'lib/wallet';
18+import { DEFAULT_HD_PATH } from 'lib/constants';
19+import { publicToAddress } from 'lib/utils/address';
20+import MasterKeyConfirm from './MasterKeyConfirm';
21+
22+const NAMES = {
23+ CONFIRM: 'CONFIRM',
24+ DOWNLOAD: 'DOWNLOAD',
25+ VERIFY: 'VERIFY',
26+ TRANSFER: 'TRANSFER',
27+};
28+
29+const VIEWS = {
30+ [NAMES.CONFIRM]: MasterKeyConfirm,
31+ [NAMES.DOWNLOAD]: PassportDownload,
32+ [NAMES.VERIFY]: PassportVerify,
33+ [NAMES.TRANSFER]: PassportTransfer,
34+};
35+
36+export default function ActivatePassport() {
37+ const { derivedPoint, generated, derivedWallet } = useActivateFlow();
38+ // pull the reset function out of the 'activate' router
39+ const { reset: resetActivateRouter } = useLocalRouter();
40+ const { Route, ...router } = useRouter({
41+ names: NAMES,
42+ views: VIEWS,
43+ initialRoutes: [{ key: NAMES.CONFIRM }],
44+ });
45+
46+ const gap = useBreakpoints([4, 4, 7]);
47+ const marginTop = useBreakpoints([false, false, 8]);
48+
49+ // addresses are not derived until we set[Urbit]Wallet(), so do that inline
50+ const address = useMemo(
51+ () =>
52+ derivedWallet
53+ .chain(wallet =>
54+ walletFromMnemonic(
55+ wallet.ownership.seed,
56+ DEFAULT_HD_PATH,
57+ wallet.meta.passphrase
58+ )
59+ )
60+ .map(wallet => publicToAddress(wallet.publicKey)),
61+ [derivedWallet]
62+ );
63+ //~hobbyn-mismur-fonrux-datber
64+ return (
65+ <View inset>
66+ <LocalRouterProvider value={router}>
67+ <Route resetActivateRouter={resetActivateRouter} />
68+ </LocalRouterProvider>
69+ </View>
70+ );
71+}
src/views/Activate/ActivateParagraph.tsxnew file+29
@@ -0,0 +1,29 @@
1+import { Box, Text } from '@tlon/indigo-react';
2+
3+type ActivateParagraphProps = {
4+ copy: string;
5+};
6+
7+const ActivateParagraph = ({ copy }: ActivateParagraphProps) => {
8+ return (
9+ <Box
10+ display={'flex'}
11+ flexDirection={'column'}
12+ flexWrap={'nowrap'}
13+ justifyContent={'center'}
14+ alignItems={'center'}>
15+ <Text
16+ display="block"
17+ fontFamily="Inter"
18+ fontSize={2}
19+ mx={3}
20+ my={3}
21+ textAlign="center"
22+ width={'80%'}>
23+ {copy}
24+ </Text>
25+ </Box>
26+ );
27+};
28+
29+export default ActivateParagraph;
src/views/Activate/ActivateSteps.tsxnew file+44
@@ -0,0 +1,44 @@
1+import { Box } from '@tlon/indigo-react';
2+
3+type ActivateStepsProps = {
4+ currentStep: number;
5+ totalSteps: number;
6+ className?: string;
7+};
8+
9+export const ActivateSteps = ({
10+ currentStep,
11+ totalSteps,
12+ className,
13+}: ActivateStepsProps) => {
14+ const renderSteps = () => {
15+ let steps = [];
16+ for (let i = 0; i < totalSteps; i++) {
17+ steps.push(
18+ <Box
19+ borderTopLeftRadius={i === 0 ? '5px' : null}
20+ borderBottomLeftRadius={i === 0 ? '5px' : null}
21+ borderTopRightRadius={i === totalSteps - 1 ? '5px' : null}
22+ borderBottomRightRadius={i === totalSteps - 1 ? '5px' : null}
23+ key={i}
24+ backgroundColor={i <= currentStep ? 'black' : 'gray'}
25+ height={'10px'}
26+ width={`${100 / totalSteps}%`}
27+ marginLeft={'2px'}
28+ marginRight={'2px'}></Box>
29+ );
30+ }
31+ return steps;
32+ };
33+
34+ return (
35+ <Box
36+ display={'flex'}
37+ flexDirection={'row'}
38+ flexWrap={'nowrap'}
39+ width={'100%'}
40+ className={className}>
41+ {renderSteps()}
42+ </Box>
43+ );
44+};
src/views/Activate/ActivateView.tsx+9−1
@@ -24,7 +24,15 @@ export default function ActivateView({
24 gridTemplateColumns="1fr"
24 gridTemplateColumns="1fr"
25 gridTemplateRows="20% 60% 20%"
25 gridTemplateRows="20% 60% 20%"
26 gridTemplateAreas={"'header' 'content' 'footer'"}
26 gridTemplateAreas={"'header' 'content' 'footer'"}
27+ border={'1px solid rgb(204, 204, 204)'}
28+ border-radius={'5px'}
29+ padding={'10px'}
30+ width={'550px'}
31+ height={'550px'}
32+ max-width={'550px'}
33+ max-height={'550px'}
27− className={cn(className, 'activate-view')}>
34+ className={cn(className)}
35+ mb={3}>
28 <Box gridArea="header">{header}</Box>
36 <Box gridArea="header">{header}</Box>
29 <Box gridArea="content">{children}</Box>
37 <Box gridArea="content">{children}</Box>
30 <Box gridArea="footer">{footer ? footer : null}</Box>
38 <Box gridArea="footer">{footer ? footer : null}</Box>
src/views/Activate/MasterKey.tsx+7−2
@@ -1,6 +1,11 @@
1+import cn from 'classnames';
2+import 'style/master-key.scss';
3+
1−export const MasterKey = () => {
4+export const MasterKey = ({ paused = false }) => {
2 return (
5 return (
6+ <svg
7+ className={cn({ 'master-key': true, paused: paused })}
3− <svg className="master-key" viewBox="0 0 300 32">
8+ viewBox="0 0 300 32">
4 <g>
9 <g>
5 <path
10 <path
6 className="first-one"
11 className="first-one"
src/views/Activate/MasterKeyConfirm.tsxnew file+92
@@ -0,0 +1,92 @@
1+import React, { useEffect, useMemo, useState } from 'react';
2+
3+import ActivateView from './ActivateView';
4+import ActivateHeader from './ActivateHeader';
5+import { MasterKey } from './MasterKey';
6+import { Box, Button, Icon } from '@tlon/indigo-react';
7+import ActivateParagraph from './ActivateParagraph';
8+import { ActivateSteps } from './ActivateSteps';
9+import { useActivateFlow } from './ActivateFlow';
10+
11+const MasterKeyConfirm = () => {
12+ const { isFaded, setIsFaded } = useActivateFlow();
13+
14+ const header = useMemo(() => {
15+ return (
16+ <Box className={isFaded ? 'faded-in' : 'faded-out'}>
17+ <ActivateHeader copy={'Here is your Master Key.'} />
18+ <ActivateParagraph
19+ copy={
20+ "Your Master Key is your 4-word password for your Urbit. Make sure you're in a private place before you reveal it."
21+ }
22+ />
23+ </Box>
24+ );
25+ }, [isFaded]);
26+
27+ const footer = useMemo(() => {
28+ return (
29+ <Box
30+ display="flex"
31+ flexDirection="column"
32+ flexWrap="nowrap"
33+ height={'100%'}
34+ className={isFaded ? 'faded-in' : 'faded-out'}
35+ justifyContent="flex-end">
36+ <Button
37+ onClick={() => console.log('reveal')}
38+ backgroundColor="black"
39+ color={'white'}
40+ padding={'16px'}
41+ fontFamily="Inter"
42+ height={'50px'}
43+ fontWeight={'400'}
44+ fontSize={'18px'}>
45+ <Icon
46+ display="inline-block"
47+ icon="Visible"
48+ size="18px"
49+ color={'white'}
50+ />
51+ &nbsp; Reveal
52+ </Button>
53+ </Box>
54+ );
55+ }, [isFaded]);
56+
57+ useEffect(() => {
58+ // Fade in content once
59+ setIsFaded(true);
60+ }, []);
61+
62+ return (
63+ <>
64+ <ActivateView header={header} footer={footer}>
65+ <Box
66+ alignItems={'center'}
67+ display={'flex'}
68+ flexDirection={'column'}
69+ flexWrap={'nowrap'}
70+ height={'100%'}
71+ justifyContent={'center'}>
72+ <Box
73+ display={'flex'}
74+ flexDirection={'row'}
75+ flexWrap={'nowrap'}
76+ width={'80%'}
77+ height={'min-content'}
78+ justifyContent={'center'}>
79+ <MasterKey paused={true} />
80+ </Box>
81+ </Box>
82+ </ActivateView>
83+ <ActivateSteps
84+ currentStep={0}
85+ totalSteps={4}
86+ className={isFaded ? 'faded-in' : 'faded-out'}
87+ />
88+ </>
89+ );
90+};
91+
92+export default MasterKeyConfirm;
src/views/Activate/useActivateFlowState.js+4
@@ -7,6 +7,7 @@ export default function useActivateFlowState() {
7 const [derivedPoint, setDerivedPoint] = useState(Nothing());
7 const [derivedPoint, setDerivedPoint] = useState(Nothing());
8 const [generated, setGenerated] = useState(false);
8 const [generated, setGenerated] = useState(false);
9 const [incomingPoints, setIncomingPoints] = useState(Nothing());
9 const [incomingPoints, setIncomingPoints] = useState(Nothing());
10+ const [isFaded, setIsFaded] = useState(false);
10
11
11 const reset = useCallback(() => {
12 const reset = useCallback(() => {
12 setDerivedWallet(Nothing());
13 setDerivedWallet(Nothing());
@@ -14,6 +15,7 @@ export default function useActivateFlowState() {
14 setDerivedPoint(Nothing());
15 setDerivedPoint(Nothing());
15 setGenerated(false);
16 setGenerated(false);
16 setIncomingPoints(Nothing());
17 setIncomingPoints(Nothing());
18+ setIsFaded(false);
17 }, [
19 }, [
18 setDerivedWallet,
20 setDerivedWallet,
19 setInviteWallet,
21 setInviteWallet,
@@ -33,6 +35,8 @@ export default function useActivateFlowState() {
33 setGenerated,
35 setGenerated,
34 incomingPoints,
36 incomingPoints,
35 setIncomingPoints,
37 setIncomingPoints,
38+ isFaded,
39+ setIsFaded,
36 reset,
40 reset,
37 };
41 };
38}
42}