Skip to content

Commit

Permalink
fix(onboarding): fixes password loop, closes leather-io#1204, leather…
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie authored and aulneau committed May 25, 2021
1 parent 5d24925 commit 71524bf
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 98 deletions.
5 changes: 5 additions & 0 deletions .changeset/perfect-dingos-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@stacks/wallet-web': patch
---

Fixes #1204, where a rerender issues causes users in the onboarding flow to enter a prohibative glitch. Credit to community member @whoabuddy for reporting
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@
"history": "5.0.0",
"http-server": "0.12.3",
"jsontokens": "3.0.0",
"just-debounce-it": "1.5.0",
"mdi-react": "7.5.0",
"preact": "10.5.13",
"prismjs": "1.23.0",
Expand All @@ -92,6 +91,7 @@
"safe-compare": "1.1.4",
"schema-inspector": "2.0.1",
"swr": "0.5.6",
"ts-debounce": "3.0.0",
"use-events": "1.4.2",
"use-latest": "1.2.0",
"valid-url": "1.0.9",
Expand Down
199 changes: 104 additions & 95 deletions src/pages/set-password.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import React, { useState, useCallback, useRef } from 'react';
import React, { useState, useCallback } from 'react';
import { debounce } from 'ts-debounce';
import { Button, Input, Stack } from '@stacks/ui';
import { Form, Formik } from 'formik';
import * as yup from 'yup';
import { PopupContainer } from '@components/popup/container';
import { useDoChangeScreen } from '@common/hooks/use-do-change-screen';
import { ScreenPaths } from '@store/types';
import { useWallet } from '@common/hooks/use-wallet';
import { buildEnterKeyEvent } from '@components/link';

import { useOnboardingState } from '@common/hooks/use-onboarding-state';
import { USERNAMES_ENABLED } from '@common/constants';
import { validatePassword, blankPasswordValidation } from '@common/validate-password';
import { Body, Caption } from '@components/typography';
import debounce from 'just-debounce-it';
import { Header } from '@components/header';

const HUMAN_REACTION_DEBOUNCE_TIME = 250;

