From 4b2409054723b9b4d5f9d2269884429fa021054c Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Sat, 31 Dec 2022 14:09:53 +0100 Subject: [PATCH] Various registration UX improvements #238 --- .../src/components/RegisterSignIn.tsx | 5 +- data-browser/src/helpers/handlers.tsx | 2 + data-browser/src/routes/ConfirmEmail.tsx | 79 +++++++++++++------ data-browser/src/views/ErrorPage.tsx | 3 + lib/src/authentication.ts | 29 ++++--- 5 files changed, 82 insertions(+), 36 deletions(-) diff --git a/data-browser/src/components/RegisterSignIn.tsx b/data-browser/src/components/RegisterSignIn.tsx index 1697dea71..34da36fae 100644 --- a/data-browser/src/components/RegisterSignIn.tsx +++ b/data-browser/src/components/RegisterSignIn.tsx @@ -151,7 +151,6 @@ function Register({ close }) { -

Lost your passphrase?

+ +

Lost your passphrase?

+
); } diff --git a/data-browser/src/helpers/handlers.tsx b/data-browser/src/helpers/handlers.tsx index e2374ee76..06313eee3 100644 --- a/data-browser/src/helpers/handlers.tsx +++ b/data-browser/src/helpers/handlers.tsx @@ -3,9 +3,11 @@ import BugsnagPluginReact, { BugsnagErrorBoundary, } from '@bugsnag/plugin-react'; import React from 'react'; +import { toast } from 'react-hot-toast'; import { isDev } from '../config'; export function handleError(e: Error): void { + toast.error(e.message); console.error(e); if (!isDev) { diff --git a/data-browser/src/routes/ConfirmEmail.tsx b/data-browser/src/routes/ConfirmEmail.tsx index f73291d98..23ca32e7f 100644 --- a/data-browser/src/routes/ConfirmEmail.tsx +++ b/data-browser/src/routes/ConfirmEmail.tsx @@ -1,11 +1,12 @@ import { confirmEmail, useStore } from '@tomic/react'; import * as React from 'react'; import { useState } from 'react'; -import { CodeBlock } from '../components/CodeBlock'; +import toast from 'react-hot-toast'; +import { Button } from '../components/Button'; +import { CodeBlockStyled } from '../components/CodeBlock'; import { ContainerNarrow } from '../components/Containers'; import { isDev } from '../config'; import { useSettings } from '../helpers/AppSettings'; -import { handleError } from '../helpers/handlers'; import { useCurrentSubject, useSubjectParam, @@ -19,10 +20,13 @@ const ConfirmEmail: React.FunctionComponent = () => { const [secret, setSecret] = useState(''); const store = useStore(); const [token] = useSubjectParam('token'); - const { agent, setAgent } = useSettings(); + const { setAgent } = useSettings(); const [destinationToGo, setDestination] = useState(); + const [err, setErr] = useState(undefined); + const [triedConfirm, setTriedConfirm] = useState(false); - const handleConfirm = async () => { + const handleConfirm = React.useCallback(async () => { + setTriedConfirm(true); let tokenUrl = subject as string; if (isDev()) { @@ -37,37 +41,68 @@ const ConfirmEmail: React.FunctionComponent = () => { store, tokenUrl, ); - setAgent(newAgent); setSecret(newAgent.buildSecret()); setDestination(destination); + setAgent(newAgent); + toast.success('Email confirmed!'); } catch (e) { - handleError(e); + setErr(e); + } + }, [subject]); + + if (!triedConfirm && subject) { + handleConfirm(); + } + + if (err) { + if (err.message.includes('expired')) { + return ( + + The link has expired. Request a new one by Registering again. + + ); } - }; - if (!agent) { - return ( - - - - ); + return {err?.message}; + } + + if (secret) { + return ; + } + + return Verifying token...; +}; + +function SavePassphrase({ secret, destination }) { + const [copied, setCopied] = useState(false); + + function copyToClipboard() { + setCopied(secret); + navigator.clipboard.writeText(secret || ''); + toast.success('Copied to clipboard'); } return ( -

Save your Passphrase

+

Mail confirmed, please save your passphrase

Your Passphrase is like your password. Never share it with anyone. Use a - password manager to store it securely. You will need this to log in - next! + password manager like{' '} + + BitWarden + {' '} + to store it securely.

- - {/* */} - - Open my new Drive! - + {secret} + {copied ? ( + + {"I've saved my PassPhrase, open my new Drive!"} + + ) : ( + + )}
); -}; +} export default ConfirmEmail; diff --git a/data-browser/src/views/ErrorPage.tsx b/data-browser/src/views/ErrorPage.tsx index 7942f09f1..644ecc19a 100644 --- a/data-browser/src/views/ErrorPage.tsx +++ b/data-browser/src/views/ErrorPage.tsx @@ -24,6 +24,9 @@ function ErrorPage({ resource }: ResourcePageProps): JSX.Element { }, [agent]); if (isUnauthorized(resource.error)) { + // This might be a bit too aggressive, but it fixes 'Unauthorized' messages after signing in to a new drive. + store.fetchResource(subject); + return ( diff --git a/lib/src/authentication.ts b/lib/src/authentication.ts index c0beeb2f4..a894fe12f 100644 --- a/lib/src/authentication.ts +++ b/lib/src/authentication.ts @@ -152,6 +152,7 @@ export async function register( * If there is no agent in the store, a new one will be created. */ export async function confirmEmail( store: Store, + /** Full http URL including the `token` query parameter */ tokenURL: string, ): Promise<{ agent: Agent; destination: string }> { const url = new URL(tokenURL); @@ -199,17 +200,21 @@ export async function confirmEmail( } function parseJwt(token) { - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const jsonPayload = decodeURIComponent( - window - .atob(base64) - .split('') - .map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }) - .join(''), - ); + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + window + .atob(base64) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join(''), + ); - return JSON.parse(jsonPayload); + return JSON.parse(jsonPayload); + } catch (e) { + throw new Error('Invalid token: ' + e); + } }