on master help sign in
Merge pull request #599 from urbit/t/restore-urbit-os-access-code

ux: restore Access Code to Urbit OS view

commitb7f48968bbf894baffccaad4ef252a230123dbd8 authorfang <github@fang.io> dateWed 28 Jul 2021, 20:58 (5 years ago) parent5a0fc64d19b091
12 files changed+332−53
package-lock.json+188 package.json+3−1 src/components/Buttons.js+8 src/components/CopyButton.js−28 src/components/CopyButtonWide.tsx+18 src/components/WithTooltip.js−23 src/components/WithTooltip.tsx+53 src/setupTests.ts+1 src/style/bridge.scss+6 src/views/UrbitOS/AccessCode.test.tsx+28 src/views/UrbitOS/AccessCode.tsx+24 src/views/UrbitOS/Home.tsx+3−1
package-lock.json+188
@@ -6788,6 +6788,155 @@
67886788 "defer-to-connect": "^1.0.1"
67896789 }
67906790 },
6791+ "@testing-library/dom": {
6792+ "version": "8.1.0",
6793+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.1.0.tgz",
6794+ "integrity": "sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q==",
6795+ "dev": true,
6796+ "requires": {
6797+ "@babel/code-frame": "^7.10.4",
6798+ "@babel/runtime": "^7.12.5",
6799+ "@types/aria-query": "^4.2.0",
6800+ "aria-query": "^4.2.2",
6801+ "chalk": "^4.1.0",
6802+ "dom-accessibility-api": "^0.5.6",
6803+ "lz-string": "^1.4.4",
6804+ "pretty-format": "^27.0.2"
6805+ },
6806+ "dependencies": {
6807+ "@jest/types": {
6808+ "version": "27.0.6",
6809+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz",
6810+ "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==",
6811+ "dev": true,
6812+ "requires": {
6813+ "@types/istanbul-lib-coverage": "^2.0.0",
6814+ "@types/istanbul-reports": "^3.0.0",
6815+ "@types/node": "*",
6816+ "@types/yargs": "^16.0.0",
6817+ "chalk": "^4.0.0"
6818+ }
6819+ },
6820+ "@types/yargs": {
6821+ "version": "16.0.4",
6822+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
6823+ "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==",
6824+ "dev": true,
6825+ "requires": {
6826+ "@types/yargs-parser": "*"
6827+ }
6828+ },
6829+ "ansi-styles": {
6830+ "version": "5.2.0",
6831+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
6832+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
6833+ "dev": true
6834+ },
6835+ "pretty-format": {
6836+ "version": "27.0.6",
6837+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz",
6838+ "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==",
6839+ "dev": true,
6840+ "requires": {
6841+ "@jest/types": "^27.0.6",
6842+ "ansi-regex": "^5.0.0",
6843+ "ansi-styles": "^5.0.0",
6844+ "react-is": "^17.0.1"
6845+ }
6846+ },
6847+ "react-is": {
6848+ "version": "17.0.2",
6849+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
6850+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
6851+ "dev": true
6852+ }
6853+ }
6854+ },
6855+ "@testing-library/jest-dom": {
6856+ "version": "5.14.1",
6857+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz",
6858+ "integrity": "sha512-dfB7HVIgTNCxH22M1+KU6viG5of2ldoA5ly8Ar8xkezKHKXjRvznCdbMbqjYGgO2xjRbwnR+rR8MLUIqF3kKbQ==",
6859+ "dev": true,
6860+ "requires": {
6861+ "@babel/runtime": "^7.9.2",
6862+ "@types/testing-library__jest-dom": "^5.9.1",
6863+ "aria-query": "^4.2.2",
6864+ "chalk": "^3.0.0",
6865+ "css": "^3.0.0",
6866+ "css.escape": "^1.5.1",
6867+ "dom-accessibility-api": "^0.5.6",
6868+ "lodash": "^4.17.15",
6869+ "redent": "^3.0.0"
6870+ },
6871+ "dependencies": {
6872+ "chalk": {
6873+ "version": "3.0.0",
6874+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
6875+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
6876+ "dev": true,
6877+ "requires": {
6878+ "ansi-styles": "^4.1.0",
6879+ "supports-color": "^7.1.0"
6880+ }
6881+ },
6882+ "css": {
6883+ "version": "3.0.0",
6884+ "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
6885+ "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
6886+ "dev": true,
6887+ "requires": {
6888+ "inherits": "^2.0.4",
6889+ "source-map": "^0.6.1",
6890+ "source-map-resolve": "^0.6.0"
6891+ }
6892+ },
6893+ "redent": {
6894+ "version": "3.0.0",
6895+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
6896+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
6897+ "dev": true,
6898+ "requires": {
6899+ "indent-string": "^4.0.0",
6900+ "strip-indent": "^3.0.0"
6901+ }
6902+ },
6903+ "source-map": {
6904+ "version": "0.6.1",
6905+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
6906+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
6907+ "dev": true
6908+ },
6909+ "source-map-resolve": {
6910+ "version": "0.6.0",
6911+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
6912+ "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
6913+ "dev": true,
6914+ "requires": {
6915+ "atob": "^2.1.2",
6916+ "decode-uri-component": "^0.2.0"
6917+ }
6918+ },
6919+ "strip-indent": {
6920+ "version": "3.0.0",
6921+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
6922+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
6923+ "dev": true,
6924+ "requires": {
6925+ "min-indent": "^1.0.0"
6926+ }
6927+ }
6928+ }
6929+ },
6930+ "@testing-library/react": {
6931+ "version": "12.0.0",
6932+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.0.0.tgz",
6933+ "integrity": "sha512-sh3jhFgEshFyJ/0IxGltRhwZv2kFKfJ3fN1vTZ6hhMXzz9ZbbcTgmDYM4e+zJv+oiVKKEWZPyqPAh4MQBI65gA==",
6934+ "dev": true,
6935+ "requires": {
6936+ "@babel/runtime": "^7.12.5",
6937+ "@testing-library/dom": "^8.0.0"
6938+ }
6939+ },
67916940 "@textile/buckets": {
67926941 "version": "6.0.5",
67936942 "resolved": "https://registry.npmjs.org/@textile/buckets/-/buckets-6.0.5.tgz",
@@ -7958,6 +8107,12 @@
79588107 "@types/node": "*"
79598108 }
79608109 },
8110+ "@types/aria-query": {
8111+ "version": "4.2.2",
8112+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
8113+ "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==",
8114+ "dev": true
8115+ },
79618116 "@types/babel__core": {
79628117 "version": "7.1.14",
79638118 "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz",
@@ -8386,6 +8541,15 @@
83868541 "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz",
83878542 "integrity": "sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ=="
83888543 },
8544+ "@types/testing-library__jest-dom": {
8545+ "version": "5.14.1",
8546+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.1.tgz",
8547+ "integrity": "sha512-Gk9vaXfbzc5zCXI9eYE9BI5BNHEp4D3FWjgqBE/ePGYElLAP+KvxBcsdkwfIVvezs605oiyd/VrpiHe3Oeg+Aw==",
8548+ "dev": true,
8549+ "requires": {
8550+ "@types/jest": "*"
8551+ }
8552+ },
83898553 "@types/to-json-schema": {
83908554 "version": "0.2.0",
83918555 "resolved": "https://registry.npmjs.org/@types/to-json-schema/-/to-json-schema-0.2.0.tgz",
@@ -12789,6 +12953,12 @@
1278912953 "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
1279012954 "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
1279112955 },
12956+ "css.escape": {
12957+ "version": "1.5.1",
12958+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
12959+ "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
12960+ "dev": true
12961+ },
1279212962 "cssdb": {
1279312963 "version": "4.4.0",
1279412964 "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
@@ -13474,6 +13644,12 @@
1347413644 "esutils": "^2.0.2"
1347513645 }
1347613646 },
13647+ "dom-accessibility-api": {
13648+ "version": "0.5.6",
13649+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz",
13650+ "integrity": "sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==",
13651+ "dev": true
13652+ },
1347713653 "dom-converter": {
1347813654 "version": "0.2.0",
1347913655 "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@@ -23293,6 +23469,12 @@
2329323469 "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=",
2329423470 "optional": true
2329523471 },
23472+ "lz-string": {
23473+ "version": "1.4.4",
23474+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
23475+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
23476+ "dev": true
23477+ },
2329623478 "magic-string": {
2329723479 "version": "0.25.7",
2329823480 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@@ -23628,6 +23810,12 @@
2362823810 "dom-walk": "^0.1.0"
2362923811 }
2363023812 },
23813+ "min-indent": {
23814+ "version": "1.0.1",
23815+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
23816+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
23817+ "dev": true
23818+ },
2363123819 "mini-css-extract-plugin": {
2363223820 "version": "0.11.3",
2363323821 "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz",
package.json+3−1
@@ -79,6 +79,8 @@
7979 "web3-utils": "^1.3.6"
8080 },
8181 "devDependencies": {
82+ "@testing-library/jest-dom": "^5.14.1",
83+ "@testing-library/react": "^12.0.0",
8284 "@types/keccak": "^3.0.1",
8385 "@types/lodash": "^4.14.170",
8486 "eslint-config-prettier": "^5.0.0",
@@ -97,7 +99,7 @@
9799 "start": "react-scripts start",
98100 "build": "react-scripts build",
99101 "build:ropsten": "REACT_APP_ROPSTEN=true react-scripts build",
100− "test": "react-scripts test --env=jsdom",
102+ "test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!@tlon\/indigo-react)/\" --env=jsdom",
101103 "eject": "react-scripts eject",
102104 "checksum": "cd build && find . -exec shasum -a256 {} ';' > ../checksums.txt && cd ..",
103105 "zip": "BRIDGE_VERSION=${REACT_APP_BRIDGE_VERSION:-$npm_package_version}; zip -ur bridge-$BRIDGE_VERSION.zip README.md bridge-https.py checksums.txt && cd build && zip -ur ../bridge-$BRIDGE_VERSION.zip ./* && cd ..",
src/components/Buttons.js+8
@@ -1,5 +1,6 @@
11import React from 'react';
22import { Button } from 'indigo-react';
3+import { Icon } from '@tlon/indigo-react';
34
45import { blinkIf } from './Blinky';
56
@@ -23,3 +24,10 @@ export const OfflineButton = props => (
2324 Offline
2425 </OutButton>
2526);
27+
28+export const CopyButton = props => (
29+ <Button
30+ accessory={<Icon icon="Copy" size="18px" color={'black'} />}
31+ {...props}
32+ />
33+);
src/components/CopyButton.js−28
@@ -2,8 +2,6 @@ import React from 'react';
22import cn from 'classnames';
33import { LinkButton } from 'indigo-react';
44
5−import { ForwardButton } from 'components/Buttons';
6−
75import useCopiable from 'lib/useCopiable';
86
97const renderDefaultCopyText = didCopy => (didCopy ? 'Copied!' : 'Copy');
@@ -29,29 +27,3 @@ export default function CopyButton({
2927 </As>
3028 );
3129}
32−
33−export function CopyButtonWide({
34− as: As = ForwardButton,
35− text,
36− children,
37− accessory = renderDefaultCopyText,
38− ...rest
39−}) {
40− const [doCopy, didCopy] = useCopiable(text);
41− return (
42− <As
43− onClick={doCopy}
44− accessory={
45− <LinkButton
46− className={{
47− black: !didCopy,
48− green3: didCopy,
49− }}>
50− {accessory(didCopy)}
51− </LinkButton>
52− }
53− {...rest}>
54− {children}
55− </As>
56− );
57−}
src/components/CopyButtonWide.tsxnew file+18
@@ -0,0 +1,18 @@
1+import React from 'react';
2+import { CopyButton as BaseCopyButton } from 'components/Buttons';
3+import useCopiable from 'lib/useCopiable';
4+import WithTooltip, { TooltipPosition } from './WithTooltip';
5+
6+export function CopyButtonWide({ text, children, ...rest }) {
7+ const [doCopy, didCopy] = useCopiable(text);
8+ return (
9+ <WithTooltip
10+ content={didCopy ? 'Copied!' : 'Copy'}
11+ className={'rel full nowrap copy-button-wide'}
12+ position={TooltipPosition.TopRight}>
13+ <BaseCopyButton as="span" onClick={doCopy} text={text} {...rest}>
14+ {children}
15+ </BaseCopyButton>
16+ </WithTooltip>
17+ );
18+}
src/components/WithTooltip.jsdeleted−23
@@ -1,23 +0,0 @@
1−import React, { useState, useCallback } from 'react';
2−
3−export default function WithTooltip({ content, children }) {
4− const [isHovered, setHovered] = useState(false);
5− const onMouseEnter = useCallback(() => setHovered(true), []);
6− const onMouseLeave = useCallback(() => setHovered(false), []);
7−
8− return (
9− <div
10− className="rel nowrap inline-block"
11− onMouseEnter={onMouseEnter}
12− onMouseLeave={onMouseLeave}>
13− {children}
14− {isHovered && (
15− <div
16− className="abs mb1 bg-black white ph4 pv2 r4"
17− style={{ bottom: '100%' }}>
18− {content}
19− </div>
20− )}
21− </div>
22− );
23−}
src/components/WithTooltip.tsxnew file+53
@@ -0,0 +1,53 @@
1+import React, { useState, useCallback } from 'react';
2+import { useMemo } from 'react';
3+
4+export enum TooltipPosition {
5+ Top,
6+ TopRight,
7+ // Add more as necessary here and in #positionStyle
8+}
9+
10+type WithTooltipArgs = {
11+ content: string;
12+ children: React.ReactNode;
13+ className?: string | undefined;
14+ position?: TooltipPosition;
15+};
16+
17+export default function WithTooltip({
18+ content,
19+ children,
20+ className,
21+ position = TooltipPosition.Top,
22+}: WithTooltipArgs) {
23+ const [isHovered, setHovered] = useState(false);
24+ const onMouseEnter = useCallback(() => setHovered(true), []);
25+ const onMouseLeave = useCallback(() => setHovered(false), []);
26+
27+ const positionStyle = useMemo(() => {
28+ switch (position) {
29+ case TooltipPosition.Top:
30+ return { bottom: '100%' };
31+ case TooltipPosition.TopRight:
32+ return { bottom: '90%', left: '90%' };
33+ default:
34+ return { bottom: '100%' };
35+ }
36+ }, [position]);
37+
38+ return (
39+ <div
40+ className={className || 'rel nowrap inline-block'}
41+ onMouseEnter={onMouseEnter}
42+ onMouseLeave={onMouseLeave}>
43+ {children}
44+ {isHovered && (
45+ <div
46+ className="abs mb1 bg-black white ph4 pv2 r4 open-tooltip"
47+ style={positionStyle}>
48+ {content}
49+ </div>
50+ )}
51+ </div>
52+ );
53+}
src/setupTests.tsnew file+1
@@ -0,0 +1 @@
1+import '@testing-library/jest-dom';
src/style/bridge.scss+6
@@ -22,3 +22,9 @@ span.circular-icon-bg {
2222 margin-right: 8px;
2323 border-radius: 50%;
2424}
25+
26+div.copy-button-wide {
27+ .open-tooltip {
28+ font-size: 0.75rem;
29+ }
30+}
src/views/UrbitOS/AccessCode.test.tsxnew file+28
@@ -0,0 +1,28 @@
1+import React from 'react';
2+import { render, screen } from '@testing-library/react';
3+import AccessCode from './AccessCode';
4+
5+describe('AccessCode', () => {
6+ describe('when code is not available', () => {
7+ // Value is initialized as `false` in `useKeyfileGenerator`
8+ const testCode = false;
9+
10+ it('does not render the component', () => {
11+ render(<AccessCode code={testCode} />);
12+ const element = screen.queryByTestId('access-code');
13+ expect(element).not.toBeInTheDocument();
14+ });
15+ });
16+
17+ describe('when code is available', () => {
18+ // Value has been loaded from `useKeyfileGenerator`
19+ const testCode = 'foo-bar';
20+
21+ it('renders the component', () => {
22+ render(<AccessCode code={testCode} />);
23+ expect(
24+ screen.getByText('This is your code to access Urbit OS')
25+ ).toBeInTheDocument();
26+ });
27+ });
28+});
src/views/UrbitOS/AccessCode.tsxnew file+24
@@ -0,0 +1,24 @@
1+import { CopyButtonWide } from 'components/CopyButtonWide';
2+import { Grid } from 'indigo-react';
3+
4+type AccessCodeArgs = {
5+ code: boolean | string;
6+};
7+
8+const AccessCode = ({ code }: AccessCodeArgs) => {
9+ return code ? (
10+ <>
11+ <Grid.Divider />
12+ <Grid.Item
13+ full
14+ detail="This is your code to access Urbit OS"
15+ as={CopyButtonWide}
16+ text={code}
17+ data-testid="access-code">
18+ Access Key
19+ </Grid.Item>
20+ </>
21+ ) : null;
22+};
23+
24+export default AccessCode;
src/views/UrbitOS/Home.tsx+3−1
@@ -16,6 +16,7 @@ import { L1Point } from 'types/L1Point';
1616import AlertBox from 'components/AlertBox';
1717import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
1818import useKeyfileGenerator from 'lib/useKeyfileGenerator';
19+import AccessCode from './AccessCode';
1920
2021export default function UrbitOSHome({ manualNetworkSeed }) {
2122 const { pointCursor } = usePointCursor();
@@ -47,7 +48,7 @@ export default function UrbitOSHome({ manualNetworkSeed }) {
4748 names,
4849 ]);
4950
50− const { bind: keyBind } = useKeyfileGenerator();
51+ const { bind: keyBind, code } = useKeyfileGenerator();
5152
5253 return (
5354 <>
@@ -80,6 +81,7 @@ export default function UrbitOSHome({ manualNetworkSeed }) {
8081 />
8182 </>
8283 )}
84+ <AccessCode code={code} />
8385 <Grid.Divider />
8486 <Grid.Item full as={ForwardButton} onClick={goNetworkingKeys}>
8587 {networkKeysAction} Networking Keys