diff --git a/.circleci/config.yml b/.circleci/config.yml index 17e4dda0758..30c7f38d967 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ jobs: background: true - run: name: test - command: yarn lerna run test --scope @blockstack/app + command: yarn lerna run test --scope @blockstack/app --stream - run: name: Build extension command: yarn lerna run prod:ext && zip -r extension.zip ./packages/app/dist diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000000..993155e31f5 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,5 @@ +[[redirects]] + from = "/actions.html" + to = "/#/sign-up?authRequest=:authRequest" + force = true + query = {authRequest = ":authRequest"} diff --git a/package.json b/package.json index ef18d1c759b..1d70a39fbd3 100644 --- a/package.json +++ b/package.json @@ -46,5 +46,6 @@ }, "workspaces":[ "packages/*" - ] + ], + "dependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 8990535b65d..0a0ec02ba3a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -29,6 +29,7 @@ "@blockstack/prettier-config": "^0.0.5", "@blockstack/ui": "^1.0.1", "@rehooks/document-title": "^1.0.1", + "@types/react-router-dom": "^5.1.3", "blockstack": "^19.3.0", "formik": "^2.1.4", "mdi-react": "^6.7.0", @@ -38,6 +39,8 @@ "react-dom": "^16.12.0", "react-redux": "^7.2.0", "react-refresh": "^0.7.2", + "react-router": "^6.0.0-alpha.2", + "react-router-dom": "^6.0.0-alpha.2", "redux": "4.0.5", "redux-persist": "^6.0.0", "redux-thunk": "^2.3.0", diff --git a/packages/app/public/html/actions.html b/packages/app/public/html/index.html similarity index 100% rename from packages/app/public/html/actions.html rename to packages/app/public/html/index.html diff --git a/packages/app/src/actions.tsx b/packages/app/src/actions.tsx deleted file mode 100644 index 4ea223120f7..00000000000 --- a/packages/app/src/actions.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import ExtStore from './store/ext-store'; -import ReactDOM from 'react-dom'; -import { Store as ReduxStore } from 'redux'; -import { Provider } from 'react-redux'; -import { PersistGate } from 'redux-persist/integration/react'; -import { persistor } from './store'; -import OnboardingApp from './pages/onboarding'; -import DevStore from './common/dev/store'; - -const buildApp = (store: ReduxStore | ReturnType) => { - ReactDOM.render( - - - - - , - document.getElementById('actions-root') - ); -}; - -if (EXT_ENV === 'web') { - const store = DevStore; - buildApp(store); -} else { - const store = ExtStore(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - store.ready().then(() => buildApp(store)); -} diff --git a/packages/app/src/common/dev/types/react-router.d.ts b/packages/app/src/common/dev/types/react-router.d.ts new file mode 100644 index 00000000000..e04b7548924 --- /dev/null +++ b/packages/app/src/common/dev/types/react-router.d.ts @@ -0,0 +1,16 @@ +import * as reactRouterDom from 'react-router-dom'; + +interface ToOptions { + pathname?: string; + hash?: string; + search?: string; +} + +interface NavigateProps { + to: string | ToOptions; +} + +declare module 'react-router-dom' { + export const Navigate: React.FC; + export const Routes: React.FC; +} \ No newline at end of file diff --git a/packages/app/src/common/hooks/use-analytics.ts b/packages/app/src/common/hooks/use-analytics.ts index e4622e45908..b836bee33c6 100644 --- a/packages/app/src/common/hooks/use-analytics.ts +++ b/packages/app/src/common/hooks/use-analytics.ts @@ -1,11 +1,20 @@ -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { selectDecodedAuthRequest } from '@store/onboarding/selectors'; import { doTrack as track, doTrackScreenChange } from '@common/track'; import { doChangeScreen as changeScreen } from '@store/onboarding/actions'; -import { ScreenName } from '@store/onboarding/types'; +import { ScreenPaths } from '@store/onboarding/types'; import { useAppDetails } from './useAppDetails'; +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore +import { useNavigate } from 'react-router-dom'; export const useAnalytics = () => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + const doNavigatePage = (path: ScreenPaths) => { + navigate(path); + dispatch(changeScreen(path)); + }; const authRequest = useSelector(selectDecodedAuthRequest); const { name, url } = useAppDetails(); @@ -23,9 +32,9 @@ export const useAnalytics = () => { }); }; - const doChangeScreen = (screen: ScreenName) => { - doTrackScreenChange(screen, authRequest); - return changeScreen(screen); + const doChangeScreen = (path: ScreenPaths) => { + doTrackScreenChange(path, authRequest); + return doNavigatePage(path); }; return { doTrack, doChangeScreen }; diff --git a/packages/app/src/common/hooks/use-onboarding-state.ts b/packages/app/src/common/hooks/use-onboarding-state.ts new file mode 100644 index 00000000000..c9a6cdf5189 --- /dev/null +++ b/packages/app/src/common/hooks/use-onboarding-state.ts @@ -0,0 +1,22 @@ +import { useSelector } from 'react-redux'; +import { + selectSecretKey, + selectCurrentScreen, + selectAuthRequest, + selectDecodedAuthRequest, + selectMagicRecoveryCode, + selectOnboardingProgress, + selectUsername, +} from '@store/onboarding/selectors'; + +export const useOnboardingState = () => { + const secretKey = useSelector(selectSecretKey); + const screen = useSelector(selectCurrentScreen); + const authRequest = useSelector(selectAuthRequest); + const decodedAuthRequest = useSelector(selectDecodedAuthRequest); + const magicRecoveryCode = useSelector(selectMagicRecoveryCode); + const isOnboardingInProgress = useSelector(selectOnboardingProgress); + const username = useSelector(selectUsername); + + return { secretKey, screen, authRequest, decodedAuthRequest, magicRecoveryCode, isOnboardingInProgress, username }; +}; diff --git a/packages/app/src/common/hooks/use-wallet.ts b/packages/app/src/common/hooks/use-wallet.ts new file mode 100644 index 00000000000..4b9c69b7c70 --- /dev/null +++ b/packages/app/src/common/hooks/use-wallet.ts @@ -0,0 +1,20 @@ +import { useSelector } from 'react-redux'; +import { + selectIdentities, + selectCurrentWallet, + selectFirstIdentity, + selectIsRestoringWallet, + selectIsSignedIn, +} from '@store/wallet/selectors'; +import { selectSecretKey } from '@store/onboarding/selectors'; + +export const useWallet = () => { + const identities = useSelector(selectIdentities); + const firstIdentity = useSelector(selectFirstIdentity); + const wallet = useSelector(selectCurrentWallet); + const secretKey = useSelector(selectSecretKey); + const isRestoringWallet = useSelector(selectIsRestoringWallet); + const isSignedIn = useSelector(selectIsSignedIn); + + return { identities, firstIdentity, wallet, secretKey, isRestoringWallet, isSignedIn }; +}; diff --git a/packages/app/src/components/onboarding/data.ts b/packages/app/src/common/onboarding-data.ts similarity index 100% rename from packages/app/src/components/onboarding/data.ts rename to packages/app/src/common/onboarding-data.ts diff --git a/packages/app/src/common/track.ts b/packages/app/src/common/track.ts index 28d7eaae16d..b907b55951e 100644 --- a/packages/app/src/common/track.ts +++ b/packages/app/src/common/track.ts @@ -1,4 +1,4 @@ -import { ScreenName } from '@store/onboarding/types'; +import { ScreenPaths } from '@store/onboarding/types'; import { DecodedAuthRequest } from './dev/types'; export const SECRET_KEY_FAQ_WHERE = 'View Secret Key FAQ (Where)'; @@ -23,32 +23,30 @@ export const USERNAME_SUBMIT_SUCCESS = 'Submit Username Success'; // Nice page names for Mark to see in Mixpanel export const pageTrackingNameMap = { - [ScreenName.CHOOSE_ACCOUNT]: 'Choose Account', - [ScreenName.USERNAME]: 'Username', - [ScreenName.GENERATION]: 'Generation', - [ScreenName.SECRET_KEY]: 'Copy Secret Key', - [ScreenName.SAVE_KEY]: 'Save Secret Key', - [ScreenName.CONNECT_APP]: 'Connect App', - [ScreenName.SIGN_IN]: 'Sign In', - [ScreenName.RECOVERY_CODE]: 'Magic Recovery Code', - [ScreenName.ADD_ACCOUNT]: ' Select Username', - [ScreenName.REGISTRY_ERROR]: 'Username Registry Error', + [ScreenPaths.CHOOSE_ACCOUNT]: 'Choose Account', + [ScreenPaths.USERNAME]: 'Username', + [ScreenPaths.GENERATION]: 'Generation', + [ScreenPaths.SECRET_KEY]: 'Copy Secret Key', + [ScreenPaths.SAVE_KEY]: 'Save Secret Key', + [ScreenPaths.SIGN_IN]: 'Sign In', + [ScreenPaths.RECOVERY_CODE]: 'Magic Recovery Code', + [ScreenPaths.ADD_ACCOUNT]: ' Select Username', + [ScreenPaths.REGISTRY_ERROR]: 'Username Registry Error', }; export const titleNameMap = { - [ScreenName.CHOOSE_ACCOUNT]: 'Choose account', - [ScreenName.USERNAME]: 'Choose a username', - [ScreenName.GENERATION]: 'Generating your Secret Key', - [ScreenName.SECRET_KEY]: 'Your Secret Key', - [ScreenName.SAVE_KEY]: 'Save your Secret Key', - [ScreenName.CONNECT_APP]: 'Connect App', - [ScreenName.SIGN_IN]: 'Sign in', - [ScreenName.RECOVERY_CODE]: 'Enter your password', - [ScreenName.ADD_ACCOUNT]: ' Select Username', - [ScreenName.REGISTRY_ERROR]: 'Failed to register username', + [ScreenPaths.CHOOSE_ACCOUNT]: 'Choose account', + [ScreenPaths.USERNAME]: 'Choose a username', + [ScreenPaths.GENERATION]: 'Generating your Secret Key', + [ScreenPaths.SECRET_KEY]: 'Your Secret Key', + [ScreenPaths.SAVE_KEY]: 'Save your Secret Key', + [ScreenPaths.SIGN_IN]: 'Sign in', + [ScreenPaths.RECOVERY_CODE]: 'Enter your password', + [ScreenPaths.ADD_ACCOUNT]: ' Select Username', + [ScreenPaths.REGISTRY_ERROR]: 'Failed to register username', }; -export const doTrackScreenChange = (screen: ScreenName, decodedAuthRequest: DecodedAuthRequest | undefined) => { +export const doTrackScreenChange = (screen: ScreenPaths, decodedAuthRequest: DecodedAuthRequest | undefined) => { document.title = titleNameMap[screen]; const appURL = decodedAuthRequest ? new URL(decodedAuthRequest?.redirect_uri) : null; window.analytics.page(pageTrackingNameMap[screen], { diff --git a/packages/app/src/common/utils.ts b/packages/app/src/common/utils.ts index 217bbabc7d8..31e241f47ea 100644 --- a/packages/app/src/common/utils.ts +++ b/packages/app/src/common/utils.ts @@ -2,8 +2,8 @@ import { DecodedAuthRequest } from './dev/types'; import { wordlists } from 'bip39'; export const getAuthRequestParam = () => { - const { search } = document.location; - const matches = /authRequest=(.*)&?/.exec(search); + const { hash } = document.location; + const matches = /authRequest=(.*)&?/.exec(hash); if (matches && matches.length === 2) { return matches[1]; } diff --git a/packages/app/src/components/accounts/index.tsx b/packages/app/src/components/accounts/index.tsx index 3a7c94cc7f7..9c6a31a0f92 100644 --- a/packages/app/src/components/accounts/index.tsx +++ b/packages/app/src/components/accounts/index.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; import { Identity } from '@blockstack/keychain'; import { Text, Flex, FlexProps, Spinner } from '@blockstack/ui'; -import { ScreenName } from '@store/onboarding/types'; +import { ScreenPaths } from '@store/onboarding/types'; import { PlusInCircle } from '@components/icons/plus-in-circle'; import { ListItem } from './list-item'; import { AccountAvatar } from './account-avatar'; @@ -52,7 +51,6 @@ interface AccountsProps { } export const Accounts = ({ identities, showAddAccount, next }: AccountsProps) => { - const dispatch = useDispatch(); const [selectedAddress, setSelectedAddress] = useState(null); const { doChangeScreen } = useAnalytics(); @@ -86,7 +84,7 @@ export const Accounts = ({ identities, showAddAccount, next }: AccountsProps) => { if (selectedAddress) return; - dispatch(doChangeScreen(ScreenName.ADD_ACCOUNT)); + doChangeScreen(ScreenPaths.ADD_ACCOUNT); }} cursor={selectedAddress ? 'not-allowed' : 'pointer'} hasAction diff --git a/packages/app/src/pages/onboarding.tsx b/packages/app/src/components/app.tsx similarity index 67% rename from packages/app/src/pages/onboarding.tsx rename to packages/app/src/components/app.tsx index 2a3b5b36d63..28b38b6347d 100644 --- a/packages/app/src/pages/onboarding.tsx +++ b/packages/app/src/components/app.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { ThemeProvider, theme, CSSReset } from '@blockstack/ui'; import { createGlobalStyle } from 'styled-components'; -import { Onboarding } from '@components/onboarding'; +import { Routes } from '@components/routes'; +import { HashRouter as Router } from 'react-router-dom'; const GlobalStyles = createGlobalStyle` #actions-root{ @@ -11,16 +12,18 @@ width: 100%; flex-direction: column; }`; -export const OnboardingApp: React.FC = () => { +export const App: React.FC = () => { return ( - + + + ); }; -export default OnboardingApp; +export default App; diff --git a/packages/app/src/components/onboarding/index.tsx b/packages/app/src/components/onboarding/index.tsx deleted file mode 100644 index 9d3c6a6c55d..00000000000 --- a/packages/app/src/components/onboarding/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useEffect } from 'react'; -import { ChooseAccount, Create, SaveKey, SecretKey, SignIn, Username, DecryptRecoveryCode } from './screens'; -import { doSaveAuthRequest } from '@store/onboarding/actions'; -import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '@store'; -import { ScreenName } from '@store/onboarding/types'; -import { doFinishSignIn as finishSignIn } from '@store/onboarding/actions'; -import { selectCurrentScreen } from '@store/onboarding/selectors'; -import { authenticationInit } from '@common/utils'; -import { UsernameRegistryError } from './screens/registery-error'; -import { useAnalytics } from '@common/hooks/use-analytics'; - -const RenderScreen = ({ ...rest }) => { - const dispatch = useDispatch(); - const { screen } = useSelector((state: AppState) => ({ - screen: selectCurrentScreen(state), - })); - const { doChangeScreen } = useAnalytics(); - - // const doFinishSignIn = async (identityIndex = 0, identity?: Identity) => { - const doFinishSignIn = ({ identityIndex }: { identityIndex: number } = { identityIndex: 0 }) => { - dispatch(finishSignIn({ identityIndex })); - }; - - /** - * TODO: make this check if logged in better - */ - // React.useEffect(() => { - // if (screen !== ScreenName.CHOOSE_ACCOUNT && identities && identities.length) { - // dispatch(doChangeScreen(ScreenName.CHOOSE_ACCOUNT)); - // } - // }, [screen, identities]); - - const ChooseScreen = () => ( - { - doFinishSignIn({ identityIndex }); - }} - {...rest} - /> - ); - - const usernameNext = () => dispatch(doChangeScreen(ScreenName.GENERATION)); - - switch (screen) { - // username - case ScreenName.USERNAME: - return ; - - case ScreenName.ADD_ACCOUNT: - return ; - - // create - case ScreenName.GENERATION: - return dispatch(doChangeScreen(ScreenName.SECRET_KEY))} {...rest} />; - - // Key screens - case ScreenName.SECRET_KEY: - return dispatch(doChangeScreen(ScreenName.SAVE_KEY))} {...rest} />; - - case ScreenName.SAVE_KEY: - return ( - { - dispatch(doChangeScreen(ScreenName.USERNAME)); - }} - {...rest} - /> - ); - - // Sign In - case ScreenName.SIGN_IN: - return ( - dispatch(doChangeScreen(ScreenName.CHOOSE_ACCOUNT))} - back={() => { - dispatch(doChangeScreen(ScreenName.SECRET_KEY)); - }} - {...rest} - /> - ); - - case ScreenName.CHOOSE_ACCOUNT: - return ; - - case ScreenName.RECOVERY_CODE: - return dispatch(doChangeScreen(ScreenName.CHOOSE_ACCOUNT))} />; - - case ScreenName.REGISTRY_ERROR: - return ; - - default: - return null; - } -}; - -const Onboarding: React.FC = () => { - const dispatch = useDispatch(); - - useEffect(() => { - const authRequest = authenticationInit(); - if (authRequest) { - dispatch(doSaveAuthRequest(authRequest)); - } - }, []); - return ; -}; - -export { Onboarding }; diff --git a/packages/app/src/components/onboarding/screens/index.tsx b/packages/app/src/components/onboarding/screens/index.tsx deleted file mode 100644 index 98a88543949..00000000000 --- a/packages/app/src/components/onboarding/screens/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export * from './sign-in'; -export * from './choose-account'; -export * from './create'; -export * from './secret-key'; -export * from './save-key'; -export * from './username'; -export * from './decrypt-recovery-code'; diff --git a/packages/app/src/components/onboarding/screens/registery-error.tsx b/packages/app/src/components/onboarding/screens/registery-error.tsx deleted file mode 100644 index d3056c64891..00000000000 --- a/packages/app/src/components/onboarding/screens/registery-error.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Screen, ScreenHeader, ScreenBody, PoweredBy, ScreenFooter, Title } from '@blockstack/connect'; -import { Flex, Box, Button } from '@blockstack/ui'; - -export const UsernameRegistryError: React.FC = () => { - return ( - - - - - There was a problem registering your username. We've alerted our team to the issue. - - - , - ]} - /> - - - - - ); -}; diff --git a/packages/app/src/components/routes.tsx b/packages/app/src/components/routes.tsx new file mode 100644 index 00000000000..27b64b42dc5 --- /dev/null +++ b/packages/app/src/components/routes.tsx @@ -0,0 +1,109 @@ +import React, { useEffect } from 'react'; +import { Home } from '../pages/home'; +import { Create, SaveKey } from '../pages/sign-up'; +import { SignIn, DecryptRecoveryCode } from '../pages/sign-in'; + +import { Username } from '../pages/username'; +import { UsernameRegistryError } from '../pages/registery-error'; +import { SecretKey } from '../pages/secret-key'; + +import { ChooseAccount } from '../pages/connect'; + +import { doSaveAuthRequest } from '@store/onboarding/actions'; +import { useDispatch } from 'react-redux'; +import { ScreenPaths } from '@store/onboarding/types'; +import { doFinishSignIn as finishSignIn } from '@store/onboarding/actions'; +import { authenticationInit } from '@common/utils'; +import { useAnalytics } from '@common/hooks/use-analytics'; +import { useWallet } from '@common/hooks/use-wallet'; +import { useOnboardingState } from '@common/hooks/use-onboarding-state'; + +import { Routes as RoutesDom, Route, Navigate } from 'react-router-dom'; + +export const Routes: React.FC = () => { + const dispatch = useDispatch(); + const { doChangeScreen } = useAnalytics(); + const { identities } = useWallet(); + const { isOnboardingInProgress } = useOnboardingState(); + const authRequest = authenticationInit(); + + useEffect(() => { + if (authRequest) { + dispatch(doSaveAuthRequest(authRequest)); + } + }, [authRequest]); + + const doFinishSignIn = ({ identityIndex }: { identityIndex: number } = { identityIndex: 0 }) => + dispatch(finishSignIn({ identityIndex })); + + const isSignedIn = !isOnboardingInProgress && identities.length; + + return ( + + } /> + {/*Sign Up*/} + + ) : ( + doChangeScreen(ScreenPaths.SECRET_KEY)} /> + ) + } + /> + doChangeScreen(ScreenPaths.SAVE_KEY)} />} /> + { + doChangeScreen(ScreenPaths.USERNAME); + }} + /> + } + /> + + ) : ( + doChangeScreen(ScreenPaths.GENERATION)} /> + ) + } + /> + {/*Sign In*/} + + ) : ( + doChangeScreen(ScreenPaths.CHOOSE_ACCOUNT)} + back={() => doChangeScreen(ScreenPaths.SECRET_KEY)} + /> + ) + } + /> + doChangeScreen(ScreenPaths.CHOOSE_ACCOUNT)} />} + /> + doChangeScreen(ScreenPaths.GENERATION)} />} />; + { + doFinishSignIn({ identityIndex }); + }} + /> + } + /> + ;{/*Error/Misc*/} + } /> + + ); +}; diff --git a/packages/app/src/components/sign-out.tsx b/packages/app/src/components/sign-out.tsx index bc07f7f1392..8c304da72e5 100644 --- a/packages/app/src/components/sign-out.tsx +++ b/packages/app/src/components/sign-out.tsx @@ -1,26 +1,28 @@ import React from 'react'; -import { Identity } from '@blockstack/keychain'; import { Flex, Text, Button, FlexProps } from '@blockstack/ui'; - import { Accounts } from '@components/accounts'; +import { useWallet } from '@common/hooks/use-wallet'; + interface SignOutProps extends FlexProps { - identities: Identity[]; signOut: () => void; buttonMode?: 'primary' | 'secondary'; } -export const SignOut = ({ buttonMode = 'primary', identities, signOut, ...rest }: SignOutProps) => ( - - - Sign out - - - - - This will sign you out on this device. To sign back in, you will have to enter your Secret Key. - - -); +export const SignOut = ({ buttonMode = 'primary', signOut, ...rest }: SignOutProps) => { + const { identities } = useWallet(); + return ( + + + Sign out + + + + + This will sign you out on this device. To sign back in, you will have to enter your Secret Key. + + + ); +}; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index c899203b995..a5ab4be6907 100755 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -1,26 +1,27 @@ -import * as React from 'react'; +import React from 'react'; import ExtStore from './store/ext-store'; import ReactDOM from 'react-dom'; import { Store as ReduxStore } from 'redux'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import { persistor } from './store'; -import { OptionsApp } from './pages/options'; +import App from '@components/app'; import DevStore from './common/dev/store'; const buildApp = (store: ReduxStore | ReturnType) => { ReactDOM.render( - + , - document.getElementById('options-root') + document.getElementById('actions-root') ); }; if (EXT_ENV === 'web') { - buildApp(DevStore); + const store = DevStore; + buildApp(store); } else { const store = ExtStore(); // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/packages/app/src/components/onboarding/screens/choose-account.tsx b/packages/app/src/pages/connect/choose-account.tsx similarity index 100% rename from packages/app/src/components/onboarding/screens/choose-account.tsx rename to packages/app/src/pages/connect/choose-account.tsx diff --git a/packages/app/src/pages/connect/index.ts b/packages/app/src/pages/connect/index.ts new file mode 100644 index 00000000000..17aaa87ccfb --- /dev/null +++ b/packages/app/src/pages/connect/index.ts @@ -0,0 +1 @@ +export * from './choose-account'; diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx new file mode 100644 index 00000000000..afb6eea66fa --- /dev/null +++ b/packages/app/src/pages/home.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Box, Flex, Text } from '@blockstack/ui'; +import { LogoWithName } from '@components/logo-with-name'; +import { SignOut } from '@components/sign-out'; + +import { useDispatch } from 'react-redux'; +import { useWallet } from '@common/hooks/use-wallet'; +import { doSignOut } from '@store/wallet'; + +const SignedOut = () => ( + + + + You're signed out of Secret Key + + + + + + Find an app to use + + + +); + +export const Home = () => { + const dispatch = useDispatch(); + const { identities } = useWallet(); + const isSignedIn = identities.length > 0; + + return ( + + + + {isSignedIn ? ( + { + dispatch(doSignOut()); + }} + /> + ) : ( + + )} + + + ); +}; diff --git a/packages/app/src/pages/options.tsx b/packages/app/src/pages/options.tsx deleted file mode 100644 index b4f32c53e77..00000000000 --- a/packages/app/src/pages/options.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { Box, Flex, Text, ThemeProvider, theme, CSSReset } from '@blockstack/ui'; -import { useSelector, useDispatch } from 'react-redux'; -import { selectIdentities } from '@store/wallet/selectors'; -import { AppState } from '../store/index'; -import { LogoWithName } from '@components/logo-with-name'; -import { doSignOut } from '@store/wallet'; -import { SignOut } from '@components/sign-out'; - -export const OptionsApp = () => { - const { identities } = useSelector((state: AppState) => ({ - identities: selectIdentities(state), - })); - const dispatch = useDispatch(); - - const isSignedIn = identities.length > 0; - - return ( - - - - - - {isSignedIn ? ( - { - dispatch(doSignOut()); - }} - /> - ) : ( - - - - You're signed out of Secret Key - - - - - - Find an app to use - - - - )} - - - - ); -}; diff --git a/packages/app/src/pages/registery-error.tsx b/packages/app/src/pages/registery-error.tsx new file mode 100644 index 00000000000..98378428d4a --- /dev/null +++ b/packages/app/src/pages/registery-error.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Screen, ScreenHeader, ScreenBody, PoweredBy, ScreenFooter, Title } from '@blockstack/connect'; +import { Flex, Box, Button } from '@blockstack/ui'; + +export const UsernameRegistryError: React.FC = () => ( + + + + + There was a problem registering your username. We've alerted our team to the issue. + + + , + ]} + /> + + + + +); diff --git a/packages/app/src/components/onboarding/screens/secret-key.tsx b/packages/app/src/pages/secret-key.tsx similarity index 83% rename from packages/app/src/components/onboarding/screens/secret-key.tsx rename to packages/app/src/pages/secret-key.tsx index 4cf61f29c95..d980129b263 100644 --- a/packages/app/src/components/onboarding/screens/secret-key.tsx +++ b/packages/app/src/pages/secret-key.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; + import { Screen, ScreenBody, ScreenActions, Title, PoweredBy, ScreenFooter } from '@blockstack/connect'; import { ScreenHeader } from '@components/connected-screen-header'; import { Button, Text } from '@blockstack/ui'; @@ -7,8 +7,9 @@ import { Button, Text } from '@blockstack/ui'; import { Toast } from '@components/toast'; import { Card } from '@components/card'; import { SeedTextarea } from '@components/seed-textarea'; -import { AppState } from '@store'; -import { selectSecretKey, selectAppName } from '@store/onboarding/selectors'; + +import { useWallet } from '@common/hooks/use-wallet'; +import { useAppDetails } from '@common/hooks/useAppDetails'; import { PasswordManagerHiddenInput } from '@components/pw-manager-input'; interface SecretKeyProps { @@ -17,10 +18,8 @@ interface SecretKeyProps { export const SecretKey: React.FC = props => { const title = 'Your Secret Key'; - const { secretKey, appName } = useSelector((state: AppState) => ({ - secretKey: selectSecretKey(state), - appName: selectAppName(state), - })); + const { secretKey } = useWallet(); + const { name } = useAppDetails(); const [copied, setCopiedState] = useState(false); useEffect(() => { @@ -41,14 +40,14 @@ export const SecretKey: React.FC = props => { return ( <> - + {title}, - Here’s your Secret Key: 12 words that prove it’s you when you want to use {appName} on a new device. Once + Here’s your Secret Key: 12 words that prove it’s you when you want to use {name} on a new device. Once lost it’s lost forever, so save it somewhere you won’t forget. , diff --git a/packages/app/src/components/onboarding/screens/decrypt-recovery-code.tsx b/packages/app/src/pages/sign-in/decrypt-recovery-code.tsx similarity index 100% rename from packages/app/src/components/onboarding/screens/decrypt-recovery-code.tsx rename to packages/app/src/pages/sign-in/decrypt-recovery-code.tsx diff --git a/packages/app/src/pages/sign-in/index.tsx b/packages/app/src/pages/sign-in/index.tsx new file mode 100644 index 00000000000..5dcf954df67 --- /dev/null +++ b/packages/app/src/pages/sign-in/index.tsx @@ -0,0 +1,2 @@ +export * from './decrypt-recovery-code'; +export * from './initial'; diff --git a/packages/app/src/components/onboarding/screens/sign-in.tsx b/packages/app/src/pages/sign-in/initial.tsx similarity index 95% rename from packages/app/src/components/onboarding/screens/sign-in.tsx rename to packages/app/src/pages/sign-in/initial.tsx index c142e8b2fc0..b523e08881b 100644 --- a/packages/app/src/components/onboarding/screens/sign-in.tsx +++ b/packages/app/src/pages/sign-in/initial.tsx @@ -8,7 +8,7 @@ import useDocumentTitle from '@rehooks/document-title'; import { useDispatch, useSelector } from 'react-redux'; import { SIGN_IN_CORRECT, SIGN_IN_CREATE, SIGN_IN_INCORRECT } from '@common/track'; import { doSetMagicRecoveryCode } from '@store/onboarding/actions'; -import { ScreenName, DEFAULT_PASSWORD } from '@store/onboarding/types'; +import { ScreenPaths, DEFAULT_PASSWORD } from '@store/onboarding/types'; import { AppState } from '@store'; import { selectAppName } from '@store/onboarding/selectors'; import { doStoreSeed } from '@store/wallet'; @@ -45,7 +45,7 @@ export const SignIn: React.FC = props => { } if (parsedKeyInput.split(' ').length <= 1) { dispatch(doSetMagicRecoveryCode(parsedKeyInput)); - dispatch(doChangeScreen(ScreenName.RECOVERY_CODE)); + doChangeScreen(ScreenPaths.RECOVERY_CODE); return; } await doStoreSeed(parsedKeyInput, DEFAULT_PASSWORD)(dispatch, () => ({}), {}); @@ -104,7 +104,7 @@ export const SignIn: React.FC = props => { color="blue" onClick={() => { doTrack(SIGN_IN_CREATE); - dispatch(doChangeScreen(ScreenName.GENERATION)); + doChangeScreen(ScreenPaths.GENERATION); }} > Create a Secret Key diff --git a/packages/app/src/components/onboarding/screens/create.tsx b/packages/app/src/pages/sign-up/create.tsx similarity index 90% rename from packages/app/src/components/onboarding/screens/create.tsx rename to packages/app/src/pages/sign-up/create.tsx index 0c0628c8c36..c164073d68e 100644 --- a/packages/app/src/components/onboarding/screens/create.tsx +++ b/packages/app/src/pages/sign-up/create.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { Spinner, Flex, Text } from '@blockstack/ui'; import { Screen, ScreenBody, PoweredBy, ScreenFooter } from '@blockstack/connect'; import { ScreenHeader } from '@components/connected-screen-header'; -import { doCreateSecretKey } from '@store/onboarding/actions'; +import { doCreateSecretKey, doSetOnboardingProgress } from '@store/onboarding/actions'; import { useAppDetails } from '@common/hooks/useAppDetails'; -import { selectCurrentWallet } from '@store/wallet/selectors'; -import { AppState } from '@store'; + +import { useWallet } from '@common/hooks/use-wallet'; interface ExplainerCardProps { title: string; @@ -51,9 +51,7 @@ interface CreateProps { } export const Create: React.FC = props => { const [cardIndex, setCardIndex] = useState(0); - const { wallet } = useSelector((state: AppState) => ({ - wallet: selectCurrentWallet(state), - })); + const { wallet, isRestoringWallet } = useWallet(); const { name } = useAppDetails(); const dispatch = useDispatch(); @@ -85,7 +83,8 @@ export const Create: React.FC = props => { // We have this check for `wallet`, because this is the // default first screen rendered. We don't want to accidentally create a new // seed if a logged-in user gets into this hook. - if (!wallet) { + if (!wallet && !isRestoringWallet) { + dispatch(doSetOnboardingProgress(true)); dispatch(doCreateSecretKey()); } }, 200); diff --git a/packages/app/src/pages/sign-up/index.tsx b/packages/app/src/pages/sign-up/index.tsx new file mode 100644 index 00000000000..ddef1af4d60 --- /dev/null +++ b/packages/app/src/pages/sign-up/index.tsx @@ -0,0 +1,2 @@ +export * from './create'; +export * from './save-key'; diff --git a/packages/app/src/components/onboarding/screens/save-key.tsx b/packages/app/src/pages/sign-up/save-key.tsx similarity index 74% rename from packages/app/src/components/onboarding/screens/save-key.tsx rename to packages/app/src/pages/sign-up/save-key.tsx index b224d9aa6dd..e50068852ef 100644 --- a/packages/app/src/components/onboarding/screens/save-key.tsx +++ b/packages/app/src/pages/sign-up/save-key.tsx @@ -1,17 +1,15 @@ import React, { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; import { Screen, ScreenBody, ScreenActions, Title } from '@blockstack/connect'; import { ScreenHeader } from '@components/connected-screen-header'; import { Button, Text } from '@blockstack/ui'; import { Collapse } from '@components/collapse'; -import { AppState } from '@store'; -import { selectAppName } from '@store/onboarding/selectors'; -import { faqs } from '@components/onboarding/data'; -import { doChangeScreen } from '@store/onboarding/actions'; -import { ScreenName } from '@store/onboarding/types'; +import { faqs } from '@common/onboarding-data'; +import { ScreenPaths } from '@store/onboarding/types'; +import { useAppDetails } from '@common/hooks/useAppDetails'; +import { useAnalytics } from '@common/hooks/use-analytics'; interface SaveKeyProps { next: () => void; @@ -19,9 +17,9 @@ interface SaveKeyProps { export const SaveKey: React.FC = ({ next }) => { const title = 'Save your Secret Key'; - const appName = useSelector((state: AppState) => selectAppName(state)); + const { name } = useAppDetails(); + const { doChangeScreen } = useAnalytics(); const [loading, setLoading] = useState(false); - const dispatch = useDispatch(); return ( @@ -59,12 +57,12 @@ export const SaveKey: React.FC = ({ next }) => { textAlign="center" py={5} cursor="pointer" - onClick={() => dispatch(doChangeScreen(ScreenName.SECRET_KEY))} + onClick={() => doChangeScreen(ScreenPaths.SECRET_KEY)} > View Secret Key again - + ); }; diff --git a/packages/app/src/components/onboarding/screens/username.tsx b/packages/app/src/pages/username.tsx similarity index 74% rename from packages/app/src/components/onboarding/screens/username.tsx rename to packages/app/src/pages/username.tsx index 7642f2148b2..5c7e7a40aae 100644 --- a/packages/app/src/components/onboarding/screens/username.tsx +++ b/packages/app/src/pages/username.tsx @@ -1,34 +1,27 @@ import React, { useState } from 'react'; -import { Box, Input, Text, Button } from '@blockstack/ui'; -import { Screen, ScreenBody, ScreenActions, Title, PoweredBy, ScreenFooter } from '@blockstack/connect'; +import { Box, Button, Input, Text } from '@blockstack/ui'; +import { PoweredBy, Screen, ScreenActions, ScreenBody, ScreenFooter, Title } from '@blockstack/connect'; import { ScreenHeader } from '@components/connected-screen-header'; import { useAppDetails } from '@common/hooks/useAppDetails'; -import { useDispatch, useSelector } from 'react-redux'; -import { doSetUsername, doFinishSignIn } from '@store/onboarding/actions'; -import { selectCurrentWallet } from '@store/wallet/selectors'; -import { AppState } from '@store'; -import { DEFAULT_PASSWORD, ScreenName } from '@store/onboarding/types'; -import { - registerSubdomain, - IdentityNameValidityError, - validateSubdomain, - Identity, - Wallet, -} from '@blockstack/keychain'; +import { useDispatch } from 'react-redux'; +import { doFinishSignIn, doSetUsername } from '@store/onboarding/actions'; +import { useWallet } from '@common/hooks/use-wallet'; + +import { DEFAULT_PASSWORD, ScreenPaths } from '@store/onboarding/types'; +import { Identity, IdentityNameValidityError, registerSubdomain, validateSubdomain } from '@blockstack/keychain'; import { didGenerateWallet } from '@store/wallet'; import { ErrorLabel } from '@components/error-label'; import { gaiaUrl, Subdomain } from '@common/constants'; -import { selectCurrentScreen } from '@store/onboarding/selectors'; -import { selectAppName } from '@store/onboarding/selectors'; import { USERNAME_REGISTER_FAILED, + USERNAME_SUBMIT_SUCCESS, USERNAME_SUBMITTED, USERNAME_VALIDATION_ERROR, - USERNAME_SUBMIT_SUCCESS, } from '@common/track'; import { useAnalytics } from '@common/hooks/use-analytics'; +import { useLocation } from 'react-router-dom'; const identityNameLengthError = 'Your username should be at least 8 characters, with a maximum of 37 characters.'; const identityNameIllegalCharError = 'You can only use lowercase letters (a–z), numbers (0–9), and underscores (_).'; @@ -45,20 +38,21 @@ interface UsernameProps { } export const Username: React.FC = ({ next }) => { + const { pathname } = useLocation(); + + const { wallet } = useWallet(); const dispatch = useDispatch(); const { name } = useAppDetails(); const { doChangeScreen, doTrack } = useAnalytics(); - const { wallet, screen, appName } = useSelector((state: AppState) => ({ - wallet: selectCurrentWallet(state) as Wallet, - screen: selectCurrentScreen(state), - appName: selectAppName(state), - })); - - const titleForPasswordManagers = `${appName} with Blockstack`; - document.title = titleForPasswordManagers; + document.title = `${name} with Blockstack`; const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); + const [status, setStatus] = useState('initial'); + const setLoadingStatus = () => setStatus('loading'); + const setErrorStatus = () => setStatus('error'); + + const isLoading = status === 'loading'; + const [username, setUsername] = useState(''); const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false); @@ -69,7 +63,7 @@ export const Username: React.FC = ({ next }) => { const onSubmit = async () => { setHasAttemptedSubmit(true); - setLoading(true); + setLoadingStatus(); doTrack(USERNAME_SUBMITTED); @@ -78,28 +72,28 @@ export const Username: React.FC = ({ next }) => { if (validationErrors !== null) { doTrack(USERNAME_VALIDATION_ERROR); setError(validationErrors); - setLoading(false); + setErrorStatus(); + return; + } + + if (!wallet) { + dispatch(doSetUsername(username)); + next(); return; } let identity: Identity; let identityIndex: number; - if (screen === ScreenName.USERNAME) { + if (pathname === ScreenPaths.USERNAME) { identity = wallet.identities[0]; identityIndex = 0; } else { - // we're in ScreenName.ADD_ACCOUNT + // we're in ScreenPaths.ADD_ACCOUNT identity = await wallet.createNewIdentity(DEFAULT_PASSWORD); identityIndex = wallet.identities.length - 1; } - if (!wallet) { - dispatch(doSetUsername(username)); - next(); - return; - } - try { await registerSubdomain({ username, @@ -112,7 +106,7 @@ export const Username: React.FC = ({ next }) => { dispatch(doFinishSignIn({ identityIndex })); } catch (error) { doTrack(USERNAME_REGISTER_FAILED, { status: error.status }); - dispatch(doChangeScreen(ScreenName.REGISTRY_ERROR)); + dispatch(doChangeScreen(ScreenPaths.REGISTRY_ERROR)); } }; @@ -152,7 +146,15 @@ export const Username: React.FC = ({ next }) => { ]} /> - diff --git a/packages/app/src/store/onboarding/actions.ts b/packages/app/src/store/onboarding/actions.ts index b57d6bfa130..5855d853e1b 100644 --- a/packages/app/src/store/onboarding/actions.ts +++ b/packages/app/src/store/onboarding/actions.ts @@ -1,7 +1,8 @@ import { OnboardingActions, CHANGE_PAGE, - ScreenName, + ONBOARDING_PROGRESS, + ScreenPaths, DEFAULT_PASSWORD, SAVE_KEY, SAVE_AUTH_REQUEST, @@ -26,7 +27,13 @@ import { finalizeAuthResponse } from '@common/utils'; import { gaiaUrl } from '@common/constants'; import { doTrackScreenChange } from '@common/track'; -export const doChangeScreen = (screen: ScreenName): OnboardingActions => { +export const doSetOnboardingProgress = (status: boolean): OnboardingActions => { + return { + type: ONBOARDING_PROGRESS, + payload: status, + }; +}; +export const doChangeScreen = (screen: ScreenPaths): OnboardingActions => { return { type: CHANGE_PAGE, screen, @@ -94,11 +101,24 @@ export function doSaveAuthRequest(authRequest: string): ThunkAction = ( @@ -39,7 +40,7 @@ export const onboardingReducer: Reducer = ( appURL: action.appURL, }; if (action.decodedAuthRequest.sendToSignIn) { - newState.screen = ScreenName.SIGN_IN; + newState.screen = ScreenPaths.SIGN_IN; } return newState; case SET_MAGIC_RECOVERY_CODE: @@ -52,6 +53,11 @@ export const onboardingReducer: Reducer = ( ...state, username: action.username, }; + case ONBOARDING_PROGRESS: + return { + ...state, + onboardingInProgress: action.payload, + }; default: return state; } diff --git a/packages/app/src/store/onboarding/selectors.ts b/packages/app/src/store/onboarding/selectors.ts index 493af787adb..25dcd242aa1 100644 --- a/packages/app/src/store/onboarding/selectors.ts +++ b/packages/app/src/store/onboarding/selectors.ts @@ -17,3 +17,5 @@ export const selectMagicRecoveryCode = (state: AppState) => state.onboarding.mag export const selectUsername = (state: AppState) => state.onboarding.username; export const selectAppURL = (state: AppState) => state.onboarding.appURL; + +export const selectOnboardingProgress = (state: AppState) => state.onboarding.onboardingInProgress; diff --git a/packages/app/src/store/onboarding/types.ts b/packages/app/src/store/onboarding/types.ts index 3bc8fa644ea..ea98d0cbe10 100644 --- a/packages/app/src/store/onboarding/types.ts +++ b/packages/app/src/store/onboarding/types.ts @@ -1,5 +1,6 @@ -import { DecodedAuthRequest } from '../../common/dev/types'; +import { DecodedAuthRequest } from '@common/dev/types'; +export const ONBOARDING_PROGRESS = 'ONBOARDING/ONBOARDING_PROGRESS'; export const CHANGE_PAGE = 'ONBOARDING/CHANGE_PAGE'; export const SAVE_KEY = 'ONBOARDING/SAVE_KEY'; export const SAVE_AUTH_REQUEST = 'ONBOARDING/SAVE_AUTH_REQUEST'; @@ -19,11 +20,23 @@ export enum ScreenName { REGISTRY_ERROR = 'screens/REGISTRY_ERROR', } +export enum ScreenPaths { + GENERATION = '/sign-up', + SECRET_KEY = '/sign-up/secret-key', + SAVE_KEY = '/sign-up/save-secret-key', + USERNAME = '/sign-up/username', + SIGN_IN = '/sign-in', + RECOVERY_CODE = '/sign-in/recover', + ADD_ACCOUNT = '/sign-in/add-account', + CHOOSE_ACCOUNT = '/connect/choose-account', + REGISTRY_ERROR = '/username-error', +} + // TODO: clarify usage of password for local key encryption export const DEFAULT_PASSWORD = 'password'; export interface OnboardingState { - screen: ScreenName; + screen: ScreenPaths; secretKey?: string; authRequest?: string; decodedAuthRequest?: DecodedAuthRequest; @@ -32,11 +45,17 @@ export interface OnboardingState { appURL?: URL; magicRecoveryCode?: string; username?: string; + onboardingInProgress?: boolean; +} + +interface OnboardingProgressAction { + type: typeof ONBOARDING_PROGRESS; + payload: boolean; } interface ChangePageAction { type: typeof CHANGE_PAGE; - screen: ScreenName; + screen: ScreenPaths; } interface StoreSecretKey { @@ -64,6 +83,7 @@ interface SetUsername { } export type OnboardingActions = + | OnboardingProgressAction | ChangePageAction | StoreSecretKey | SetMagicRecoveryCode diff --git a/packages/app/src/store/wallet/actions.ts b/packages/app/src/store/wallet/actions.ts index 71a9921d0d9..f58404fe39b 100644 --- a/packages/app/src/store/wallet/actions.ts +++ b/packages/app/src/store/wallet/actions.ts @@ -28,10 +28,10 @@ export function doSignOut(): WalletActions { }; } -export function doStoreSeed(seed: string, password: string): ThunkAction, {}, {}, WalletActions> { +export function doStoreSeed(secretKey: string, password: string): ThunkAction, {}, {}, WalletActions> { return async dispatch => { dispatch(isRestoringWallet()); - const wallet = await Wallet.restore(password, seed); + const wallet = await Wallet.restore(password, secretKey); dispatch(didRestoreWallet(wallet)); return wallet; }; diff --git a/packages/app/src/store/wallet/reducer.ts b/packages/app/src/store/wallet/reducer.ts index bc303196caa..33eedc5835d 100644 --- a/packages/app/src/store/wallet/reducer.ts +++ b/packages/app/src/store/wallet/reducer.ts @@ -2,9 +2,9 @@ import { Reducer } from 'redux'; import { WalletActions, WalletState, RESTORE_WALLET, IS_RESTORING_WALLET, GENERATE_WALLET, SIGN_OUT } from './types'; const initialState: WalletState = { - seed: null, + secretKey: undefined, isRestoringWallet: false, - currentWallet: null, + currentWallet: undefined, identities: [], }; diff --git a/packages/app/src/store/wallet/selectors.ts b/packages/app/src/store/wallet/selectors.ts index 7a0968a5f43..af7f6ce28f1 100644 --- a/packages/app/src/store/wallet/selectors.ts +++ b/packages/app/src/store/wallet/selectors.ts @@ -1,11 +1,7 @@ import { AppState } from '..'; -export const selectCurrentWallet = (state: AppState) => { - return state.wallet.currentWallet; -}; +export const selectCurrentWallet = (state: AppState) => state.wallet.currentWallet; export const selectIdentities = (state: AppState) => state.wallet.identities; export const selectFirstIdentity = (state: AppState) => state.wallet.currentWallet?.identities[0]; - -export const selectSeed = (state: AppState) => state.wallet.seed; - +export const selectIsSignedIn = (state: AppState) => state.wallet.identities?.length > 1; export const selectIsRestoringWallet = (state: AppState) => state.wallet.isRestoringWallet; diff --git a/packages/app/src/store/wallet/types.ts b/packages/app/src/store/wallet/types.ts index 880670705c7..a2bc57a1135 100644 --- a/packages/app/src/store/wallet/types.ts +++ b/packages/app/src/store/wallet/types.ts @@ -24,9 +24,9 @@ interface LogOutAction { } export interface WalletState { - seed: string | null; + secretKey?: string; isRestoringWallet: boolean; - currentWallet: Wallet | null; + currentWallet?: Wallet; identities: Identity[]; } diff --git a/packages/app/tests/integration/authentication.test.ts b/packages/app/tests/integration/authentication.test.ts index 24bd0d0bf22..afba4ccc697 100644 --- a/packages/app/tests/integration/authentication.test.ts +++ b/packages/app/tests/integration/authentication.test.ts @@ -34,7 +34,7 @@ if (process.env.CI_TEST_DEVICES) { environments.push([chromium, devices['Pixel 2']]); } -jest.retryTimes(3); +jest.retryTimes(process.env.CI ? 3 : 1); describe.each(environments)('auth scenarios - %o %o', (browserType, deviceType) => { let browser: Browser; let context: BrowserContext; diff --git a/packages/app/tests/integration/page-objects/demo.page.ts b/packages/app/tests/integration/page-objects/demo.page.ts index 16428c92841..78ec6702953 100644 --- a/packages/app/tests/integration/page-objects/demo.page.ts +++ b/packages/app/tests/integration/page-objects/demo.page.ts @@ -15,8 +15,8 @@ export class DemoPage { profileBtn = '//*[@title="Your Profile"]'; logoutBtn = '//*[contains(text(),"Log out")]'; alreadyHaveSecretKeyLink = '//span[text()="I already have a Secret Key"]'; - openConnectBtn = 'text="Open Connect"'; - openSignInBtn = 'text="Sign In"'; + openConnectBtn = createTestSelector('sign-up'); + openSignInBtn = createTestSelector('sign-in'); page: Page; diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index 024210c71f8..ea8ce376ae3 100755 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -64,8 +64,7 @@ module.exports = { popup: path.join(sourceRootPath, 'extension', 'index.tsx'), inpage: path.join(sourceRootPath, 'extension', 'inpage.ts'), 'message-bus': path.join(sourceRootPath, 'extension', 'content-scripts', 'message-bus.ts'), - options: path.join(sourceRootPath, 'index.tsx'), - actions: path.join(sourceRootPath, 'actions.tsx'), + index: path.join(sourceRootPath, 'index.tsx'), }, output: { path: distRootPath, @@ -170,6 +169,7 @@ module.exports = { }, devServer: { contentBase: './dist', + historyApiFallback: true, }, devtool: getSourceMap(), watch: false, @@ -177,14 +177,6 @@ module.exports = { new webpack.IgnorePlugin(/^\.\/wordlists\/(?!english)/, /bip39\/src$/), new webpack.HashedModuleIdsPlugin(), new CheckerPlugin(), - new HtmlWebpackPlugin({ - template: path.join(sourceRootPath, '../', 'public', 'html', 'options.html'), - inject: 'body', - filename: 'index.html', - title: 'Blockstack', - chunks: ['options', 'common'], - ...hmtlProdOpts, - }), new HtmlWebpackPlugin({ template: path.join(sourceRootPath, '../', 'public', 'html', 'popup.html'), inject: 'body', @@ -194,11 +186,11 @@ module.exports = { ...hmtlProdOpts, }), new HtmlWebpackPlugin({ - template: path.join(sourceRootPath, '../', 'public', 'html', 'actions.html'), + template: path.join(sourceRootPath, '../', 'public', 'html', 'index.html'), inject: 'body', - filename: 'actions.html', + filename: 'index.html', title: 'Blockstack', - chunks: ['actions', 'common'], + chunks: ['index', 'common'], ...hmtlProdOpts, }), new CopyWebpackPlugin([ diff --git a/packages/connect/src/auth.ts b/packages/connect/src/auth.ts index e9cb401b088..d8c2a9d7472 100644 --- a/packages/connect/src/auth.ts +++ b/packages/connect/src/auth.ts @@ -64,8 +64,11 @@ export const authenticate = async ({ const urlParams = new URLSearchParams(); params.forEach(([key, value]) => urlParams.set(key, value)); urlParams.set('authRequest', authRequest); + + const path = sendToSignIn ? 'sign-in' : 'sign-up'; + const popup = popupCenter({ - url: `${authURL.origin}/actions.html?${urlParams.toString()}`, + url: `${authURL.origin}/#/${path}?${urlParams.toString()}`, }); setupListener({ popup, authRequest, finished, authURL, userSession }); diff --git a/packages/test-app/components/app.tsx b/packages/test-app/components/app.tsx index 379cde554ca..ee3484a2ecf 100755 --- a/packages/test-app/components/app.tsx +++ b/packages/test-app/components/app.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { ThemeProvider, theme, Flex, CSSReset, Button, Stack } from '@blockstack/ui'; +import { ThemeProvider, Box, theme, Text, Flex, CSSReset, Button, Stack } from '@blockstack/ui'; import { Connect, AuthOptions, useConnect } from '@blockstack/connect'; const icon = `${document.location.href}/assets/messenger-app-icon.png`; @@ -14,21 +14,68 @@ if (document.location.origin.includes('deploy-preview')) { authOrigin = document.location.origin.replace('authenticator-demo', 'stacks-authenticator'); } +const Card: React.FC = props => ( + +); + const AppContent: React.FC = () => { const { doOpenAuth, doAuth } = useConnect(); return ( - - - - - + + + Blockstack Connect + + + + + + + + + + ); +}; + +interface AppState { + [key: string]: any; +} + +const SignedIn = (props: { username: string; handleSignOut: () => void }) => { + return ( + + + Welcome back! + + + {props.username} + + + + + ); }; export const App: React.FC = () => { + const [state, setState] = React.useState({}); const [authResponse, setAuthResponse] = React.useState(''); const [appPrivateKey, setAppPrivateKey] = React.useState(''); @@ -36,9 +83,11 @@ export const App: React.FC = () => { manifestPath: '/static/manifest.json', redirectTo: '/', finished: ({ userSession, authResponse }) => { - console.log(userSession.loadUserData()); + const userData = userSession.loadUserData(); + setState(() => userData); setAppPrivateKey(userSession.loadUserData().appPrivateKey); setAuthResponse(authResponse); + console.log(userData); }, authOrigin, appDetails: { @@ -47,14 +96,19 @@ export const App: React.FC = () => { }, }; + const handleSignOut = () => { + setState({}); + }; + const isSignedIn = (state && state.identityAddress) || undefined; return ( - + {authResponse && } {appPrivateKey && } - + + {!isSignedIn ? : } diff --git a/yarn.lock b/yarn.lock index ce5d8fd0717..684bab94a6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1164,7 +1164,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== @@ -2996,6 +2996,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/history@*": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" + integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== + "@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" @@ -3148,6 +3153,23 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" + integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-test-renderer@^16.8.2": version "16.9.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" @@ -8623,6 +8645,13 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +history@5.0.0-beta.4: + version "5.0.0-beta.4" + resolved "https://registry.yarnpkg.com/history/-/history-5.0.0-beta.4.tgz#7fd3bb1f6c75d00d9b5112a816766bfc72d1a3cd" + integrity sha512-LMUnKPB5UlEzDF1BO0VxtDsrguGPO7SuQEmB/5OjL1305afR1O8FvX29rbJep4g2SLmKK3YdDA7+8ZDs8P8n8g== + dependencies: + "@babel/runtime" "^7.7.6" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -14054,6 +14083,22 @@ react-refresh@^0.7.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.7.2.tgz#f30978d21eb8cac6e2f2fde056a7d04f6844dd50" integrity sha512-u5l7fhAJXecWUJzVxzMRU2Zvw8m4QmDNHlTrT5uo3KBlYBhmChd7syAakBoay1yIiVhx/8Fi7a6v6kQZfsw81Q== +react-router-dom@^6.0.0-alpha.2: + version "6.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.0-alpha.2.tgz#fd79fa09cad791ff2f55fab9246fae49bec912a4" + integrity sha512-c0lRIHtxVGj83jDy/HtBhNwJMfNiiDVdIgZ/Y8nUbyLMpl84Lrtfl3s8WQ0S2IVt6yHtbX6THlYUKYru7wQsuQ== + dependencies: + history "5.0.0-beta.4" + prop-types "^15.7.2" + +react-router@^6.0.0-alpha.2: + version "6.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.0-alpha.2.tgz#cfed9b59e88109f124f6164108b95d4fc8851a77" + integrity sha512-pQyM4qQfkB2PYzf5O43RKi6nMrdFrUyHfkFRpY3Y/RjgzLljQ8CKy+ePoBSAL94t7WKDew0ArSTUOv1C1uiDVg== + dependencies: + history "5.0.0-beta.4" + prop-types "^15.7.2" + react-simple-code-editor@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz#73e7ac550a928069715482aeb33ccba36efe2373"