interface SetPasswordProps {
redirect?: boolean;
accountGate?: boolean;
Expand All @@ -23,110 +27,115 @@ export const SetPasswordPage: React.FC<SetPasswordProps> = ({
accountGate,
placeholder,
}) => {
const ref = useRef<HTMLInputElement | null>(null);
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [strengthResult, setStrengthResult] = useState(blankPasswordValidation);
const [hasSubmitted, setHasSubmitted] = useState(false);
const { doSetPassword, wallet, doFinishSignIn } = useWallet();
const doChangeScreen = useDoChangeScreen();
const { decodedAuthRequest } = useOnboardingState();

const showWarning = !strengthResult.meetsAllStrengthRequirements && hasSubmitted;

const validate = debounce((value: string) => {
const result = validatePassword(value);
setStrengthResult(result);
ref?.current?.focus();
}, 100);

const handlePasswordInput = useCallback(
(e: React.FormEvent<HTMLInputElement>) => {
const value = e.currentTarget.value;
setPassword(value);
validate(value);
const submit = useCallback(
async (password: string) => {
if (!wallet) throw 'Please log in before setting a password.';
setLoading(true);
await doSetPassword(password);
if (accountGate) return;
if (decodedAuthRequest) {
const { accounts } = wallet;
if (accounts && (accounts.length > 1 || accounts[0].username)) {
doChangeScreen(ScreenPaths.CHOOSE_ACCOUNT);
} else if (!USERNAMES_ENABLED) {
await doFinishSignIn(0);
} else {
doChangeScreen(ScreenPaths.USERNAME);
}
} else if (redirect) {
doChangeScreen(ScreenPaths.INSTALLED);
}
},
[validate]
[
doSetPassword,
doChangeScreen,
redirect,
decodedAuthRequest,
wallet,
doFinishSignIn,
accountGate,
]
);

const submit = useCallback(async () => {
if (!wallet) throw 'Please log in before setting a password.';
setLoading(true);
await doSetPassword(password);
if (accountGate) return;
if (decodedAuthRequest) {
const { accounts } = wallet;
if (accounts && (accounts.length > 1 || accounts[0].username)) {
doChangeScreen(ScreenPaths.CHOOSE_ACCOUNT);
} else if (!USERNAMES_ENABLED) {
await doFinishSignIn(0);
} else {
doChangeScreen(ScreenPaths.USERNAME);
const handleSubmit = useCallback(
async ({ password }) => {
if (!password) return;
setLoading(true);
if (strengthResult.meetsAllStrengthRequirements) {
await submit(password);
return;
}
} else if (redirect) {
doChangeScreen(ScreenPaths.INSTALLED);
}
}, [
doSetPassword,
doChangeScreen,
password,
redirect,
decodedAuthRequest,
wallet,
doFinishSignIn,
accountGate,
]);
setLoading(false);
},
[strengthResult, submit]
);

const handleSubmit = useCallback(async () => {
if (!password) return;
setLoading(true);
setHasSubmitted(true);
const result = validatePassword(password);
setStrengthResult(result);
if (result.meetsAllStrengthRequirements) {
await submit();
return;
}
setLoading(false);
}, [password, submit]);
const validationSchema = yup.object({
password: yup
.string()
.defined()
.test({
message: 'Use a stronger password',
test: debounce((value: unknown) => {
if (typeof value !== 'string') return false;
const result = validatePassword(value);
setStrengthResult(result);
console.log(result);
return result.meetsAllStrengthRequirements;
}, HUMAN_REACTION_DEBOUNCE_TIME),
}),
});

return (
<PopupContainer header={<Header hideActions title="Set a password." />}>
<Stack spacing="loose">
<Body className="onboarding-text">
This password is for this device only. To access your account on a new device you will use
your Secret Key.
</Body>
<Stack spacing="loose" width="100%">
{showWarning ? (
<Caption fontSize={0}>
Please use a stronger password before continuing. Longer than 12 characters, with
symbols, numbers, and words.
</Caption>
) : null}
<Input
ref={ref}
placeholder={placeholder || 'Set a password'}
key="password-input"
width="100%"
autoFocus
type="password"
data-test="set-password"
onChange={handlePasswordInput}
onKeyUp={buildEnterKeyEvent(handleSubmit)}
/>

<Button
width="100%"
isLoading={loading}
isDisabled={loading || showWarning}
onClick={handleSubmit}
data-test="set-password-done"
>
Done
</Button>
</Stack>
</Stack>
</PopupContainer>
<Formik
initialValues={{ password: '' }}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{formik => (
<Form>
<PopupContainer header={<Header hideActions title="Set a password" />}>
<Stack spacing="loose">
<Body className="onboarding-text">
This password is for this device only. To access your account on a new device you
will use your Secret Key.
</Body>
<Stack spacing="loose" width="100%">
{formik.submitCount && !strengthResult.meetsAllStrengthRequirements ? (
<Caption fontSize={0}>
Please use a stronger password before continuing. Longer than 12 characters,
with symbols, numbers, and words.
</Caption>
) : null}
<Input
name="password"
placeholder={placeholder || 'Set a password'}
key="password-input"
width="100%"
type="password"
data-test="set-password"
onChange={formik.handleChange}
/>
<Button
type="submit"
width="100%"
isLoading={loading}
isDisabled={loading}
data-test="set-password-done"
>
Done
</Button>
</Stack>
</Stack>
</PopupContainer>
</Form>
)}
</Formik>
);
};
2 changes: 1 addition & 1 deletion webpack/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ config.optimization = {
: {}),
};

config.devtool = false;
config.devtool = 'source-map';

module.exports = config;
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11690,7 +11690,7 @@ junk@^1.0.2:
resolved "https://registry.yarnpkg.com/junk/-/junk-1.0.3.tgz#87be63488649cbdca6f53ab39bec9ccd2347f592"
integrity sha1-h75jSIZJy9ym9Tqzm+yczSNH9ZI=

just-debounce-it@*, [email protected]:
just-debounce-it@*:
version "1.5.0"
resolved "https://registry.yarnpkg.com/just-debounce-it/-/just-debounce-it-1.5.0.tgz#2276448332dd5925e825ba3c524a71da707bf628"
integrity sha512-itSWJS5d2DTSCizVJ2Z0Djx/dGmUGfZe7WNfUfVP23+htGcIcPHbEjL4eB8ljojTs/+oYwLexImRRCP0A2WXjA==
Expand Down Expand Up @@ -16146,6 +16146,11 @@ triplesec@^3.0.27:
progress "~1.1.2"
uglify-js "^3.1.9"

[email protected]:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-3.0.0.tgz#9beedf59c04de3b5bef8ff28bd6885624df357be"
integrity sha512-7jiRWgN4/8IdvCxbIwnwg2W0bbYFBH6BxFqBjMKk442t7+liF2Z1H6AUCcl8e/pD93GjPru+axeiJwFmRww1WQ==

[email protected]:
version "26.5.6"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35"
Expand Down

0 comments on commit 71524bf

Please sign in to comment.