Skip to content

Commit 2f490bf

Browse files
committed
Various registration UX improvements #238
1 parent 59cd594 commit 2f490bf

File tree

5 files changed

+81
-36
lines changed

5 files changed

+81
-36
lines changed

data-browser/src/components/RegisterSignIn.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ function Register({ close }) {
151151
<InputWrapper>
152152
<InputStyled
153153
autoFocus={true}
154-
// pattern={emailRegex}
155154
type={'email'}
156155
required
157156
value={email}
@@ -186,8 +185,10 @@ function SignIn() {
186185
</DialogTitle>
187186
<DialogContent>
188187
<SettingsAgent />
189-
<p>Lost your passphrase?</p>
190188
</DialogContent>
189+
<DialogActions>
190+
<p>Lost your passphrase?</p>
191+
</DialogActions>
191192
</>
192193
);
193194
}

data-browser/src/helpers/loggingHandlers.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import BugsnagPluginReact, {
33
BugsnagErrorBoundary,
44
} from '@bugsnag/plugin-react';
55
import React from 'react';
6+
import { toast } from 'react-hot-toast';
67
import { isDev } from '../config';
78

89
export function handleError(e: Error): void {
+57-22
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { confirmEmail, useStore } from '@tomic/react';
22
import * as React from 'react';
33
import { useState } from 'react';
4-
import { CodeBlock } from '../components/CodeBlock';
4+
import toast from 'react-hot-toast';
5+
import { Button } from '../components/Button';
6+
import { CodeBlockStyled } from '../components/CodeBlock';
57
import { ContainerNarrow } from '../components/Containers';
68
import { isDev } from '../config';
79
import { useSettings } from '../helpers/AppSettings';
8-
import { handleError } from '../helpers/handlers';
910
import {
1011
useCurrentSubject,
1112
useSubjectParam,
@@ -19,10 +20,13 @@ const ConfirmEmail: React.FunctionComponent = () => {
1920
const [secret, setSecret] = useState('');
2021
const store = useStore();
2122
const [token] = useSubjectParam('token');
22-
const { agent, setAgent } = useSettings();
23+
const { setAgent } = useSettings();
2324
const [destinationToGo, setDestination] = useState<string>();
25+
const [err, setErr] = useState<Error | undefined>(undefined);
26+
const [triedConfirm, setTriedConfirm] = useState(false);
2427

25-
const handleConfirm = async () => {
28+
const handleConfirm = React.useCallback(async () => {
29+
setTriedConfirm(true);
2630
let tokenUrl = subject as string;
2731

2832
if (isDev()) {
@@ -37,37 +41,68 @@ const ConfirmEmail: React.FunctionComponent = () => {
3741
store,
3842
tokenUrl,
3943
);
40-
setAgent(newAgent);
4144
setSecret(newAgent.buildSecret());
4245
setDestination(destination);
46+
setAgent(newAgent);
47+
toast.success('Email confirmed!');
4348
} catch (e) {
44-
handleError(e);
49+
setErr(e);
50+
}
51+
}, [subject]);
52+
53+
if (!triedConfirm && subject) {
54+
handleConfirm();
55+
}
56+
57+
if (err) {
58+
if (err.message.includes('expired')) {
59+
return (
60+
<ContainerNarrow>
61+
The link has expired. Request a new one by Registering again.
62+
</ContainerNarrow>
63+
);
4564
}
46-
};
4765

48-
if (!agent) {
49-
return (
50-
<ContainerNarrow>
51-
<button onClick={handleConfirm}>confirm</button>
52-
</ContainerNarrow>
53-
);
66+
return <ContainerNarrow>{err?.message}</ContainerNarrow>;
67+
}
68+
69+
if (secret) {
70+
return <SavePassphrase secret={secret} destination={destinationToGo} />;
71+
}
72+
73+
return <ContainerNarrow>Verifying token...</ContainerNarrow>;
74+
};
75+
76+
function SavePassphrase({ secret, destination }) {
77+
const [copied, setCopied] = useState(false);
78+
79+
function copyToClipboard() {
80+
setCopied(secret);
81+
navigator.clipboard.writeText(secret || '');
82+
toast.success('Copied to clipboard');
5483
}
5584

5685
return (
5786
<ContainerNarrow>
58-
<h1>Save your Passphrase</h1>
87+
<h1>Mail confirmed, please save your passphrase</h1>
5988
<p>
6089
Your Passphrase is like your password. Never share it with anyone. Use a
61-
password manager to store it securely. You will need this to log in
62-
next!
90+
password manager like{' '}
91+
<a href='https://bitwarden.com/' target='_blank' rel='noreferrer'>
92+
BitWarden
93+
</a>{' '}
94+
to store it securely.
6395
</p>
64-
<CodeBlock content={secret} wrapContent />
65-
{/* <Button onClick={handleGoToDestination}>Continue here</Button> */}
66-
<a href={destinationToGo} target='_blank' rel='noreferrer'>
67-
Open my new Drive!
68-
</a>
96+
<CodeBlockStyled wrapContent>{secret}</CodeBlockStyled>
97+
{copied ? (
98+
<a href={destination} target='_blank' rel='noreferrer'>
99+
{"I've saved my PassPhrase, open my new Drive!"}
100+
</a>
101+
) : (
102+
<Button onClick={copyToClipboard}>Copy Passphrase</Button>
103+
)}
69104
</ContainerNarrow>
70105
);
71-
};
106+
}
72107

73108
export default ConfirmEmail;

data-browser/src/views/ErrorPage.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ function ErrorPage({ resource }: ResourcePageProps): JSX.Element {
2424
}, [agent]);
2525

2626
if (isUnauthorized(resource.error)) {
27+
// This might be a bit too aggressive, but it fixes 'Unauthorized' messages after signing in to a new drive.
28+
store.fetchResource(subject);
29+
2730
return (
2831
<ContainerWide>
2932
<Column>

lib/src/authentication.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export async function register(
153153
* If there is no agent in the store, a new one will be created. */
154154
export async function confirmEmail(
155155
store: Store,
156+
/** Full http URL including the `token` query parameter */
156157
tokenURL: string,
157158
): Promise<{ agent: Agent; destination: string }> {
158159
const url = new URL(tokenURL);
@@ -204,17 +205,21 @@ export const removeCookieAuthentication = () => {
204205
};
205206

206207
function parseJwt(token) {
207-
const base64Url = token.split('.')[1];
208-
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
209-
const jsonPayload = decodeURIComponent(
210-
window
211-
.atob(base64)
212-
.split('')
213-
.map(function (c) {
214-
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
215-
})
216-
.join(''),
217-
);
208+
try {
209+
const base64Url = token.split('.')[1];
210+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
211+
const jsonPayload = decodeURIComponent(
212+
window
213+
.atob(base64)
214+
.split('')
215+
.map(function (c) {
216+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
217+
})
218+
.join(''),
219+
);
218220

219-
return JSON.parse(jsonPayload);
221+
return JSON.parse(jsonPayload);
222+
} catch (e) {
223+
throw new Error('Invalid token: ' + e);
224+
}
220225
}

0 commit comments

Comments
 (0)