Merge pull request #22 from blockstack/feature/onboarding
Importing the onboarding UI from Sample app
@@ -59,6 +59,8 @@ module.exports = {
|
||||
'@typescript-eslint/camelcase': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'react/jsx-uses-vars': [2]
|
||||
'react/jsx-uses-vars': [2],
|
||||
'react/jsx-key': [0],
|
||||
'react/prop-types': [0],
|
||||
}
|
||||
}
|
||||
|
||||
3
packages/app/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
@@ -19,10 +19,11 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blockstack/keychain": "^0.1.0",
|
||||
"@blockstack/keychain": "^0.1.2",
|
||||
"@blockstack/ui": "^1.0.0-alpha.19",
|
||||
"@blockstack/prettier-config": "^0.0.4",
|
||||
"@blockstack/ui": "^1.0.0-alpha.6",
|
||||
"formik": "^2.0.3",
|
||||
"mdi-react": "^6.3.0",
|
||||
"react": "^16.11.0",
|
||||
"react-chrome-redux": "^2.0.0-alpha.5",
|
||||
"react-dom": "^16.11.0",
|
||||
@@ -37,6 +38,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.6.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.7.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
|
||||
"@babel/plugin-transform-regenerator": "^7.4.5",
|
||||
"@babel/plugin-transform-runtime": "^7.6.2",
|
||||
"@babel/preset-env": "^7.6.3",
|
||||
@@ -67,13 +70,13 @@
|
||||
"fork-ts-checker-webpack-plugin": "^1.5.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^24.8.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier": "^1.19.1",
|
||||
"react-hot-loader": "^4.12.15",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
"ts-jest": "^24.0.2",
|
||||
"ts-loader": "^6.0.4",
|
||||
"tslint": "^5.18.0",
|
||||
"typescript": "3.6.2",
|
||||
"typescript": "3.7.2",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-chrome-extension-reloader": "^1.3.0",
|
||||
"webpack-cli": "^3.3.5",
|
||||
|
||||
BIN
packages/app/src/assets/images/graphic-wink-app-icon-locked.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
packages/app/src/assets/images/graphic-wink-app-icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
7
packages/app/src/assets/images/icon-chain-of-blocks.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#7452FF"/>
|
||||
<rect x="8" y="8" width="7" height="7" rx="1" fill="white"/>
|
||||
<rect opacity="0.8" x="17" y="8" width="7" height="7" rx="1" fill="white"/>
|
||||
<rect opacity="0.64" x="8" y="17" width="7" height="7" rx="1" fill="white"/>
|
||||
<rect opacity="0.4" x="17" y="17" width="7" height="7" rx="1" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
4
packages/app/src/assets/images/icon-cross-over-eye.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#00A3FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.13 9.23432C21.2688 9.09025 21.4578 9.00633 21.657 9.00034C21.8114 8.99572 21.9636 9.03824 22.0937 9.12234C22.2237 9.20644 22.3255 9.32819 22.3857 9.47163C22.446 9.61507 22.4618 9.77351 22.4312 9.92618C22.4005 10.0788 22.3249 10.2186 22.2141 10.3272L10.8699 21.7646C10.7991 21.8383 10.7145 21.8971 10.621 21.9375C10.5275 21.9779 10.427 21.9991 10.3252 22C10.2235 22.0008 10.1226 21.9812 10.0285 21.9423C9.9343 21.9035 9.84877 21.8461 9.77684 21.7736C9.7049 21.701 9.64801 21.6148 9.60947 21.5199C9.57093 21.4249 9.55152 21.3232 9.55236 21.2207C9.5532 21.1181 9.57429 21.0167 9.61438 20.9224C9.65448 20.8282 9.71278 20.7429 9.7859 20.6716L21.13 9.23432ZM8.1291 14.9692C9.6529 12.0082 12.6902 10.1442 15.9988 10.1442C17.0095 10.144 17.9946 10.3182 18.9192 10.6434L17.8389 11.7327C17.2842 11.4569 16.6603 11.3013 16 11.3013C13.7003 11.3013 11.836 13.1808 11.836 15.4994C11.836 16.1651 11.9904 16.7942 12.2639 17.3534L10.6145 19.0164C9.59676 18.2327 8.74172 17.2239 8.12987 16.038C7.95695 15.7032 7.95673 15.3044 8.1291 14.9692ZM19.7361 13.6453L21.3854 11.9825C22.4031 12.7662 23.2583 13.775 23.8701 14.961C24.0431 15.2959 24.0433 15.6947 23.8709 16.0299C22.347 18.9909 19.3098 20.8545 16.0012 20.8545C14.9905 20.8547 14.0054 20.6806 13.0808 20.3553L14.161 19.2662C14.7157 19.542 15.3397 19.6976 16 19.6976C18.2997 19.6976 20.164 17.818 20.164 15.4994C20.164 14.8337 20.0097 14.2046 19.7361 13.6453ZM14.1619 13.6461C14.6694 13.1344 15.3348 12.8788 16 12.8788C16.2103 12.8788 16.4205 12.9046 16.6258 12.9557L13.4769 16.1304C13.2657 15.2686 13.4939 14.3196 14.1619 13.6461ZM15.3742 18.0431L18.5231 14.8683C18.7342 15.7301 18.5059 16.679 17.838 17.3524C17.17 18.0259 16.2289 18.2559 15.3742 18.0431Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
25
packages/app/src/assets/images/icon-delay-apps.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect x="12" y="10" width="96" height="96" rx="24" fill="white"/>
|
||||
</g>
|
||||
<path d="M40 42.7383C40 41.0814 41.3431 39.7383 43 39.7383H53.7391C55.396 39.7383 56.7391 41.0814 56.7391 42.7383V53.4774C56.7391 55.1343 55.396 56.4774 53.7391 56.4774H43C41.3431 56.4774 40 55.1343 40 53.4774V42.7383Z" fill="#5548FF"/>
|
||||
<path opacity="0.8" d="M62.7348 56.4784H77.048C78.3773 56.4784 79.2212 55.0547 78.5832 53.8885L71.4267 40.8066C70.7628 39.5931 69.0199 39.5931 68.3561 40.8066L61.1995 53.8885C60.5615 55.0547 61.4055 56.4784 62.7348 56.4784Z" fill="#5548FF"/>
|
||||
<path opacity="0.64" d="M40 69.9574C40 65.1549 43.8932 61.2617 48.6957 61.2617C53.4981 61.2617 57.3913 65.1549 57.3913 69.9574C57.3913 74.7598 53.4981 78.653 48.6957 78.653C43.8932 78.653 40 74.7598 40 69.9574Z" fill="#5548FF"/>
|
||||
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M64.4534 61.7735C64.1305 61.4616 63.698 61.289 63.249 61.2929C62.8001 61.2968 62.3706 61.4769 62.0532 61.7944C61.7357 62.1118 61.5556 62.5413 61.5517 62.9902C61.5478 63.4392 61.7204 63.8717 62.0323 64.1946L67.4824 69.6447L62.0323 75.0947C61.711 75.4158 61.5304 75.8513 61.5303 76.3055C61.5302 76.5304 61.5744 76.7531 61.6604 76.9609C61.7464 77.1687 61.8725 77.3576 62.0314 77.5167C62.1904 77.6758 62.3792 77.802 62.5869 77.8881C62.7947 77.9743 63.0174 78.0186 63.2423 78.0187C63.6965 78.0189 64.1321 77.8386 64.4534 77.5175L69.9035 72.0675L75.3535 77.5175C75.6748 77.8388 76.1105 78.0193 76.5649 78.0193C77.0193 78.0193 77.455 77.8388 77.7763 77.5175C78.0976 77.1962 78.2781 76.7605 78.2781 76.3061C78.2781 75.8518 78.0976 75.416 77.7763 75.0947L72.3263 69.6447L77.7763 64.1946C77.9353 64.0355 78.0614 63.8467 78.1474 63.6389C78.2334 63.4311 78.2776 63.2084 78.2775 62.9835C78.2774 62.7586 78.233 62.5359 78.1469 62.3281C78.0608 62.1204 77.9345 61.9316 77.7755 61.7727C77.6164 61.6137 77.4275 61.4876 77.2197 61.4016C77.0119 61.3156 76.7892 61.2714 76.5643 61.2715C76.3394 61.2716 76.1167 61.3159 75.909 61.4021C75.7012 61.4882 75.5125 61.6144 75.3535 61.7735L69.9035 67.2236L64.4534 61.7735Z" fill="#5548FF"/>
|
||||
<defs>
|
||||
<filter id="filter0_dd" x="0" y="0" width="120" height="120" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
22
packages/app/src/assets/images/icon-delay-padlock.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect x="12" y="10" width="96" height="96" rx="24" fill="white"/>
|
||||
</g>
|
||||
<path d="M71.6364 52.2857H70.1818V48C70.1818 45.3478 69.1091 42.8043 67.1996 40.9289C65.2902 39.0536 62.7004 38 60 38C57.2996 38 54.7098 39.0536 52.8004 40.9289C50.8909 42.8043 49.8182 45.3478 49.8182 48V52.2857H48.3636C47.2067 52.287 46.0975 52.7389 45.2795 53.5423C44.4614 54.3458 44.0013 55.4352 44 56.5714V73.7143C44.0013 74.8505 44.4614 75.9399 45.2795 76.7434C46.0975 77.5468 47.2067 77.9988 48.3636 78H71.6364C72.7933 77.9988 73.9025 77.5468 74.7205 76.7434C75.5386 75.9399 75.9987 74.8505 76 73.7143V56.5714C75.9987 55.4352 75.5386 54.3458 74.7205 53.5423C73.9025 52.7389 72.7933 52.287 71.6364 52.2857ZM52.7273 48C52.7273 46.1056 53.4935 44.2888 54.8574 42.9492C56.2213 41.6097 58.0712 40.8571 60 40.8571C61.9288 40.8571 63.7787 41.6097 65.1426 42.9492C66.5065 44.2888 67.2727 46.1056 67.2727 48V52.2857H52.7273V48Z" fill="#5548FF"/>
|
||||
<defs>
|
||||
<filter id="filter0_dd" x="0" y="0" width="120" height="120" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
23
packages/app/src/assets/images/icon-delay-private.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect x="12" y="10" width="96" height="96" rx="24" fill="white"/>
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M72.825 42.5858C73.1721 42.2256 73.6446 42.0158 74.1425 42.0009C74.5286 41.9893 74.9091 42.0956 75.2342 42.3059C75.5593 42.5161 75.8138 42.8205 75.9644 43.1791C76.1149 43.5377 76.1545 43.9338 76.0779 44.3154C76.0014 44.6971 75.8122 45.0465 75.5353 45.318L47.1747 73.9115C46.9978 74.0957 46.7863 74.2427 46.5525 74.3437C46.3188 74.4447 46.0674 74.4978 45.813 74.4999C45.5587 74.502 45.3065 74.453 45.0711 74.3559C44.8358 74.2587 44.6219 74.1152 44.4421 73.9339C44.2623 73.7526 44.12 73.537 44.0237 73.2997C43.9273 73.0624 43.8788 72.8081 43.8809 72.5517C43.883 72.2952 43.9357 72.0418 44.036 71.8061C44.1362 71.5704 44.282 71.3572 44.4647 71.1789L72.825 42.5858ZM40.3228 56.9229C44.1323 49.5205 51.7254 44.8606 59.997 44.8606C62.5238 44.86 64.9865 45.2954 67.2981 46.1085L64.5972 48.8317C63.2106 48.1423 61.6506 47.7531 60 47.7531C54.2507 47.7531 49.59 52.452 49.5901 58.2486C49.5901 59.9127 49.976 61.4856 50.6598 62.8835L46.5362 67.041C43.9919 65.0818 41.8543 62.5598 40.3247 59.5951C39.8924 58.7579 39.8918 57.7609 40.3228 56.9229ZM69.3402 53.6134L73.4635 49.4562C76.0079 51.4155 78.1457 53.9375 79.6753 56.9026C80.1076 57.7398 80.1082 58.7367 79.6772 59.5747C75.8676 66.9772 68.2745 71.6363 60.003 71.6363C57.4762 71.6369 55.0136 71.2014 52.7019 70.3883L55.4025 67.6656C56.7892 68.3551 58.3493 68.7441 60 68.7441C65.7493 68.7441 70.41 64.0451 70.41 58.2486C70.41 56.5843 70.0241 55.0115 69.3402 53.6134ZM55.4047 53.6152C56.6736 52.3361 58.337 51.6971 60 51.6971C60.5258 51.6971 61.0513 51.7614 61.5646 51.8891L53.6923 59.8261C53.1643 57.6714 53.7347 55.2991 55.4047 53.6152ZM58.4354 64.6078L66.3077 56.6708C66.8355 58.8254 66.2648 61.1974 64.5949 62.8809C62.925 64.5647 60.5723 65.1399 58.4354 64.6078Z" fill="#5548FF"/>
|
||||
<path d="M74.1201 41.2512L74.12 41.2512C73.4273 41.272 72.771 41.5632 72.289 42.0612L43.9363 70.6468C43.6845 70.8936 43.4839 71.1879 43.3458 71.5126C43.2069 71.8393 43.1339 72.1904 43.131 72.5455C43.128 72.9006 43.1952 73.2528 43.3288 73.5818C43.4624 73.9108 43.6597 74.2101 43.9096 74.462L43.9096 74.462C44.1595 74.714 44.457 74.9137 44.785 75.0491C45.1129 75.1845 45.4645 75.2528 45.8192 75.2499C46.1739 75.247 46.5244 75.1729 46.8501 75.0322C47.1737 74.8923 47.4662 74.6895 47.7111 74.4357L76.0644 45.8495C76.447 45.4729 76.7077 44.9897 76.8133 44.463C76.9194 43.9344 76.8646 43.3857 76.6559 42.8887C76.4472 42.3916 76.094 41.9687 75.6415 41.6761C75.189 41.3834 74.6586 41.2351 74.1201 41.2512Z" fill="#5548FF" stroke="white" stroke-width="1.5"/>
|
||||
<defs>
|
||||
<filter id="filter0_dd" x="0" y="0" width="120" height="120" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
5
packages/app/src/assets/images/icon-padlock.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#3700FF"/>
|
||||
<path d="M20.3636 13.3571H19.8182V11.75C19.8182 10.7554 19.4159 9.80161 18.6999 9.09835C17.9838 8.39509 17.0126 8 16 8C14.9874 8 14.0162 8.39509 13.3001 9.09835C12.5841 9.80161 12.1818 10.7554 12.1818 11.75V13.3571H11.6364C11.2025 13.3576 10.7866 13.5271 10.4798 13.8284C10.173 14.1297 10.0005 14.5382 10 14.9643V21.3929C10.0005 21.819 10.173 22.2275 10.4798 22.5288C10.7866 22.8301 11.2025 22.9995 11.6364 23H20.3636C20.7975 22.9995 21.2134 22.8301 21.5202 22.5288C21.827 22.2275 21.9995 21.819 22 21.3929V14.9643C21.9995 14.5382 21.827 14.1297 21.5202 13.8284C21.2134 13.5271 20.7975 13.3576 20.3636 13.3571ZM13.2727 11.75C13.2727 11.0396 13.5601 10.3583 14.0715 9.85596C14.583 9.35363 15.2767 9.07143 16 9.07143C16.7233 9.07143 17.417 9.35363 17.9285 9.85596C18.4399 10.3583 18.7273 11.0396 18.7273 11.75V13.3571H13.2727V11.75ZM16.5455 18.2974V19.5179C16.5455 19.6599 16.488 19.7962 16.3857 19.8967C16.2834 19.9971 16.1447 20.0536 16 20.0536C15.8553 20.0536 15.7166 19.9971 15.6143 19.8967C15.512 19.7962 15.4545 19.6599 15.4545 19.5179V18.2974C15.2466 18.1795 15.084 17.9975 14.9921 17.7796C14.9002 17.5617 14.8841 17.3201 14.9463 17.0922C15.0084 16.8644 15.1454 16.6631 15.3359 16.5195C15.5264 16.3759 15.7599 16.2981 16 16.2981C16.2401 16.2981 16.4736 16.3759 16.6641 16.5195C16.8546 16.6631 16.9916 16.8644 17.0537 17.0922C17.1159 17.3201 17.0998 17.5617 17.0079 17.7796C16.916 17.9975 16.7534 18.1795 16.5455 18.2974Z" fill="white"/>
|
||||
<rect x="14" y="15" width="4" height="7" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
7
packages/app/src/assets/images/icon-shapes.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#AAB3FF"/>
|
||||
<rect x="8" y="8" width="7" height="7" rx="1" fill="white"/>
|
||||
<rect x="8" y="17" width="7" height="7" rx="3.5" fill="white"/>
|
||||
<path d="M17.7232 15H22.2768C23.0446 15 23.526 14.1705 23.145 13.5039L20.4341 8.75974C20.2422 8.42384 19.7578 8.42384 19.5659 8.75974L16.8549 13.5039C16.474 14.1705 16.9553 15 17.7232 15Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5284 17.3284C21.9614 16.8905 22.6633 16.8905 23.0963 17.3284C23.5292 17.7663 23.5292 18.4763 23.0963 18.9142L21.568 20.46L23.1754 22.0858C23.6084 22.5237 23.6084 23.2337 23.1754 23.6716C22.7425 24.1095 22.0405 24.1095 21.6076 23.6716L20.0001 22.0458L18.3926 23.6716C17.9597 24.1095 17.2577 24.1095 16.8247 23.6716C16.3918 23.2337 16.3918 22.5237 16.8247 22.0858L18.4322 20.46L16.9039 18.9142C16.4709 18.4763 16.4709 17.7663 16.9039 17.3284C17.3368 16.8905 18.0388 16.8905 18.4718 17.3284L20.0001 18.8742L21.5284 17.3284Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
5
packages/app/src/assets/images/logo-data-vault.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" rx="12" fill="#E3E5FF"/>
|
||||
<path d="M15.2727 10.0179H14.8636V8.8125C14.8636 8.06658 14.5619 7.35121 14.0249 6.82376C13.4879 6.29632 12.7595 6 12 6C11.2405 6 10.5121 6.29632 9.9751 6.82376C9.43807 7.35121 9.13636 8.06658 9.13636 8.8125V10.0179H8.72727C8.40189 10.0182 8.08994 10.1453 7.85985 10.3713C7.62977 10.5973 7.50036 10.9036 7.5 11.2232V16.0446C7.50036 16.3642 7.62977 16.6706 7.85985 16.8966C8.08994 17.1225 8.40189 17.2497 8.72727 17.25H15.2727C15.5981 17.2497 15.9101 17.1225 16.1401 16.8966C16.3702 16.6706 16.4996 16.3642 16.5 16.0446V11.2232C16.4996 10.9036 16.3702 10.5973 16.1401 10.3713C15.9101 10.1453 15.5981 10.0182 15.2727 10.0179ZM9.95455 8.8125C9.95455 8.2797 10.17 7.76872 10.5536 7.39197C10.9372 7.01523 11.4575 6.80357 12 6.80357C12.5425 6.80357 13.0628 7.01523 13.4464 7.39197C13.83 7.76872 14.0455 8.2797 14.0455 8.8125V10.0179H9.95455V8.8125ZM12.4091 13.7231V14.6384C12.4091 14.745 12.366 14.8471 12.2893 14.9225C12.2126 14.9978 12.1085 15.0402 12 15.0402C11.8915 15.0402 11.7874 14.9978 11.7107 14.9225C11.634 14.8471 11.5909 14.745 11.5909 14.6384V13.7231C11.4349 13.6346 11.313 13.4981 11.2441 13.3347C11.1752 13.1712 11.1631 12.99 11.2097 12.8192C11.2563 12.6483 11.359 12.4973 11.5019 12.3896C11.6448 12.282 11.8199 12.2236 12 12.2236C12.1801 12.2236 12.3552 12.282 12.4981 12.3896C12.641 12.4973 12.7437 12.6483 12.7903 12.8192C12.8369 12.99 12.8248 13.1712 12.7559 13.3347C12.687 13.4981 12.5651 13.6346 12.4091 13.7231Z" fill="#3700FF"/>
|
||||
<rect x="10.5" y="11.25" width="3" height="5.25" fill="#3700FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -4,10 +4,7 @@ import { ThemeProvider, theme, CSSReset } from '@blockstack/ui';
|
||||
import { Flex, Box, Text, Button } from '@blockstack/ui';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { IAppState } from '@store';
|
||||
import {
|
||||
selectAuthRequest,
|
||||
selectDecodedAuthRequest,
|
||||
} from '@store/permissions/selectors';
|
||||
import { selectAuthRequest, selectDecodedAuthRequest } from '@store/permissions/selectors';
|
||||
import { selectCurrentWallet } from '@store/wallet/selectors';
|
||||
import { AppManifest } from '@dev/types';
|
||||
import Gutter from '@components/gutter';
|
||||
@@ -28,11 +25,9 @@ const ActionsApp: React.FC = () => {
|
||||
return <>No auth request found</>;
|
||||
}
|
||||
|
||||
console.log(decodedAuthRequest);
|
||||
const loadManifest = async () => {
|
||||
const res = await fetch(decodedAuthRequest.manifest_uri);
|
||||
const json: AppManifest = await res.json();
|
||||
console.log(json);
|
||||
setManifest(json);
|
||||
};
|
||||
|
||||
@@ -63,19 +58,11 @@ const ActionsApp: React.FC = () => {
|
||||
<Flex pt={6} px={2} wrap="wrap">
|
||||
<Gutter base={6} multiplier={2} width="100%" />
|
||||
<Box width="100%" textAlign="center">
|
||||
<Text textStyle="display.large">
|
||||
{!manifest ? 'Loading...' : `Sign in to ${manifest.name}`}
|
||||
</Text>
|
||||
<Text textStyle="display.large">{!manifest ? 'Loading...' : `Sign in to ${manifest.name}`}</Text>
|
||||
</Box>
|
||||
<Gutter multiplier={1} width="100%" />
|
||||
<Box width="100%" textAlign="center" pt={6} px={4}>
|
||||
<Button
|
||||
isLoading={!manifest}
|
||||
variant="solid"
|
||||
mt={6}
|
||||
size="lg"
|
||||
onClick={signIn}
|
||||
>
|
||||
<Button isLoading={!manifest} variant="solid" mt={6} size="lg" onClick={signIn}>
|
||||
Continue
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
17
packages/app/src/ts/actions/containers/Onboarding.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Onboarding } from '@components/sign-up/onboarding';
|
||||
import { ThemeProvider, theme, CSSReset } from '@blockstack/ui';
|
||||
|
||||
export const OnboardingApp: React.FC = () => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<React.Fragment>
|
||||
<CSSReset />
|
||||
<Onboarding />
|
||||
</React.Fragment>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default hot(OnboardingApp);
|
||||
@@ -6,14 +6,15 @@ import { Store as ReduxStore } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { persistor, middlewareComponents } from '../background/store';
|
||||
import ActionsApp from './containers/ActionsApp';
|
||||
// import ActionsApp from './containers/ActionsApp';
|
||||
import OnboardingApp from './containers/Onboarding';
|
||||
import DevStore from '../dev/store';
|
||||
|
||||
const buildApp = (store: ReduxStore | Store) => {
|
||||
ReactDOM.render(
|
||||
<Provider store={store as any}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<ActionsApp />
|
||||
<OnboardingApp />
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
document.getElementById('actions-root')
|
||||
|
||||
@@ -2,21 +2,15 @@ export const openPopup = (actionsUrl: string) => {
|
||||
// window.open(actionsUrl, 'Blockstack', 'scrollbars=no,status=no,menubar=no,width=300px,height=200px,left=0,top=0')
|
||||
const height = 584;
|
||||
const width = 440;
|
||||
// width=440,height=584
|
||||
popupCenter(actionsUrl, 'Blockstack', width, height);
|
||||
};
|
||||
|
||||
// from https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen
|
||||
// open a popup, centered on the screen, with logic to handle dual-monitor setups
|
||||
export const popupCenter = (
|
||||
url: string,
|
||||
title: string,
|
||||
w: number,
|
||||
h: number
|
||||
) => {
|
||||
const dualScreenLeft =
|
||||
window.screenLeft != undefined ? window.screenLeft : window.screenX;
|
||||
const dualScreenTop =
|
||||
window.screenTop != undefined ? window.screenTop : window.screenY;
|
||||
export const popupCenter = (url: string, title: string, w: number, h: number) => {
|
||||
const dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX;
|
||||
const dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY;
|
||||
|
||||
const width = window.innerWidth
|
||||
? window.innerWidth
|
||||
@@ -31,14 +25,12 @@ export const popupCenter = (
|
||||
|
||||
const systemZoom = width / window.screen.availWidth;
|
||||
const left = (width - w) / 2 / systemZoom + dualScreenLeft;
|
||||
console.log(height - h, systemZoom, dualScreenTop);
|
||||
const top = (height - h) / 2 / systemZoom + dualScreenTop;
|
||||
const newWindow = window.open(
|
||||
url,
|
||||
title,
|
||||
// 'scrollbars=no, width=' + w / systemZoom + ', height=' + h / systemZoom + ', top=' + top + ', left=' + left
|
||||
`scrollbars=no, width=${w / systemZoom}, height=${h /
|
||||
systemZoom}, top=${top}, left=${left}`
|
||||
`scrollbars=no, width=${w / systemZoom}, height=${h / systemZoom}, top=${top}, left=${left}`
|
||||
);
|
||||
|
||||
// Puts focus on the newWindow
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
combineReducers,
|
||||
createStore,
|
||||
Store,
|
||||
compose,
|
||||
applyMiddleware,
|
||||
} from 'redux';
|
||||
import { combineReducers, createStore, Store, compose, applyMiddleware } from 'redux';
|
||||
import { persistReducer, persistStore } from 'redux-persist';
|
||||
import thunk from 'redux-thunk';
|
||||
import storage from 'redux-persist/lib/storage';
|
||||
@@ -12,33 +6,28 @@ import settings, { IAppSettings } from './settings/reducer';
|
||||
import { walletReducer, WalletState } from './wallet';
|
||||
import { permissionsReducer, PermissionsState } from './permissions';
|
||||
import { WalletTransform } from './transforms';
|
||||
|
||||
import 'redux';
|
||||
// Enhance the Action interface with the option of a payload.
|
||||
// While still importing the Action interface from redux.
|
||||
declare module 'redux' {
|
||||
export interface Action<T = any, P = any> {
|
||||
type: T;
|
||||
payload?: P;
|
||||
}
|
||||
}
|
||||
import { onboardingReducer } from './onboarding/reducer';
|
||||
import { OnboardingState } from './onboarding/types';
|
||||
|
||||
export interface IAppState {
|
||||
settings: IAppSettings;
|
||||
wallet: WalletState;
|
||||
permissions: PermissionsState;
|
||||
onboarding: OnboardingState;
|
||||
}
|
||||
|
||||
const reducers = combineReducers<IAppState>({
|
||||
settings,
|
||||
wallet: walletReducer,
|
||||
permissions: permissionsReducer,
|
||||
onboarding: onboardingReducer,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
storage,
|
||||
key: 'blockstack-redux',
|
||||
transforms: [WalletTransform],
|
||||
whitelist: ['settings', 'wallet', 'permissions'],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, reducers);
|
||||
@@ -47,18 +36,12 @@ const _window = window as any;
|
||||
|
||||
const middleware = compose(
|
||||
applyMiddleware(thunk),
|
||||
_window.__REDUX_DEVTOOLS_EXTENSION__
|
||||
? _window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||
: (f: unknown) => f
|
||||
_window.__REDUX_DEVTOOLS_EXTENSION__ ? _window.__REDUX_DEVTOOLS_EXTENSION__() : (f: unknown) => f
|
||||
);
|
||||
|
||||
export const middlewareComponents = [thunk];
|
||||
|
||||
export const store: Store<IAppState> = createStore(
|
||||
persistedReducer,
|
||||
undefined,
|
||||
middleware
|
||||
);
|
||||
export const store: Store<IAppState> = createStore(persistedReducer, undefined, middleware);
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
|
||||
|
||||
87
packages/app/src/ts/background/store/onboarding/actions.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
OnboardingActions,
|
||||
CHANGE_PAGE,
|
||||
Screen,
|
||||
DEFAULT_PASSWORD,
|
||||
SAVE_KEY,
|
||||
SAVE_AUTH_REQUEST,
|
||||
SET_MAGIC_RECOVERY_CODE,
|
||||
} from './types';
|
||||
import { decodeToken } from 'jsontokens';
|
||||
import { doGenerateWallet } from '@store/wallet';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { decrypt } from '@blockstack/keychain';
|
||||
import { DecodedAuthRequest, AppManifest } from '@dev/types';
|
||||
|
||||
export const doChangeScreen = (screen: Screen): OnboardingActions => ({
|
||||
type: CHANGE_PAGE,
|
||||
screen,
|
||||
});
|
||||
|
||||
export const doSaveSecretKey = (secretKey: string): OnboardingActions => ({
|
||||
type: SAVE_KEY,
|
||||
secretKey,
|
||||
});
|
||||
|
||||
export const doSetMagicRecoveryCode = (magicRecoveryCode: string): OnboardingActions => ({
|
||||
type: SET_MAGIC_RECOVERY_CODE,
|
||||
magicRecoveryCode,
|
||||
});
|
||||
|
||||
export function doCreateSecretKey(): ThunkAction<void, {}, {}, OnboardingActions> {
|
||||
return async dispatch => {
|
||||
const wallet = await dispatch(doGenerateWallet(DEFAULT_PASSWORD));
|
||||
const secretKey = await decrypt(wallet.encryptedBackupPhrase, DEFAULT_PASSWORD);
|
||||
dispatch(doSaveSecretKey(secretKey));
|
||||
};
|
||||
}
|
||||
|
||||
const loadManifest = async (decodedAuthRequest: DecodedAuthRequest) => {
|
||||
const res = await fetch(decodedAuthRequest.manifest_uri);
|
||||
const json: AppManifest = await res.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
interface SaveAuthRequestParams {
|
||||
appName: string;
|
||||
appIcon: string;
|
||||
decodedAuthRequest: DecodedAuthRequest;
|
||||
authRequest: string;
|
||||
}
|
||||
|
||||
const saveAuthRequest = ({
|
||||
appName,
|
||||
appIcon,
|
||||
decodedAuthRequest,
|
||||
authRequest,
|
||||
}: SaveAuthRequestParams): OnboardingActions => {
|
||||
return {
|
||||
type: SAVE_AUTH_REQUEST,
|
||||
appName,
|
||||
appIcon,
|
||||
decodedAuthRequest,
|
||||
authRequest,
|
||||
};
|
||||
};
|
||||
|
||||
export function doSaveAuthRequest(authRequest: string): ThunkAction<void, {}, {}, OnboardingActions> {
|
||||
return async dispatch => {
|
||||
const { payload } = decodeToken(authRequest);
|
||||
const decodedAuthRequest = (payload as unknown) as DecodedAuthRequest;
|
||||
let appName = decodedAuthRequest.appDetails?.name;
|
||||
let appIcon = decodedAuthRequest.appDetails?.icon;
|
||||
if (!appName || !appIcon) {
|
||||
const appManifest = await loadManifest(decodedAuthRequest);
|
||||
appName = appManifest.name;
|
||||
appIcon = appManifest.icons[0].src;
|
||||
}
|
||||
dispatch(
|
||||
saveAuthRequest({
|
||||
decodedAuthRequest,
|
||||
authRequest,
|
||||
appName,
|
||||
appIcon,
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
51
packages/app/src/ts/background/store/onboarding/reducer.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Reducer } from 'redux';
|
||||
import {
|
||||
OnboardingActions,
|
||||
OnboardingState,
|
||||
CHANGE_PAGE,
|
||||
Screen,
|
||||
SAVE_KEY,
|
||||
SAVE_AUTH_REQUEST,
|
||||
SET_MAGIC_RECOVERY_CODE,
|
||||
} from './types';
|
||||
|
||||
const initialState: OnboardingState = {
|
||||
screen: Screen.INTRO,
|
||||
};
|
||||
|
||||
export const onboardingReducer: Reducer<OnboardingState, OnboardingActions> = (
|
||||
state = initialState,
|
||||
action: OnboardingActions
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case CHANGE_PAGE:
|
||||
return {
|
||||
...state,
|
||||
screen: action.screen,
|
||||
};
|
||||
case SAVE_KEY:
|
||||
return {
|
||||
...state,
|
||||
secretKey: action.secretKey,
|
||||
};
|
||||
case SAVE_AUTH_REQUEST:
|
||||
const newState = {
|
||||
...state,
|
||||
authRequest: action.authRequest,
|
||||
decodedAuthRequest: action.decodedAuthRequest,
|
||||
appName: action.appName,
|
||||
appIcon: action.appIcon,
|
||||
};
|
||||
if (action.decodedAuthRequest.sendToSignIn) {
|
||||
newState.screen = Screen.SIGN_IN;
|
||||
}
|
||||
return newState;
|
||||
case SET_MAGIC_RECOVERY_CODE:
|
||||
return {
|
||||
...state,
|
||||
magicRecoveryCode: action.magicRecoveryCode,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
29
packages/app/src/ts/background/store/onboarding/selectors.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { IAppState } from '@store';
|
||||
|
||||
export const selectCurrentScreen = (state: IAppState) => {
|
||||
return state.onboarding.screen;
|
||||
};
|
||||
|
||||
export const selectSecretKey = (state: IAppState) => {
|
||||
return state.onboarding.secretKey;
|
||||
};
|
||||
|
||||
export const selectDecodedAuthRequest = (state: IAppState) => {
|
||||
return state.onboarding.decodedAuthRequest;
|
||||
};
|
||||
|
||||
export const selectAuthRequest = (state: IAppState) => {
|
||||
return state.onboarding.authRequest;
|
||||
};
|
||||
|
||||
export const selectAppName = (state: IAppState) => {
|
||||
return state.onboarding.appName;
|
||||
};
|
||||
|
||||
export const selectAppIcon = (state: IAppState) => {
|
||||
return state.onboarding.appIcon;
|
||||
};
|
||||
|
||||
export const selectMagicRecoveryCode = (state: IAppState) => {
|
||||
return state.onboarding.magicRecoveryCode;
|
||||
};
|
||||
56
packages/app/src/ts/background/store/onboarding/types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { DecodedAuthRequest } from '@dev/types';
|
||||
|
||||
export const CHANGE_PAGE = 'ONBOARDING/CHANGE_PAGE';
|
||||
export const SAVE_KEY = 'ONBOARDING/SAVE_KEY';
|
||||
export const SAVE_AUTH_REQUEST = 'ONBOARDING/SAVE_AUTH_REQUEST';
|
||||
export const SET_MAGIC_RECOVERY_CODE = 'ONBOARDING/SET_MAGIC_RECOVERY_CODE';
|
||||
|
||||
export enum Screen {
|
||||
INTRO = 'screens/INTRO',
|
||||
HOW_IT_WORKS = 'screens/HOW_IT_WORKS',
|
||||
CREATE = 'screens/CREATE',
|
||||
SECRET_KEY = 'screens/SECRET_KEY',
|
||||
SAVE_KEY = 'screens/SAVE_KEY',
|
||||
CONNECT_APP = 'screens/CONNECT_APP',
|
||||
CONNECTED = 'screens/CONNECTED',
|
||||
SIGN_IN = 'screens/SIGN_IN',
|
||||
RECOVERY_CODE = 'screens/RECOVERY_CODE',
|
||||
}
|
||||
|
||||
// TODO: clarify usage of password for local key encryption
|
||||
export const DEFAULT_PASSWORD = 'password';
|
||||
|
||||
export interface OnboardingState {
|
||||
screen: Screen;
|
||||
secretKey?: string;
|
||||
authRequest?: string;
|
||||
decodedAuthRequest?: DecodedAuthRequest;
|
||||
appName?: string;
|
||||
appIcon?: string;
|
||||
magicRecoveryCode?: string;
|
||||
}
|
||||
|
||||
interface ChangePageAction {
|
||||
type: typeof CHANGE_PAGE;
|
||||
screen: Screen;
|
||||
}
|
||||
|
||||
interface StoreSecretKey {
|
||||
type: typeof SAVE_KEY;
|
||||
secretKey: string;
|
||||
}
|
||||
|
||||
interface SaveAuthRequest {
|
||||
type: typeof SAVE_AUTH_REQUEST;
|
||||
appName: string;
|
||||
appIcon: string;
|
||||
decodedAuthRequest: DecodedAuthRequest;
|
||||
authRequest: string;
|
||||
}
|
||||
|
||||
interface SetMagicRecoveryCode {
|
||||
type: typeof SET_MAGIC_RECOVERY_CODE;
|
||||
magicRecoveryCode: string;
|
||||
}
|
||||
|
||||
export type OnboardingActions = ChangePageAction | StoreSecretKey | SetMagicRecoveryCode | SaveAuthRequest;
|
||||
@@ -7,6 +7,6 @@ export const doAuthRequest = (authRequest: string): PermissionsActions => {
|
||||
return {
|
||||
type: AUTH_REQUEST,
|
||||
authRequest,
|
||||
decodedAuthRequest: payload as DecodedAuthRequest,
|
||||
decodedAuthRequest: (payload as unknown) as DecodedAuthRequest,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,10 +6,10 @@ const initialState: PermissionsState = {
|
||||
decodedAuthRequest: null,
|
||||
};
|
||||
|
||||
export const permissionsReducer: Reducer<
|
||||
PermissionsState,
|
||||
PermissionsActions
|
||||
> = (state = initialState, action: PermissionsActions): PermissionsState => {
|
||||
export const permissionsReducer: Reducer<PermissionsState, PermissionsActions> = (
|
||||
state = initialState,
|
||||
action: PermissionsActions
|
||||
): PermissionsState => {
|
||||
switch (action.type) {
|
||||
case AUTH_REQUEST:
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IAppState } from '@store';
|
||||
|
||||
export const selectAuthRequest = (state: IAppState) =>
|
||||
state.permissions.authRequest;
|
||||
export const selectAuthRequest = (state: IAppState) => state.permissions.authRequest;
|
||||
|
||||
export const selectDecodedAuthRequest = (state: IAppState) =>
|
||||
state.permissions.decodedAuthRequest;
|
||||
export const selectDecodedAuthRequest = (state: IAppState) => state.permissions.decodedAuthRequest;
|
||||
|
||||
@@ -10,10 +10,7 @@ const initialState: IAppSettings = {
|
||||
theme: 'light',
|
||||
};
|
||||
|
||||
const settings: Reducer<IAppSettings, SettingsActions> = (
|
||||
state = initialState,
|
||||
action
|
||||
) => {
|
||||
const settings: Reducer<IAppSettings, SettingsActions> = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'DARK_THEME':
|
||||
return { ...state, theme: 'dark' };
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { Wallet } from '@blockstack/keychain';
|
||||
import {
|
||||
WalletActions,
|
||||
RESTORE_WALLET,
|
||||
IS_RESTORING_WALLET,
|
||||
GENERATE_WALLET,
|
||||
} from './types';
|
||||
import { WalletActions, RESTORE_WALLET, IS_RESTORING_WALLET, GENERATE_WALLET } from './types';
|
||||
|
||||
export function didRestoreWallet(wallet: Wallet): WalletActions {
|
||||
return {
|
||||
@@ -27,22 +22,20 @@ function isRestoringWallet(): WalletActions {
|
||||
};
|
||||
}
|
||||
|
||||
export function doStoreSeed(
|
||||
seed: string
|
||||
): ThunkAction<void, {}, {}, WalletActions> {
|
||||
export function doStoreSeed(seed: string, password: string): ThunkAction<Promise<Wallet>, {}, {}, WalletActions> {
|
||||
return async dispatch => {
|
||||
dispatch(isRestoringWallet());
|
||||
const wallet = await Wallet.restore('password', seed);
|
||||
const wallet = await Wallet.restore(password, seed);
|
||||
dispatch(didRestoreWallet(wallet));
|
||||
return wallet;
|
||||
};
|
||||
}
|
||||
|
||||
export function doGenerateWallet(
|
||||
password: string
|
||||
): ThunkAction<void, {}, {}, WalletActions> {
|
||||
export function doGenerateWallet(password: string): ThunkAction<Promise<Wallet>, {}, {}, WalletActions> {
|
||||
return async dispatch => {
|
||||
dispatch(isRestoringWallet());
|
||||
const wallet = await Wallet.generate(password);
|
||||
dispatch(didGenerateWallet(wallet));
|
||||
return wallet;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { Reducer } from 'redux';
|
||||
import {
|
||||
WalletActions,
|
||||
WalletState,
|
||||
RESTORE_WALLET,
|
||||
IS_RESTORING_WALLET,
|
||||
GENERATE_WALLET,
|
||||
} from './types';
|
||||
import { WalletActions, WalletState, RESTORE_WALLET, IS_RESTORING_WALLET, GENERATE_WALLET } from './types';
|
||||
|
||||
const initialState: WalletState = {
|
||||
seed: null,
|
||||
|
||||
@@ -6,5 +6,4 @@ export const selectCurrentWallet = (state: IAppState) => {
|
||||
|
||||
export const selectSeed = (state: IAppState) => state.wallet.seed;
|
||||
|
||||
export const selectIsRestoringWallet = (state: IAppState) =>
|
||||
state.wallet.isRestoringWallet;
|
||||
export const selectIsRestoringWallet = (state: IAppState) => state.wallet.isRestoringWallet;
|
||||
|
||||
@@ -24,7 +24,4 @@ export interface WalletState {
|
||||
currentWallet: Wallet | null;
|
||||
}
|
||||
|
||||
export type WalletActions =
|
||||
| StoreSeedAction
|
||||
| IsRestoringWalletAction
|
||||
| GenerateWalletAction;
|
||||
export type WalletActions = StoreSeedAction | IsRestoringWalletAction | GenerateWalletAction;
|
||||
|
||||
76
packages/app/src/ts/common/track.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
const LANDING_SIGN_IN = 'clicked_sign_in_dv';
|
||||
const LANDING_TRY_WINK = 'clicked_try_data_vault';
|
||||
const LANDING_SIGN_IN_MODAL_CONTINUE = 'clicked_continue_with_data_vault';
|
||||
|
||||
const SUBMIT_ABOUT_YOU = 'submitted_about_you_form';
|
||||
const SUBMIT_LOOKING_FOR = 'submitted_preferences_form';
|
||||
// SUBMIT_LOOKING_FOR
|
||||
// - "looking_for"
|
||||
// - "min_age"
|
||||
// - "max_age"
|
||||
// - "ideal_connection"
|
||||
|
||||
const INTRO_CLOSED = 'auth_new_closed_intro';
|
||||
const INTRO_CREATE = 'auth_new_created';
|
||||
const INTRO_SIGN_IN = 'auth_new_selected_sign_in_from_intro';
|
||||
const INTRO_HOW_WORKS = 'auth_new_selected_how_it_works_from_intro';
|
||||
|
||||
const SECRET_KEY_INTRO_CLOSED = 'auth_new_closed_secret_key_intro';
|
||||
const SECRET_KEY_INTRO_COPIED = 'auth_new_copied_secret_key';
|
||||
|
||||
const SECRET_KEY_INSTR_CLOSE = 'auth_new_closed_secret_key_instructions';
|
||||
const SECRET_KEY_INSTR_CONFIRMED = 'auth_new_confirmed_secret_key';
|
||||
|
||||
const SECRET_KEY_FAQ_WHERE = 'auth_new_selected_secret_key_instruction_where';
|
||||
const SECRET_KEY_FAQ_LOSE = 'auth_new_selected_secret_key_instruction_lose';
|
||||
const SECRET_KEY_FAQ_WHEN = 'auth_new_selected_secret_key_instruction_when';
|
||||
const SECRET_KEY_FAQ_PASSWORD = 'auth_new_selected_secret_key_instruction_password';
|
||||
|
||||
const CONNECT_CLOSED = 'auth_new_closed_secret_key_entry';
|
||||
const CONNECT_SAVED = 'auth_new_entered_correct_secret_key';
|
||||
const CONNECT_INCORRECT = 'auth_new_entered_incorrect_secret_key';
|
||||
const CONNECT_BACK = 'auth_new_retreated_secret_key_entry';
|
||||
|
||||
const SIGN_IN_CLOSED = 'auth_existing_closed';
|
||||
const SIGN_IN_CORRECT = 'auth_existing_entered_correct_secret_key';
|
||||
const SIGN_IN_CREATE = 'auth_existing_create_data_vault'; // was not specified in wink app spec
|
||||
const SIGN_IN_FORGOT = 'auth_existing_forgot_secret_key'; // was not specified in wink app spec
|
||||
const SIGN_IN_INCORRECT = 'auth_existing_entered_incorrect_secret_key';
|
||||
|
||||
const doTrack = (type: string, payload?: string | object) => {
|
||||
// if (process.browser) {
|
||||
// console.log('tracking: ', { type, payload });
|
||||
// window.analytics.track(type, payload);
|
||||
// }
|
||||
console.log('Tracking:', { type, payload });
|
||||
};
|
||||
|
||||
export {
|
||||
doTrack,
|
||||
LANDING_SIGN_IN,
|
||||
LANDING_TRY_WINK,
|
||||
LANDING_SIGN_IN_MODAL_CONTINUE,
|
||||
SUBMIT_ABOUT_YOU,
|
||||
SUBMIT_LOOKING_FOR,
|
||||
INTRO_CLOSED, // TODO: build in ability to pass tracking fn to modal close action
|
||||
INTRO_CREATE,
|
||||
INTRO_SIGN_IN,
|
||||
INTRO_HOW_WORKS,
|
||||
SECRET_KEY_INTRO_CLOSED, // TODO: build in ability to pass tracking fn to modal close action
|
||||
SECRET_KEY_INTRO_COPIED,
|
||||
SECRET_KEY_INSTR_CLOSE, // TODO: build in ability to pass tracking fn to modal close action
|
||||
SECRET_KEY_INSTR_CONFIRMED,
|
||||
SECRET_KEY_FAQ_WHERE,
|
||||
SECRET_KEY_FAQ_LOSE,
|
||||
SECRET_KEY_FAQ_WHEN,
|
||||
SECRET_KEY_FAQ_PASSWORD,
|
||||
CONNECT_CLOSED, // TODO: build in ability to pass tracking fn to modal close action
|
||||
CONNECT_SAVED,
|
||||
CONNECT_INCORRECT,
|
||||
CONNECT_BACK,
|
||||
SIGN_IN_CLOSED, // TODO: build in ability to pass tracking fn to modal close action
|
||||
SIGN_IN_CREATE,
|
||||
SIGN_IN_CORRECT,
|
||||
SIGN_IN_FORGOT,
|
||||
SIGN_IN_INCORRECT,
|
||||
};
|
||||
68
packages/app/src/ts/common/utils.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { DecodedAuthRequest } from '@dev/types';
|
||||
|
||||
export const getAuthRequestParam = () => {
|
||||
const { search } = document.location;
|
||||
const matches = /authRequest=(.*)&?/.exec(search);
|
||||
if (matches && matches.length === 2) {
|
||||
return matches[1];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const authenticationInit = () => {
|
||||
const authRequest = getAuthRequestParam();
|
||||
if (authRequest) {
|
||||
return authRequest;
|
||||
} else {
|
||||
console.log('No auth request found');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
interface FinalizeAuthParams {
|
||||
decodedAuthRequest: DecodedAuthRequest;
|
||||
authResponse: string;
|
||||
authRequest: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function at the end of onboarding.
|
||||
*
|
||||
* It works by waiting for a cross-origin message from the origin app. It has to wait
|
||||
* for this message, because that origin created the popup originally, which is why it's allowed
|
||||
* to make the cross-origin message. Once we get that message, we are allowed to send a
|
||||
* message back to that origin.
|
||||
*
|
||||
* Using cross-origin messaging allows for a better UX, because the origin app can receive a callback
|
||||
* when authentication is done.
|
||||
*
|
||||
* If the cross-origin messaging fails for any reason, just fall back to the usual redirect method,
|
||||
* but using a new tab.
|
||||
*
|
||||
*/
|
||||
export const finalizeAuthResponse = ({ decodedAuthRequest, authRequest, authResponse }: FinalizeAuthParams) => {
|
||||
let didSendMessageBack = false;
|
||||
setTimeout(() => {
|
||||
if (!didSendMessageBack) {
|
||||
const redirect = `${decodedAuthRequest.redirect_uri}?authResponse=${authResponse}`;
|
||||
window.open(redirect);
|
||||
}
|
||||
window.close();
|
||||
}, 500);
|
||||
window.addEventListener('message', event => {
|
||||
if (authRequest && event.data.authRequest === authRequest) {
|
||||
const isWindow = !(event.source instanceof MessagePort) && !(event.source instanceof ServiceWorker);
|
||||
if (isWindow) {
|
||||
didSendMessageBack = true;
|
||||
(event.source as Window).postMessage(
|
||||
{
|
||||
authRequest,
|
||||
authResponse,
|
||||
source: 'blockstack-app',
|
||||
},
|
||||
event.origin
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -7,12 +7,8 @@ interface GutterProps extends BoxProps {
|
||||
base?: number;
|
||||
}
|
||||
|
||||
const Gutter: React.FC<GutterProps> = ({
|
||||
base = 6,
|
||||
multiplier,
|
||||
...rest
|
||||
}: GutterProps) => {
|
||||
const boxes = [];
|
||||
const Gutter: React.FC<GutterProps> = ({ base = 6, multiplier, ...rest }: GutterProps) => {
|
||||
const boxes: React.ReactNode[] = [];
|
||||
for (let index = 0; index < multiplier; index++) {
|
||||
boxes.push(<Box py={base} key={`gutter-${index}`} {...rest} />);
|
||||
}
|
||||
|
||||
21
packages/app/src/ts/components/image.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ImageProps {
|
||||
src?: string;
|
||||
alt?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const Image: React.FC<ImageProps> = ({ ...props }) => {
|
||||
return (
|
||||
<img
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
display: 'block',
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Image };
|
||||
17
packages/app/src/ts/components/link.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Text, Box } from '@blockstack/ui';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box/types';
|
||||
|
||||
interface LinkProps extends BoxProps {
|
||||
_hover?: BoxProps;
|
||||
}
|
||||
|
||||
const Link: React.FC<LinkProps> = ({ _hover = {}, children, ...rest }) => (
|
||||
<Box {...rest}>
|
||||
<Text _hover={{ textDecoration: 'underline', cursor: 'pointer', ..._hover }} fontWeight="medium">
|
||||
{children}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export { Link };
|
||||
10
packages/app/src/ts/components/logo.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@blockstack/ui';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box';
|
||||
|
||||
export const Logo: React.FC<BoxProps> = ({ ...props }) => (
|
||||
<Box {...props}>
|
||||
<img src="/assets/images/logo-data-vault.svg" alt="Data vault logo" />
|
||||
</Box>
|
||||
);
|
||||
21
packages/app/src/ts/components/sign-up/card/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Box, Text } from '@blockstack/ui';
|
||||
|
||||
interface CardProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const Card: React.FC<CardProps> = ({ title, children, ...rest }) => {
|
||||
return (
|
||||
<Box borderRadius="6px" border="1px solid" borderColor="#E5E5EC" boxShadow="mid" textAlign="center" {...rest}>
|
||||
<Box borderBottom="1px solid" borderColor="#E5E5EC" p={3}>
|
||||
<Text color="ink.600">{title}</Text>
|
||||
</Box>
|
||||
<Box px={10} py={5}>
|
||||
<Text>{children}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export { Card };
|
||||
36
packages/app/src/ts/components/sign-up/checklist/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, Text, Stack } from '@blockstack/ui';
|
||||
import CheckIcon from 'mdi-react/CheckIcon';
|
||||
|
||||
/**
|
||||
* This renders a list of items with a checkmark to their left
|
||||
*/
|
||||
|
||||
const Checkmark: React.FC = props => (
|
||||
<Box transform="translateY(-2px)" color="ink.300" mr={2} pt={1} {...props}>
|
||||
<CheckIcon size={18} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
const Item: React.FC = ({ children }) => (
|
||||
<Text fontSize="14px" color="ink.600">
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
interface CheckListProps {
|
||||
items: string[];
|
||||
}
|
||||
|
||||
const CheckList: React.FC<CheckListProps> = ({ items, ...rest }) => (
|
||||
<Stack spacing={3} {...rest}>
|
||||
{items.map((text, key) => (
|
||||
<Flex align="flex-start" textAlign="left" key={key}>
|
||||
<Checkmark />
|
||||
<Item>{text}</Item>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export { CheckList };
|
||||
103
packages/app/src/ts/components/sign-up/collapse/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, Text } from '@blockstack/ui';
|
||||
import ChevronDownIcon from 'mdi-react/ChevronDownIcon';
|
||||
import { useHover } from 'use-events';
|
||||
import { doTrack } from '@common/track';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box';
|
||||
// import { doTrack } from '../../../common/track';
|
||||
|
||||
interface TitleProps extends BoxProps {
|
||||
isFirst: boolean;
|
||||
isOpen: boolean;
|
||||
hovered: boolean;
|
||||
}
|
||||
|
||||
const TitleElement: React.FC<TitleProps> = ({ onClick, isFirst, isOpen, hovered, title }) => (
|
||||
<Flex
|
||||
align="center"
|
||||
borderBottom="1px solid"
|
||||
borderTop={isFirst ? '1px solid' : 'unset'}
|
||||
borderColor="#E5E5EC" // this is not currently in the UI lib, asked jasper about it but he was out of office
|
||||
py={3}
|
||||
justify="space-between"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Box>
|
||||
<Text color={isOpen || hovered ? 'ink' : 'ink.600'}>{title}</Text>
|
||||
</Box>
|
||||
<Box color="ink.300" transform={isOpen ? 'rotate(180deg)' : 'none'}>
|
||||
<ChevronDownIcon />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
interface BodyProps {
|
||||
body: string;
|
||||
}
|
||||
|
||||
const Body: React.FC<BodyProps> = ({ body }) => (
|
||||
<Box
|
||||
borderBottom="1px solid"
|
||||
borderColor="#E5E5EC" // this is not currently in the UI lib, asked jasper about it but he was out of office
|
||||
py={3}
|
||||
>
|
||||
<Text color="ink.600">
|
||||
<div dangerouslySetInnerHTML={{ __html: body }} />
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
interface Data {
|
||||
title: string;
|
||||
body: string;
|
||||
tracking?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
interface CollapseProps {
|
||||
data: Data[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This component renders a list of clickable items that
|
||||
* will reveal content onClick, then hide it on any other
|
||||
* onClick of an item
|
||||
*/
|
||||
const Collapse: React.FC<CollapseProps> = ({ data, ...rest }) => {
|
||||
const [open, setOpen] = React.useState<number | null>(null);
|
||||
const handleOpen = (key: number) => (key === open ? setOpen(null) : setOpen(key));
|
||||
return (
|
||||
<Box fontSize="14px" {...rest}>
|
||||
{/*
|
||||
It's important to include the rest of the props
|
||||
because in certain cases we want to add/adjust spacing,
|
||||
eg if this is contained in <Stack> it will add
|
||||
spacing automatically
|
||||
|
||||
A pattern we're trying to follow is that these components
|
||||
will not have margin in and of themselves. No component should
|
||||
have default whitespace
|
||||
*/}
|
||||
{data.map(({ title, body, tracking }, key) => {
|
||||
const [hovered, bind] = useHover();
|
||||
return (
|
||||
<Box key={key} cursor={hovered ? 'pointer' : undefined} {...bind}>
|
||||
<TitleElement
|
||||
onClick={() => {
|
||||
tracking && doTrack(tracking);
|
||||
handleOpen(key);
|
||||
}}
|
||||
isFirst={key === 0}
|
||||
title={title}
|
||||
hovered={hovered}
|
||||
isOpen={key === open}
|
||||
/>
|
||||
{open === key ? <Body body={body} /> : null}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export { Collapse };
|
||||
155
packages/app/src/ts/components/sign-up/modal/index.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React from 'react';
|
||||
import { Box, PseudoBox, Flex, Text } from '@blockstack/ui';
|
||||
// import { useHover } from 'use-events';
|
||||
import { Logo } from '@components/logo';
|
||||
import ChevronRightIcon from 'mdi-react/ChevronRightIcon';
|
||||
import CloseIcon from 'mdi-react/CloseIcon';
|
||||
import { Image } from '@components/image';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box';
|
||||
|
||||
interface ModalContextTypes {
|
||||
isOpen: boolean;
|
||||
doOpenModal?: () => void;
|
||||
doCloseModal?: () => void;
|
||||
}
|
||||
|
||||
const ModalContext = React.createContext<ModalContextTypes>({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
const useModalState = () => React.useContext(ModalContext);
|
||||
|
||||
interface HeaderTitleProps {
|
||||
title: string;
|
||||
hideIcon?: boolean;
|
||||
}
|
||||
|
||||
const HeaderTitle: React.FC<HeaderTitleProps> = ({ hideIcon = false, title }) => (
|
||||
<Flex align="center">
|
||||
{hideIcon ? null : <Logo mr={2} />}
|
||||
<Text fontWeight="bold" fontSize={'12px'}>
|
||||
{title}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const HeaderCloseButton: React.FC<BoxProps> = ({ onClick }) => (
|
||||
<PseudoBox color="ink.300" opacity={0.5} _hover={{ opacity: 1, cursor: 'pointer' }} onClick={onClick}>
|
||||
<CloseIcon size={20} />
|
||||
</PseudoBox>
|
||||
);
|
||||
|
||||
interface AppIconProps {
|
||||
src: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const AppIcon: React.FC<AppIconProps> = ({ src, name, ...rest }) => (
|
||||
<Box size={6} {...rest}>
|
||||
<Image src={src} alt={name} title={name} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
interface IModalHeader {
|
||||
appIcon?: string;
|
||||
appName?: string;
|
||||
title: string;
|
||||
close: () => void;
|
||||
hideIcon?: boolean;
|
||||
}
|
||||
|
||||
const ModalHeader = ({ appIcon, close, title, hideIcon, appName, ...rest }: IModalHeader) => {
|
||||
return (
|
||||
<Flex
|
||||
p={[4, 5]}
|
||||
borderBottom="1px solid"
|
||||
borderBottomColor="inherit"
|
||||
borderRadius={['unset', '6px 6px 0 0']}
|
||||
bg="white"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
{...rest}
|
||||
>
|
||||
<Flex align="center">
|
||||
{appIcon ? <AppIcon src={appIcon} name={appName || 'loading'} /> : null}
|
||||
{appIcon ? (
|
||||
<Box pr={1} pl={2} color="ink.300">
|
||||
<ChevronRightIcon size={20} />
|
||||
</Box>
|
||||
) : null}
|
||||
<HeaderTitle hideIcon={hideIcon} title={title} />
|
||||
</Flex>
|
||||
<HeaderCloseButton onClick={close} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContent: React.FC = ({ children, ...rest }) => {
|
||||
return (
|
||||
<Flex width="100%" height="100%" {...rest}>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
interface ModalProps {
|
||||
footer?: React.ReactNode;
|
||||
appIcon?: string;
|
||||
appName?: string;
|
||||
title: string;
|
||||
hideIcon?: boolean;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ footer = null, appIcon, title, hideIcon = false, close, appName, children }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
position="fixed"
|
||||
size="100%"
|
||||
left={0}
|
||||
top={0}
|
||||
align={['flex-end', 'center']}
|
||||
justify="center"
|
||||
bg="rgba(0,0,0,0.48)"
|
||||
zIndex={99}
|
||||
>
|
||||
<Flex
|
||||
bg="white"
|
||||
direction="column"
|
||||
minWidth={['100%', '440px']}
|
||||
width="100%"
|
||||
maxWidth={['100%', '440px']}
|
||||
maxHeight={['100vh', 'calc(100vh - 48px)']}
|
||||
borderRadius={['unset', '6px']}
|
||||
boxShadow="high"
|
||||
>
|
||||
<ModalHeader hideIcon={hideIcon} close={close} appIcon={appIcon} appName={appName} title={title} />
|
||||
<Flex width="100%" p={[5, 8]} overflowY="auto" flexGrow={1} position="relative">
|
||||
<ModalContent>{children}</ModalContent>
|
||||
</Flex>
|
||||
{footer ? footer : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalProvider: React.FC = props => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const doOpenModal = () => (!isOpen ? setIsOpen(true) : null);
|
||||
const doCloseModal = () => (isOpen ? setIsOpen(true) : null);
|
||||
return (
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
isOpen,
|
||||
doOpenModal,
|
||||
doCloseModal,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { ModalProvider, Modal, useModalState };
|
||||
64
packages/app/src/ts/components/sign-up/onboarding/data.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { SECRET_KEY_FAQ_WHERE, SECRET_KEY_FAQ_LOSE, SECRET_KEY_FAQ_WHEN, SECRET_KEY_FAQ_PASSWORD } from '@common/track';
|
||||
|
||||
const faqs = (appName: string) => {
|
||||
return [
|
||||
{
|
||||
title: 'Where should I save my Secret Key?',
|
||||
body: `
|
||||
Save your Secret Key in a place where only you can find it. For example:
|
||||
<ul style="list-style: none;">
|
||||
<li>• A password manager such as 1password</li>
|
||||
<li>• Your Notes app, protected with a password</li>
|
||||
<li>• Written down and kept somewhere safe</li>
|
||||
</ul>
|
||||
Don’t save it anywhere where others can find it, or on a website you do not trust. Anybody with access to your Secret Key will have access to your Data Vault and apps.
|
||||
`,
|
||||
tracking: SECRET_KEY_FAQ_WHERE,
|
||||
},
|
||||
{
|
||||
title: 'What if I lose my Secret Key?',
|
||||
body: `If you lose your Secret Key, it will be lost forever. Neither ${appName} nor Data Vault can help you recover your Secret Key.`,
|
||||
tracking: SECRET_KEY_FAQ_LOSE,
|
||||
},
|
||||
{
|
||||
title: 'When will I need my Secret Key?',
|
||||
body:
|
||||
'You will use your Secret Key to unlock your Data Vault and connect it to new apps — like a password, but much more secure.',
|
||||
tracking: SECRET_KEY_FAQ_WHEN,
|
||||
},
|
||||
{
|
||||
title: 'Why don’t I have a password?',
|
||||
body:
|
||||
'Your Secret Key is much stronger than a combination of email and password — it’s virtually impossible to hack. Only you know it, which keeps your apps and data secure.',
|
||||
tracking: SECRET_KEY_FAQ_PASSWORD,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const howDataVaultWorks = [
|
||||
{
|
||||
icon: '/assets/images/icon-cross-over-eye.svg',
|
||||
title: 'Private data storage',
|
||||
body:
|
||||
'Normally, companies store your data on their servers for them to keep. Data Vault stores your encrypted data independently from the app, so companies like Nurx (and even Data Vault) can’t have access.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-padlock.svg',
|
||||
title: 'Encryption that’s always on',
|
||||
body:
|
||||
'Encryption turns your data into indecipherable text that can be read only using the Secret Key that you control. This keeps everything you do private.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-chain-of-blocks.svg',
|
||||
title: 'Blockchain technology',
|
||||
body:
|
||||
'The Secret Key that unlocks your Data Vault is made using blockchain technology. That ensures there is only ever one, and that no one can take it from you. Your data will be private, out of the hands of companies, and only accessible to you.',
|
||||
},
|
||||
{
|
||||
icon: '/assets/images/icon-shapes.svg',
|
||||
title: 'One Vault works with 100s of apps',
|
||||
body: 'You’ll only ever have to create one Data Vault to use 100s of other apps like Nurx privately.',
|
||||
},
|
||||
];
|
||||
|
||||
export { faqs, howDataVaultWorks };
|
||||
143
packages/app/src/ts/components/sign-up/onboarding/index.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal } from '../modal';
|
||||
import { Intro, HowItWorks, Create, SecretKey, Connect, SaveKey, Final, SignIn } from './screens';
|
||||
import DecryptRecoveryCode from '@components/sign-up/onboarding/screens/decrypt-recovery-code';
|
||||
import { doChangeScreen, doSaveAuthRequest } from '@store/onboarding/actions';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IAppState } from '@store';
|
||||
import { Screen } from '@store/onboarding/types';
|
||||
import {
|
||||
selectCurrentScreen,
|
||||
selectDecodedAuthRequest,
|
||||
selectAuthRequest,
|
||||
selectAppIcon,
|
||||
selectAppName,
|
||||
} from '@store/onboarding/selectors';
|
||||
import { selectCurrentWallet } from '@store/wallet/selectors';
|
||||
import { authenticationInit, finalizeAuthResponse } from '@common/utils';
|
||||
|
||||
const RenderScreen = ({ ...rest }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { screen, wallet, decodedAuthRequest, authRequest } = useSelector((state: IAppState) => ({
|
||||
screen: selectCurrentScreen(state),
|
||||
wallet: selectCurrentWallet(state),
|
||||
decodedAuthRequest: selectDecodedAuthRequest(state),
|
||||
authRequest: selectAuthRequest(state),
|
||||
}));
|
||||
|
||||
// TODO
|
||||
const doFinishSignIn = async () => {
|
||||
if (!wallet || !decodedAuthRequest || !authRequest) {
|
||||
console.log('Uh oh! Finished onboarding without auth info.');
|
||||
return;
|
||||
}
|
||||
const gaiaUrl = 'https://hub.blockstack.org';
|
||||
const appURL = new URL(decodedAuthRequest.redirect_uri);
|
||||
await wallet.identities[0].refresh();
|
||||
const authResponse = await wallet.identities[0].makeAuthResponse({
|
||||
gaiaUrl,
|
||||
appDomain: appURL.origin,
|
||||
transitPublicKey: decodedAuthRequest.public_keys[0],
|
||||
});
|
||||
finalizeAuthResponse({ decodedAuthRequest, authRequest, authResponse });
|
||||
};
|
||||
const doFinishOnboarding = doFinishSignIn;
|
||||
|
||||
const [hasSaved, setHasSaved] = useState(false);
|
||||
switch (screen) {
|
||||
// intro / about
|
||||
case Screen.INTRO:
|
||||
return <Intro next={() => dispatch(doChangeScreen(Screen.CREATE))} {...rest} />;
|
||||
|
||||
case Screen.HOW_IT_WORKS:
|
||||
return <HowItWorks back={() => dispatch(doChangeScreen(Screen.INTRO))} {...rest} />;
|
||||
|
||||
// create
|
||||
case Screen.CREATE:
|
||||
return <Create next={() => dispatch(doChangeScreen(Screen.SECRET_KEY))} {...rest} />;
|
||||
|
||||
// Key screens
|
||||
case Screen.SECRET_KEY:
|
||||
return (
|
||||
<SecretKey next={() => dispatch(doChangeScreen(hasSaved ? Screen.CONNECT_APP : Screen.SAVE_KEY))} {...rest} />
|
||||
);
|
||||
|
||||
case Screen.SAVE_KEY:
|
||||
return (
|
||||
<SaveKey
|
||||
next={() => {
|
||||
setHasSaved(true);
|
||||
dispatch(doChangeScreen(Screen.CONNECT_APP));
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
// Connect
|
||||
case Screen.CONNECT_APP:
|
||||
return (
|
||||
<Connect
|
||||
next={() => dispatch(doChangeScreen(Screen.CONNECTED))}
|
||||
back={() => dispatch(doChangeScreen(Screen.SECRET_KEY))}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
case Screen.CONNECTED:
|
||||
return (
|
||||
<Final
|
||||
next={async () => {
|
||||
await doFinishOnboarding();
|
||||
}}
|
||||
back={() => dispatch(doChangeScreen(Screen.SECRET_KEY))}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
// Sign In
|
||||
|
||||
case Screen.SIGN_IN:
|
||||
return (
|
||||
<SignIn
|
||||
next={async () => await doFinishSignIn()}
|
||||
back={() => {
|
||||
dispatch(doChangeScreen(Screen.SECRET_KEY));
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
case Screen.RECOVERY_CODE:
|
||||
return <DecryptRecoveryCode next={async () => await doFinishSignIn()} />;
|
||||
|
||||
default:
|
||||
return <Intro {...rest} />;
|
||||
}
|
||||
};
|
||||
|
||||
const Onboarding: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const appIcon = useSelector((state: IAppState) => selectAppIcon(state));
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
|
||||
useEffect(() => {
|
||||
const authRequest = authenticationInit();
|
||||
if (authRequest) {
|
||||
dispatch(doSaveAuthRequest(authRequest));
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<Modal
|
||||
appIcon={appIcon}
|
||||
appName={appName}
|
||||
close={() => {
|
||||
console.log('Close Modal');
|
||||
}}
|
||||
title="Data Vault"
|
||||
>
|
||||
<RenderScreen />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export { Onboarding };
|
||||
@@ -0,0 +1,69 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Input, Text } from '@blockstack/ui';
|
||||
import { decrypt } from '@blockstack/keychain';
|
||||
import { ScreenTemplate } from '../../screen';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IAppState } from '@store';
|
||||
import { selectMagicRecoveryCode } from '@store/onboarding/selectors';
|
||||
import { doTrack, SIGN_IN_CORRECT } from '@common/track';
|
||||
import { doStoreSeed } from '@store/wallet/actions';
|
||||
import { DEFAULT_PASSWORD } from '@store/onboarding/types';
|
||||
|
||||
interface RecoveryProps {
|
||||
next: () => void;
|
||||
}
|
||||
|
||||
const DecryptRecoveryCode: React.FC<RecoveryProps> = ({ next }) => {
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [password, setCode] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const recoveryCode = useSelector((state: IAppState) => selectMagicRecoveryCode(state) as string);
|
||||
return (
|
||||
<ScreenTemplate
|
||||
title="Enter your password"
|
||||
isLoading={loading}
|
||||
body={[
|
||||
'You entered a Magic Recovery Code. Enter the password you set when you first created your Blockstack ID.',
|
||||
<Box textAlign="left">
|
||||
{/*Validate: track SIGN_IN_INCORRECT*/}
|
||||
<Input
|
||||
autoFocus
|
||||
// minHeight="80px"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setPasswordError('');
|
||||
setCode(evt.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
{passwordError && (
|
||||
<Text textAlign="left" textStyle="caption" color="feedback.error">
|
||||
{passwordError}
|
||||
</Text>
|
||||
)}
|
||||
</Box>,
|
||||
]}
|
||||
action={{
|
||||
label: 'Continue',
|
||||
onClick: async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const codeBuffer = Buffer.from(recoveryCode, 'base64');
|
||||
const seed = await decrypt(codeBuffer, password);
|
||||
await doStoreSeed(seed, DEFAULT_PASSWORD)(dispatch, () => ({}), {});
|
||||
doTrack(SIGN_IN_CORRECT);
|
||||
next();
|
||||
} catch (error) {
|
||||
setPasswordError('Invalid password.');
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecryptRecoveryCode;
|
||||
@@ -0,0 +1,450 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Flex, Box, Text, Stack, Spinner, Input } from '@blockstack/ui';
|
||||
import { Toast } from '../../toast';
|
||||
import { ScreenTemplate } from '../../screen';
|
||||
import { CheckList } from '../../checklist';
|
||||
import { Collapse } from '../../collapse';
|
||||
import { Image } from '@components/image';
|
||||
import { Link } from '../../../link';
|
||||
import { Card } from '../../card';
|
||||
import { SeedTextarea } from '../../seed-textarea';
|
||||
|
||||
import { howDataVaultWorks, faqs } from '../data';
|
||||
|
||||
import {
|
||||
doTrack,
|
||||
// INTRO_CLOSED,
|
||||
INTRO_CREATE,
|
||||
INTRO_SIGN_IN,
|
||||
INTRO_HOW_WORKS,
|
||||
// SECRET_KEY_INTRO_CLOSED,
|
||||
SECRET_KEY_INTRO_COPIED,
|
||||
// SECRET_KEY_INSTR_CLOSE,
|
||||
SECRET_KEY_INSTR_CONFIRMED,
|
||||
// CONNECT_CLOSED,
|
||||
CONNECT_SAVED,
|
||||
// CONNECT_INCORRECT,
|
||||
CONNECT_BACK,
|
||||
// SIGN_IN_CLOSED,
|
||||
SIGN_IN_CORRECT,
|
||||
// SIGN_IN_INCORRECT,
|
||||
SIGN_IN_CREATE,
|
||||
SIGN_IN_FORGOT,
|
||||
SIGN_IN_INCORRECT,
|
||||
} from '@common/track';
|
||||
import { doChangeScreen, doCreateSecretKey, doSetMagicRecoveryCode } from '@store/onboarding/actions';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Screen, DEFAULT_PASSWORD } from '@store/onboarding/types';
|
||||
import { IAppState } from '@store';
|
||||
import { selectSecretKey, selectDecodedAuthRequest, selectAppName, selectAppIcon } from '@store/onboarding/selectors';
|
||||
import { doStoreSeed } from '@store/wallet';
|
||||
|
||||
const AppIcon: React.FC = ({ ...rest }) => {
|
||||
const appIcon = useSelector((state: IAppState) => selectAppIcon(state));
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
return (
|
||||
<Box size={['48px', '78px']} mx="auto" {...rest}>
|
||||
<Image src={appIcon} alt={appName} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Intro = ({ next }: { next?: () => void }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { decodedAuthRequest, appName } = useSelector((state: IAppState) => ({
|
||||
decodedAuthRequest: selectDecodedAuthRequest(state),
|
||||
appName: selectAppName(state),
|
||||
appIcon: selectAppIcon(state),
|
||||
}));
|
||||
|
||||
if (!decodedAuthRequest || !appName) {
|
||||
return (
|
||||
<ScreenTemplate
|
||||
title="Fetching Authentication Request"
|
||||
body={['Data Vault is securely fetching information to authenticate you']}
|
||||
isLoading
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScreenTemplate
|
||||
before={<AppIcon />}
|
||||
textAlign="center"
|
||||
noMinHeight
|
||||
title={`Use ${appName} privately and securely with Data Vault`}
|
||||
body={[
|
||||
`${appName} will use your Data Vault to store your data privately, where no one but you can see it.`,
|
||||
<Box mx="auto" width="128px" height="1px" bg="#E5E5EC" />,
|
||||
<CheckList
|
||||
items={[
|
||||
`Keep everything you do in ${appName} private with encryption and blockchain`,
|
||||
'It’s free and takes just 2 minutes to create',
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
action={{
|
||||
label: 'Create Data Vault',
|
||||
onClick: () => {
|
||||
doTrack(INTRO_CREATE);
|
||||
next && next();
|
||||
},
|
||||
}}
|
||||
footer={
|
||||
<>
|
||||
<Stack spacing={4} isInline>
|
||||
<Link
|
||||
onClick={() => {
|
||||
doTrack(INTRO_SIGN_IN);
|
||||
dispatch(doChangeScreen(Screen.SIGN_IN));
|
||||
}}
|
||||
>
|
||||
Sign in instead
|
||||
</Link>
|
||||
<Link
|
||||
onClick={() => {
|
||||
doTrack(INTRO_HOW_WORKS);
|
||||
dispatch(doChangeScreen(Screen.HOW_IT_WORKS));
|
||||
}}
|
||||
>
|
||||
How Data Vault works
|
||||
</Link>
|
||||
</Stack>
|
||||
<Link>Help</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface HowItWorksProps {
|
||||
back: () => void;
|
||||
}
|
||||
|
||||
const HowItWorks: React.FC<HowItWorksProps> = props => (
|
||||
<>
|
||||
<ScreenTemplate
|
||||
title="How Data Vault works"
|
||||
back={props.back}
|
||||
noMinHeight
|
||||
body={howDataVaultWorks.map(({ title, body, icon }, key) => (
|
||||
<Box key={key}>
|
||||
<Stack spacing={3}>
|
||||
<Box size="32px" borderRadius="8px">
|
||||
<img src={icon} alt={title} />
|
||||
</Box>
|
||||
<Text fontWeight="semibold">{title}</Text>
|
||||
<Text>{body}</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
interface MockData {
|
||||
title: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
const createTimeoutLoop = (setState: (item: MockData) => void, arr: MockData[], onEnd: () => void) =>
|
||||
arr.forEach((item, index) =>
|
||||
setTimeout(() => {
|
||||
setState(item);
|
||||
if (index === arr.length - 1) {
|
||||
onEnd();
|
||||
}
|
||||
}, (index + 1) * 2400)
|
||||
);
|
||||
|
||||
interface CreateProps {
|
||||
next: () => void;
|
||||
}
|
||||
|
||||
const Create: React.FC<CreateProps> = props => {
|
||||
const [state, setState] = React.useState({
|
||||
title: 'Creating your Data Vault',
|
||||
imageUrl: '',
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const mockData: MockData[] = [
|
||||
{
|
||||
title: 'Private data storage',
|
||||
imageUrl: '/assets/images/icon-delay-private.svg',
|
||||
},
|
||||
{
|
||||
title: 'Always-on encryption',
|
||||
imageUrl: '/assets/images/icon-delay-padlock.svg',
|
||||
},
|
||||
{
|
||||
title: 'Access to 100s of apps',
|
||||
imageUrl: '/assets/images/icon-delay-apps.svg',
|
||||
},
|
||||
{
|
||||
title: 'This will not display',
|
||||
imageUrl: '',
|
||||
},
|
||||
];
|
||||
|
||||
React.useEffect(() => {
|
||||
createTimeoutLoop(setState, mockData, () => props.next());
|
||||
dispatch(doCreateSecretKey());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ScreenTemplate
|
||||
textAlign="center"
|
||||
before={
|
||||
state.imageUrl === '' ? (
|
||||
undefined
|
||||
) : (
|
||||
<Box>
|
||||
<Text>Your Data Vault includes:</Text>
|
||||
<Flex mt={6} mx="auto" width="240px" height="152px" justifyContent="center">
|
||||
<img src={state.imageUrl} />
|
||||
</Flex>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
body={[
|
||||
<Box pt={10} width="100%">
|
||||
<Spinner thickness="3px" size="lg" color="blue" />
|
||||
</Box>,
|
||||
]}
|
||||
title={state.title}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface SecretKeyProps {
|
||||
next: () => void;
|
||||
}
|
||||
|
||||
const SecretKey: React.FC<SecretKeyProps> = props => {
|
||||
const { secretKey } = useSelector((state: IAppState) => ({
|
||||
secretKey: selectSecretKey(state),
|
||||
}));
|
||||
const [copied, setCopiedState] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (copied) {
|
||||
setTimeout(() => {
|
||||
props.next();
|
||||
}, 2500);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScreenTemplate
|
||||
title="Your Secret Key"
|
||||
body={[
|
||||
'Your Data Vault has a Secret Key: 12 words that unlock it, like the key to your home. Once lost, it’s lost forever. So save it somewhere you won’t forget.',
|
||||
<Card title="Your Secret Key">
|
||||
<SeedTextarea readOnly value={secretKey} className="hidden-secret-key" />
|
||||
</Card>,
|
||||
]}
|
||||
action={{
|
||||
label: 'Copy Secret Key',
|
||||
onClick: () => {
|
||||
doTrack(SECRET_KEY_INTRO_COPIED);
|
||||
const input: HTMLInputElement = document.querySelector('.hidden-secret-key') as HTMLInputElement;
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
document.execCommand('copy');
|
||||
setCopiedState(true);
|
||||
},
|
||||
disabled: copied,
|
||||
}}
|
||||
/>
|
||||
<Toast show={copied} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface SaveKeyProps {
|
||||
next: () => void;
|
||||
}
|
||||
|
||||
const SaveKey: React.FC<SaveKeyProps> = ({ next }) => {
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
return (
|
||||
<ScreenTemplate
|
||||
title="Save your Secret Key"
|
||||
body={[
|
||||
'Paste your Secret Key wherever you keep critical, private, information such as passwords.',
|
||||
'Once lost, it’s lost forever. So save it somewhere you won’t forget.',
|
||||
]}
|
||||
action={{
|
||||
label: "I've saved it",
|
||||
onClick: () => {
|
||||
doTrack(SECRET_KEY_INSTR_CONFIRMED);
|
||||
next();
|
||||
},
|
||||
}}
|
||||
after={<Collapse data={faqs(appName as string)} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface ConnectProps {
|
||||
next: () => void;
|
||||
back: () => void;
|
||||
}
|
||||
|
||||
const Connect: React.FC<ConnectProps> = props => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
return (
|
||||
<ScreenTemplate
|
||||
textAlign="center"
|
||||
before={<AppIcon />}
|
||||
title={`Connect ${appName} to your Data Vault`}
|
||||
body={[
|
||||
'Enter your Secret Key to continue.',
|
||||
<Box>
|
||||
{/*Validate, track: CONNECT_INCORRECT */}
|
||||
<Input autoFocus minHeight="80px" placeholder="12-word Secret Key" as="textarea" />
|
||||
</Box>,
|
||||
]}
|
||||
action={{
|
||||
label: 'Continue',
|
||||
onClick: () => {
|
||||
setLoading(true);
|
||||
doTrack(CONNECT_SAVED);
|
||||
setTimeout(() => {
|
||||
props.next();
|
||||
setLoading(false);
|
||||
}, 1500);
|
||||
},
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
footer={
|
||||
<>
|
||||
<Flex>
|
||||
<Text>Didn’t save your Secret Key?</Text>{' '}
|
||||
<Link
|
||||
onClick={() => {
|
||||
doTrack(CONNECT_BACK);
|
||||
props.back();
|
||||
}}
|
||||
pl={1}
|
||||
color="blue"
|
||||
>
|
||||
Go Back
|
||||
</Link>
|
||||
</Flex>
|
||||
<Link>Help</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface FinalProps {
|
||||
next: () => void;
|
||||
back: () => void;
|
||||
}
|
||||
|
||||
const Final: React.FC<FinalProps> = props => {
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
return (
|
||||
<ScreenTemplate
|
||||
textAlign="center"
|
||||
before={<AppIcon />}
|
||||
title={`You’re all set! ${appName} has been connected to your Data Vault`}
|
||||
body={[`Everything you do in ${appName} will be private, secure, and only accessible with your Secret Key.`]}
|
||||
action={{
|
||||
label: 'Done',
|
||||
onClick: props.next,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface SignInProps {
|
||||
next: () => void;
|
||||
back: () => void;
|
||||
}
|
||||
|
||||
const SignIn: React.FC<SignInProps> = props => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [seed, setSeed] = useState('');
|
||||
const [seedError, setSeedError] = useState<null | string>(null);
|
||||
const dispatch = useDispatch();
|
||||
const appName = useSelector((state: IAppState) => selectAppName(state));
|
||||
|
||||
return (
|
||||
<ScreenTemplate
|
||||
textAlign="center"
|
||||
before={<AppIcon />}
|
||||
title={`Sign into ${appName}`}
|
||||
body={[
|
||||
'Enter your Data Vault’s Secret Key to continue',
|
||||
<Box textAlign="left">
|
||||
{/*Validate: track SIGN_IN_INCORRECT*/}
|
||||
<Input
|
||||
autoFocus
|
||||
minHeight="80px"
|
||||
placeholder="12-word Secret Key"
|
||||
as="textarea"
|
||||
value={seed}
|
||||
onChange={(evt: React.FormEvent<HTMLInputElement>) => {
|
||||
setSeedError(null);
|
||||
setSeed(evt.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
{seedError && (
|
||||
<Text textAlign="left" textStyle="caption" color="feedback.error">
|
||||
{seedError}
|
||||
</Text>
|
||||
)}
|
||||
</Box>,
|
||||
]}
|
||||
action={[
|
||||
{
|
||||
label: 'Create a Data Vault',
|
||||
variant: 'text',
|
||||
onClick: () => {
|
||||
doTrack(SIGN_IN_CREATE);
|
||||
dispatch(doChangeScreen(Screen.INTRO));
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Continue',
|
||||
onClick: async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (seed.trim().split(' ').length <= 1) {
|
||||
dispatch(doSetMagicRecoveryCode(seed.trim()));
|
||||
dispatch(doChangeScreen(Screen.RECOVERY_CODE));
|
||||
return;
|
||||
}
|
||||
await doStoreSeed(seed, DEFAULT_PASSWORD)(dispatch, () => ({}), {});
|
||||
doTrack(SIGN_IN_CORRECT);
|
||||
props.next();
|
||||
} catch (error) {
|
||||
setSeedError("The seed phrase you've entered is invalid.");
|
||||
doTrack(SIGN_IN_INCORRECT);
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
},
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
footer={
|
||||
<>
|
||||
<Flex>
|
||||
<Link onClick={() => doTrack(SIGN_IN_FORGOT)}>Forgot Secret Key?</Link>
|
||||
</Flex>
|
||||
<Link>Help</Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Intro, HowItWorks, Create, SecretKey, Connect, SaveKey, Final, SignIn };
|
||||
112
packages/app/src/ts/components/sign-up/screen/index.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { Button, Flex, Box, Spinner, Stack } from '@blockstack/ui';
|
||||
import { Title, Body, BackLink } from '../typography';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box';
|
||||
import { Link } from '../../link';
|
||||
|
||||
const Footer: React.FC = props => (
|
||||
<Flex fontSize={['12px', '14px']} color="ink.600" fontWeight="medium" justify="space-between" {...props} />
|
||||
);
|
||||
|
||||
interface ScreenAction {
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
href?: string;
|
||||
disabled?: boolean;
|
||||
variant?: string;
|
||||
}
|
||||
|
||||
interface IScreenTemplate {
|
||||
title: string | React.ElementType;
|
||||
body?: (string | JSX.Element)[];
|
||||
back?: () => void;
|
||||
action?: ScreenAction | ScreenAction[];
|
||||
after?: string | JSX.Element;
|
||||
before?: string | JSX.Element;
|
||||
footer?: string | JSX.Element;
|
||||
isLoading?: boolean;
|
||||
noMinHeight?: boolean;
|
||||
}
|
||||
const ScreenTemplate = ({
|
||||
before,
|
||||
title,
|
||||
body,
|
||||
action,
|
||||
after,
|
||||
back,
|
||||
footer,
|
||||
isLoading,
|
||||
noMinHeight = false,
|
||||
...rest
|
||||
}: IScreenTemplate & BoxProps) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
width="100%"
|
||||
height="100%"
|
||||
bg={`rgba(255,255,255,${isLoading ? 0.6 : 0})`}
|
||||
borderBottomLeftRadius="6px"
|
||||
borderBottomRightRadius="6px"
|
||||
zIndex={99}
|
||||
transition="250ms all"
|
||||
style={{ pointerEvents: isLoading ? 'unset' : 'none' }}
|
||||
opacity={isLoading ? 1 : 0}
|
||||
>
|
||||
<Box transition="500ms all" transform={isLoading ? 'none' : 'translateY(10px)'}>
|
||||
<Spinner size="xl" thickness="3px" color="blue" />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Stack
|
||||
width="100%"
|
||||
letterSpacing="tighter"
|
||||
minHeight={noMinHeight ? undefined : ['calc(100vh - 57px)', 'unset']}
|
||||
spacing={[4, 6]}
|
||||
style={{ pointerEvents: isLoading ? 'none' : 'unset' }}
|
||||
{...rest}
|
||||
>
|
||||
{back ? <BackLink onClick={back} /> : null}
|
||||
{before && before}
|
||||
<Stack spacing={2}>
|
||||
<Title>{title}</Title>
|
||||
<Stack spacing={[3, 4]}>
|
||||
{body && body.length ? body.map((text, key) => <Body key={key}>{text}</Body>) : body}
|
||||
</Stack>
|
||||
</Stack>
|
||||
{action ? (
|
||||
Array.isArray(action) ? (
|
||||
<Flex justify="space-between" align="center">
|
||||
{action.map((a, key) => (
|
||||
<Box key={key}>
|
||||
{a.variant && a.variant === 'text' ? (
|
||||
<Link color="blue" onClick={a.onClick}>
|
||||
{a.label}
|
||||
</Link>
|
||||
) : (
|
||||
<Button onClick={a.onClick} isDisabled={a.disabled}>
|
||||
{a.label}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
<Box>
|
||||
<Button width="100%" onClick={action.onClick} isDisabled={action.disabled}>
|
||||
{action.label}
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
) : null}
|
||||
{after ? after : null}
|
||||
{footer ? <Footer>{footer}</Footer> : null}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { ScreenTemplate };
|
||||
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SeedTextarea = styled.textarea`
|
||||
width: 100%;
|
||||
resize: none;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
`;
|
||||
38
packages/app/src/ts/components/sign-up/toast/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, Text } from '@blockstack/ui';
|
||||
import CheckCircleIcon from 'mdi-react/CheckCircleIcon';
|
||||
|
||||
const Toast = ({ show = false, icon: Icon = CheckCircleIcon, text = 'Copied to clipboard' }) => (
|
||||
<Flex
|
||||
p={6}
|
||||
width="100%"
|
||||
position="fixed"
|
||||
justify="center"
|
||||
align="center"
|
||||
bottom={0}
|
||||
left={0}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>
|
||||
<Flex
|
||||
width={['100%', 'unset']}
|
||||
bg="white"
|
||||
boxShadow="high"
|
||||
border="1px solid"
|
||||
borderColor="inherit"
|
||||
p={4}
|
||||
borderRadius="6px"
|
||||
opacity={show ? 1 : 0}
|
||||
transform={show ? 'none' : 'translateY(20px)'}
|
||||
transition="150ms all"
|
||||
>
|
||||
<Box mr={2} color="green">
|
||||
<Icon />
|
||||
</Box>
|
||||
<Text fontSize="14px" fontWeight="medium">
|
||||
{text}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export { Toast };
|
||||
27
packages/app/src/ts/components/sign-up/typography/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Text, Box } from '@blockstack/ui';
|
||||
import ChevronLeftIcon from 'mdi-react/ChevronLeftIcon';
|
||||
import { BoxProps } from '@blockstack/ui/dist/box';
|
||||
|
||||
const Title: React.FC = props => (
|
||||
<Text width="100%" fontWeight="medium" fontSize={['20px', '24px']} lineHeight={['28px', '32px']} {...props} />
|
||||
);
|
||||
const Body: React.FC = props => <Text fontSize="14px" lineHeight="20px" {...props} />;
|
||||
|
||||
const BackLink: React.FC<BoxProps> = props => (
|
||||
<Text
|
||||
display="flex"
|
||||
_hover={{ color: 'ink', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
color="blue"
|
||||
fontWeight="medium"
|
||||
alignItems="center"
|
||||
{...props}
|
||||
>
|
||||
<Box>
|
||||
<ChevronLeftIcon size="1rem" />
|
||||
</Box>
|
||||
Back
|
||||
</Text>
|
||||
);
|
||||
|
||||
export { Title, Body, BackLink };
|
||||
51
packages/app/src/ts/components/sign-up/window/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* This doesnt work quite right yet. It opens a new window, but has nothing from next.js. It's all by itself, with no next.js code.
|
||||
*/
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ThemeProvider, CSSReset, theme } from '@blockstack/ui';
|
||||
|
||||
const PortalContainer: React.FC = props => {
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CSSReset />
|
||||
{props.children}
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface WindowState {
|
||||
win: Window | null;
|
||||
el: HTMLDivElement | null;
|
||||
}
|
||||
|
||||
interface WindowProps {
|
||||
children: React.ReactChildren;
|
||||
}
|
||||
|
||||
const Window = ({ children }: WindowProps) => {
|
||||
const [state, setState] = React.useState<WindowState>({
|
||||
win: null,
|
||||
el: null,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const win = window.open('', '', 'width=440,height=584,left=200,top=200') as Window;
|
||||
win.document.title = 'Continue with Data Vault';
|
||||
const el = document.createElement('div');
|
||||
win.document.body.appendChild(el);
|
||||
setState({ win, el });
|
||||
|
||||
return () => state.win?.close();
|
||||
}, []);
|
||||
|
||||
if (!state.el) {
|
||||
return null;
|
||||
} else {
|
||||
return ReactDOM.createPortal(<PortalContainer>{children}</PortalContainer>, state.el);
|
||||
}
|
||||
};
|
||||
|
||||
export { Window };
|
||||
@@ -4,6 +4,11 @@ export interface DecodedAuthRequest {
|
||||
manifest_uri: string;
|
||||
redirect_uri: string;
|
||||
scopes: string[];
|
||||
sendToSignIn: boolean;
|
||||
appDetails?: {
|
||||
name: string;
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AppManifestIcon {
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
// import { useDispatch } from 'react-redux';
|
||||
import { Box, Text, Input, Button, ButtonGroup } from '@blockstack/ui';
|
||||
import { doAuthRequest } from '@store/permissions/actions';
|
||||
// import { doAuthRequest } from '@store/permissions/actions';
|
||||
import { openPopup } from '../../actions/utils';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
const DevActions = () => {
|
||||
const dispatch = useDispatch();
|
||||
// const dispatch = useDispatch();
|
||||
|
||||
const saveAuthRequest = (authRequest: string) => {
|
||||
dispatch(doAuthRequest(authRequest));
|
||||
openPopup('http://localhost:8080/actions.html');
|
||||
// dispatch(doAuthRequest(authRequest));
|
||||
openPopup(`/actions.html?authRequest=${encodeURIComponent(authRequest)}`);
|
||||
};
|
||||
|
||||
const openBrowserActions = () => {
|
||||
openPopup('http://localhost:8080/popup.html');
|
||||
openPopup('/popup.html');
|
||||
};
|
||||
|
||||
// const openOnboarding = (authRequest: string) => {
|
||||
// // console.log('Open onboarding');
|
||||
// openPopup(`/actions.html?authRequest=${encodeURIComponent(authRequest)}`);
|
||||
// };
|
||||
|
||||
return (
|
||||
<Box width="100%">
|
||||
<Text textStyle="display.small" display="block">
|
||||
Dev Console
|
||||
</Text>
|
||||
<Text my={4} display="block">
|
||||
Mimic an authentication request by entering an{' '}
|
||||
<code>`authRequest`</code> below.
|
||||
Mimic an authentication request by entering an <code>`authRequest`</code> below.
|
||||
</Text>
|
||||
<Formik
|
||||
initialValues={{ authRequest: '' }}
|
||||
@@ -40,6 +44,7 @@ const DevActions = () => {
|
||||
value={values.authRequest}
|
||||
onChange={handleChange}
|
||||
textStyle="body.small"
|
||||
name="authRequest"
|
||||
/>
|
||||
<Button mt={4}>Submit</Button>
|
||||
</form>
|
||||
@@ -47,6 +52,7 @@ const DevActions = () => {
|
||||
</Formik>
|
||||
<ButtonGroup variantColor="purple" my={4}>
|
||||
<Button onClick={openBrowserActions}>Debug Browser Action</Button>
|
||||
{/* <Button onClick={openOnboarding}>Debug Onboarding UI</Button> */}
|
||||
</ButtonGroup>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Flex,
|
||||
ThemeProvider,
|
||||
theme,
|
||||
CSSReset,
|
||||
Stack,
|
||||
Box,
|
||||
} from '@blockstack/ui';
|
||||
import { Flex, ThemeProvider, theme, CSSReset, Stack, Box } from '@blockstack/ui';
|
||||
import Seed from './Seed';
|
||||
import DevActions from './DevActions';
|
||||
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import React from 'react';
|
||||
import { validateMnemonic } from 'bip39';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
Box,
|
||||
Input,
|
||||
Text,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
} from '@blockstack/ui';
|
||||
import { Box, Input, Text, Button, FormControl, FormLabel } from '@blockstack/ui';
|
||||
import { IAppState } from '@store';
|
||||
import { doStoreSeed, doGenerateWallet } from '@store/wallet';
|
||||
import { Formik, FormikErrors } from 'formik';
|
||||
import {
|
||||
selectCurrentWallet,
|
||||
selectIsRestoringWallet,
|
||||
} from '@store/wallet/selectors';
|
||||
import { selectCurrentWallet, selectIsRestoringWallet } from '@store/wallet/selectors';
|
||||
|
||||
interface FormValues {
|
||||
seed: string;
|
||||
@@ -39,10 +29,7 @@ const Seed = () => {
|
||||
) : (
|
||||
<>
|
||||
<Text display="block">Enter your 12-word seed to log in.</Text>
|
||||
<Text>
|
||||
To generate a new wallet, enter a password and leave your seed
|
||||
blank.
|
||||
</Text>
|
||||
<Text>To generate a new wallet, enter a password and leave your seed blank.</Text>
|
||||
</>
|
||||
)}
|
||||
<Formik
|
||||
@@ -53,7 +40,7 @@ const Seed = () => {
|
||||
onSubmit={values => {
|
||||
console.log(values);
|
||||
if (values.seed) {
|
||||
dispatch(doStoreSeed(values.seed));
|
||||
dispatch(doStoreSeed(values.seed, values.password));
|
||||
} else {
|
||||
dispatch(doGenerateWallet(values.password));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ThemeProvider,
|
||||
theme,
|
||||
CSSReset,
|
||||
Flex,
|
||||
Box,
|
||||
Text,
|
||||
} from '@blockstack/ui';
|
||||
import { ThemeProvider, theme, CSSReset, Flex, Box, Text } from '@blockstack/ui';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import Gutter from '@components/gutter';
|
||||
|
||||
@@ -18,9 +11,7 @@ const PopupApp: React.FC = () => {
|
||||
<Flex>
|
||||
<Box width="100%" textAlign="center" px={5}>
|
||||
<Gutter multiplier={3} />
|
||||
<Text textStyle="display.large">
|
||||
Welcome to the Blockstack App!
|
||||
</Text>
|
||||
<Text textStyle="display.large">Welcome to the Blockstack App!</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "noImplicitAny": false, // TODO: only for migrating onboarding work
|
||||
"strictNullChecks": true, /* Enable strict null checks. */
|
||||
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
@@ -46,7 +47,8 @@
|
||||
"@store": ["background/store/index"],
|
||||
"@dev/*": ["dev/*"],
|
||||
"@components/*": ["components/*"],
|
||||
"@containers/*": ["containers/*"]
|
||||
"@containers/*": ["containers/*"],
|
||||
"@common/*": ["common/*"],
|
||||
},
|
||||
"baseUrl": "src/ts",
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
|
||||
@@ -59,7 +59,9 @@ module.exports = {
|
||||
// ["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
"react-hot-loader/babel",
|
||||
"@babel/plugin-transform-runtime"
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,14 @@
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-json-strings" "^7.2.0"
|
||||
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator@^7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.7.4.tgz#7db302c83bc30caa89e38fee935635ef6bd11c28"
|
||||
integrity sha512-TbYHmr1Gl1UC7Vo2HVuj/Naci5BEGNZ0AJhzqD2Vpr6QPFWpUmBRLrIDjedzx7/CShq0bRDS2gI4FIs77VHLVQ==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.7.4"
|
||||
|
||||
"@babel/plugin-proposal-object-rest-spread@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096"
|
||||
@@ -331,6 +339,14 @@
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
|
||||
|
||||
"@babel/plugin-proposal-optional-chaining@^7.7.5":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.7.5.tgz#f0835f044cef85b31071a924010a2a390add11d4"
|
||||
integrity sha512-sOwFqT8JSchtJeDD+CjmWCaiFoLxY4Ps7NjvwHC/U7l4e9i5pTRNt8nDMIFSOUL+ncFbYSwruHM8WknYItWdXw==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-optional-chaining" "^7.7.4"
|
||||
|
||||
"@babel/plugin-proposal-unicode-property-regex@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz#05413762894f41bfe42b9a5e80919bd575dcc802"
|
||||
@@ -368,6 +384,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator@^7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.7.4.tgz#e53b751d0c3061b1ba3089242524b65a7a9da12b"
|
||||
integrity sha512-XKh/yIRPiQTOeBg0QJjEus5qiSKucKAiApNtO1psqG7D17xmE+X2i5ZqBEuSvo0HRuyPaKaSN/Gy+Ha9KFQolw==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
|
||||
@@ -382,6 +405,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-optional-chaining@^7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.7.4.tgz#c91fdde6de85d2eb8906daea7b21944c3610c901"
|
||||
integrity sha512-2MqYD5WjZSbJdUagnJvIdSfkb/ucOC9/1fRJxm7GAxY6YQLWlUvkfxoNbUPcPLHJyetKUDQ4+yyuUyAoc0HriA==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.2.0":
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz#a7cc3f66119a9f7ebe2de5383cce193473d65991"
|
||||
@@ -841,10 +871,10 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@blockstack/keychain@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/keychain/-/keychain-0.1.0.tgz#6a03a03ca8c3026301639f16a69e9745668c995b"
|
||||
integrity sha512-4OMUn0mQHt/DTUNqL4wEcz3FGNQKSTcQD+xs7x7UsC+V5FuJO900nvwQlXQXQm5/a0j5HlBZlBh5ksuotbL+Ew==
|
||||
"@blockstack/keychain@^0.1.2":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/keychain/-/keychain-0.1.3.tgz#e94cef7d0b4a2b01ae0d23c6db93f2b9df170593"
|
||||
integrity sha512-9CQRRYD5Rn86hAZGg+vFi4SdUTA41CcSiJ7WZjpmaMAJev3wPNS3wV95PAu49oaszsLxwcJHrs8T8jkIj26RtQ==
|
||||
dependencies:
|
||||
bip39 "^3.0.2"
|
||||
bitcoinjs-lib "^5.1.6"
|
||||
@@ -857,23 +887,22 @@
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/prettier-config/-/prettier-config-0.0.4.tgz#b051cd5eb66ff9ec61b0776f0eecc5a8437196d7"
|
||||
integrity sha512-fiDOBRLpm/Sv34qx9GsNer38qyTkp+rDRs74rrVd9Nn0JZVHoV97W98o81+6E9Bd+xvnY6JV+g1gA1P60RBCYA==
|
||||
|
||||
"@blockstack/ui@^1.0.0-alpha.6":
|
||||
version "1.0.0-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/ui/-/ui-1.0.0-alpha.6.tgz#c04711aacde22871368538fbb997eab6086bb393"
|
||||
integrity sha512-uFbZ8m8Rp7rT/qmNIbK20ODTLcyKfaC8XwQqu6Yb2Kn2uSQS/5XZQ/27msF+hjLd57DBFYvTP3abdEZBm3qLjQ==
|
||||
"@blockstack/ui@^1.0.0-alpha.19":
|
||||
version "1.0.0-alpha.19"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/ui/-/ui-1.0.0-alpha.19.tgz#d464973eca7abb34b1dc49239c7103e4a27bed0b"
|
||||
integrity sha512-815YkVDg1PMUxajLnykRhybGbQeyRd7Q2T503CJow7W9sKWSaPfhi6emhJh04jQfzSIiOhLhXXX4CbVEUrfNFw==
|
||||
dependencies:
|
||||
"@styled-system/css" "5.0.23"
|
||||
"@types/color" "^3.0.0"
|
||||
"@types/styled-components" "^4.1.19"
|
||||
"@types/styled-system" "^5.1.2"
|
||||
"@types/styled-components" "^4.1.20"
|
||||
"@types/styled-system" "^5.1.3"
|
||||
"@types/styled-system__css" "^5.0.4"
|
||||
color "3.1.2"
|
||||
csstype "^2.6.7"
|
||||
prop-types "^15.7.2"
|
||||
react-spring "8.0.27"
|
||||
styled-system "5.1.2"
|
||||
use-dark-mode "2.3.1"
|
||||
use-events "^1.3.0"
|
||||
use-events "^1.4.1"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
@@ -1417,19 +1446,19 @@
|
||||
"@types/react" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/styled-components@^4.1.19":
|
||||
version "4.1.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-4.1.20.tgz#8afd41039c0fd582152e57ff75c58a5353870de6"
|
||||
integrity sha512-WztLENdKY+H9udccx5ZhAblgTp08NSfOFYdGaWM0MMm+1tEOioYVFwIIQ7Hx+9wWXiWKDXeoX6R3D/i1obnG3g==
|
||||
"@types/styled-components@^4.1.20":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-4.4.0.tgz#15a3d59533fd3a5bd013db4a7c4422ec542c59d2"
|
||||
integrity sha512-QFl+w3hQJNHE64Or3PXMFpC3HAQDiuQLi5o9m1XPEwYWfgCZtAribO5ksjxnO8U0LG8Parh0ESCgVxo4VfxlHg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-native" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/styled-system@^5.1.2":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-system/-/styled-system-5.1.2.tgz#d75c40bc4a3bb0d0022eb3dcae58854129e9dd32"
|
||||
integrity sha512-Byh33qthYnI6+qS0TRr4vqd+N/ax6ic1NFE6ZA16xuVr/EvYvSB8+diEP1lTSE7sP/MTdQpl+KaONREnyalDUA==
|
||||
"@types/styled-system@^5.1.3":
|
||||
version "5.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-system/-/styled-system-5.1.4.tgz#c273b1ebb9b26cb1bd9282c994fc56c82d0fbfaf"
|
||||
integrity sha512-iBtFFmlxBbiTMpoKvRupwg75PeVbf7BBsgTlpcVBBbZyUKrhuU/4jtJQUn/wsSndKfxW0U9T17Geq0GGFWkCZw==
|
||||
dependencies:
|
||||
csstype "^2.6.4"
|
||||
|
||||
@@ -3029,7 +3058,7 @@ csstype@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41"
|
||||
integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==
|
||||
|
||||
csstype@^2.6.4, csstype@^2.6.6, csstype@^2.6.7:
|
||||
csstype@^2.6.4, csstype@^2.6.6:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5"
|
||||
integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==
|
||||
@@ -5833,6 +5862,11 @@ md5.js@^1.3.4:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
mdi-react@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mdi-react/-/mdi-react-6.3.0.tgz#09635d90856fe992ad20e06a233a562c24339ff6"
|
||||
integrity sha512-Cs1Q0hwuXjM+scKXnQiw2QH2w2GV3xG/2+dGFidFTjiJeDxkPEBnULkfGZTp1cJMItoBRRKLe+SdPNfW0Xkmmg==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
@@ -6793,10 +6827,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
prettier@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
pretty-error@^2.0.2:
|
||||
version "2.1.1"
|
||||
@@ -8502,10 +8536,10 @@ typeforce@^1.11.3, typeforce@^1.11.5:
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
|
||||
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
|
||||
|
||||
typescript@3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.2.tgz#105b0f1934119dde543ac8eb71af3a91009efe54"
|
||||
integrity sha512-lmQ4L+J6mnu3xweP8+rOrUwzmN+MRAj7TgtJtDaXE5PMyX2kCrklhg3rvOsOIfNeAWMQWO2F1GPc1kMD2vLAfw==
|
||||
typescript@3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
|
||||
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
|
||||
|
||||
uglify-js@3.4.x:
|
||||
version "3.4.10"
|
||||
@@ -8629,7 +8663,7 @@ use-dark-mode@2.3.1:
|
||||
"@use-it/event-listener" "^0.1.2"
|
||||
use-persisted-state "^0.3.0"
|
||||
|
||||
use-events@^1.3.0:
|
||||
use-events@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/use-events/-/use-events-1.4.1.tgz#bf1ef93d502f1cad69afcda5619fda8b5f60b325"
|
||||
integrity sha512-ANiKauqtfTnK5HCQ8mPlOoBXld/ZjFtC5rJSrH6wYZIke8Nz2DW8gYoMDKlmBk+qBPt24mVwQh8ea5sCqdrfnA==
|
||||
|
||||