feat: recovery codes UI (recovery sign in + get recovery codes) (#2139)

* feat(web): show recovery codes

* feat(web): add recovery sign in

* fix: copy

* fix: styles

* feat: add "copy to clipboard" button

* style: copy

* fix: copy button bg

* style: singularize recovery codes

* style: singularize recovery codes

* feat: password validation

Co-authored-by: Aman Harwara <amanharwara@protonmail.com>
Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
Karol Sójko
2023-01-10 21:33:44 +01:00
committed by GitHub
parent de3fa476c7
commit 5e6c901c21
16 changed files with 234 additions and 32 deletions

View File

@@ -0,0 +1,63 @@
import { WebApplication } from '@/Application/Application'
import { useState } from 'react'
import Button from '../Button/Button'
import Icon from '../Icon/Icon'
import StyledTooltip from '../StyledTooltip/StyledTooltip'
const RecoveryCodeBanner = ({ application }: { application: WebApplication }) => {
const [recoveryCode, setRecoveryCode] = useState<string>()
const [errorMessage, setErrorMessage] = useState<string>()
const onClickShow = async () => {
const authorized = await application.challenges.promptForAccountPassword()
if (!authorized) {
return
}
const recoveryCodeOrError = await application.getRecoveryCodes.execute()
if (recoveryCodeOrError.isFailed()) {
setErrorMessage(recoveryCodeOrError.getError())
return
}
setRecoveryCode(recoveryCodeOrError.getValue())
}
return (
<div className="grid grid-cols-1 rounded-md border border-border p-4">
<div className="flex items-center">
<Icon className="mr-1 -ml-1 h-5 w-5 text-info group-disabled:text-passive-2" type="asterisk" />
<h1 className="sk-h3 m-0 text-sm font-semibold">Save your recovery code</h1>
</div>
<p className="col-start-1 col-end-3 m-0 mt-1 text-sm">
Your recovery code allows you access to your account in the event you lose your 2FA authenticating device or
app. Save your recovery code in a safe place outside your account.
</p>
{errorMessage && <div>{errorMessage}</div>}
{!recoveryCode && (
<Button primary small className="col-start-1 col-end-3 mt-3 justify-self-start uppercase" onClick={onClickShow}>
Show Recovery Code
</Button>
)}
{recoveryCode && (
<div className="group relative mt-2 rounded border border-border py-2 px-3 text-sm font-semibold">
<StyledTooltip label="Copy to clipboard" className="!z-modal">
<button
className="absolute top-2 right-2 flex rounded border border-border bg-default p-1 opacity-0 hover:bg-contrast focus:opacity-100 group-hover:opacity-100"
onClick={() => {
void navigator.clipboard.writeText(recoveryCode)
}}
>
<Icon type="copy" size="small" />
</button>
</StyledTooltip>
{recoveryCode}
</div>
)}
</div>
)
}
export default RecoveryCodeBanner