Add fancy error effect when user enters wrong password
This commit is contained in:
@ -13,18 +13,19 @@ body {
|
|||||||
font-family: var(--sans-serif);
|
font-family: var(--sans-serif);
|
||||||
}
|
}
|
||||||
:root {
|
:root {
|
||||||
--page-background: #fff;
|
|
||||||
--color: #000;
|
|
||||||
--titlebar-height: 46px;
|
|
||||||
--titlebar-height: 0px;
|
|
||||||
--label-background: #ddd;
|
|
||||||
--selected-background: #d5d5d5;
|
|
||||||
--hover-background: #ddd;
|
|
||||||
--border-color: #e3e3e3;
|
--border-color: #e3e3e3;
|
||||||
--sans-serif: -apple-system, BlinkMacSystemFont, system-ui, "Roboto", "Oxygen",
|
--color: #000;
|
||||||
"Cantarell", "Droid Sans", "Helvetica Neue", "Noto Sans CJK JP", sans-serif;
|
--hover-background: #ddd;
|
||||||
|
--label-background: #ddd;
|
||||||
--monospace: D2Coding, "source-code-pro", Menlo, Monaco, Consolas, "Courier New",
|
--monospace: D2Coding, "source-code-pro", Menlo, Monaco, Consolas, "Courier New",
|
||||||
monospace;
|
monospace;
|
||||||
|
--page-background: #fff;
|
||||||
|
--sans-serif: -apple-system, BlinkMacSystemFont, system-ui, "Roboto", "Oxygen",
|
||||||
|
"Cantarell", "Droid Sans", "Helvetica Neue", "Noto Sans CJK JP", sans-serif;
|
||||||
|
--selected-background: #d5d5d5;
|
||||||
|
--red-border: #fa144d;
|
||||||
|
--titlebar-height: 0px;
|
||||||
|
--titlebar-height: 46px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
@ -33,6 +34,21 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color: #fff;
|
||||||
|
--label-background: #353535;
|
||||||
|
--selected-background: #353535;
|
||||||
|
--border-color: #333;
|
||||||
|
--hover-background: #222;
|
||||||
|
--page-background: #292929;
|
||||||
|
--red-border: #9e1641;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
@ -44,20 +60,6 @@ body,
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color: #fff;
|
|
||||||
--label-background: #353535;
|
|
||||||
--selected-background: #353535;
|
|
||||||
--border-color: #333;
|
|
||||||
--hover-background: #222;
|
|
||||||
--page-background: #292929;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pre,
|
pre,
|
||||||
code {
|
code {
|
||||||
font-family: var(--monospace);
|
font-family: var(--monospace);
|
||||||
|
@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useState } from "react"
|
|||||||
import { IoMdArrowRoundBack } from "react-icons/io"
|
import { IoMdArrowRoundBack } from "react-icons/io"
|
||||||
import { FaUnlock } from "react-icons/fa"
|
import { FaUnlock } from "react-icons/fa"
|
||||||
import { useTranslate } from "../../i18n"
|
import { useTranslate } from "../../i18n"
|
||||||
|
import { useWrongPasswordFeedback } from "../../utils/useWrongPasswordFeedback"
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@ -72,7 +73,7 @@ const VaultPath = styled.div`
|
|||||||
export const Unlock: React.FC<{
|
export const Unlock: React.FC<{
|
||||||
instance: OnePassword
|
instance: OnePassword
|
||||||
vaultPath: string
|
vaultPath: string
|
||||||
onUnlock(profile: string, password: string): void
|
onUnlock(profile: string, password: string): Promise<boolean>
|
||||||
onReturn(): void
|
onReturn(): void
|
||||||
}> = ({ onUnlock, onReturn, instance, vaultPath }) => {
|
}> = ({ onUnlock, onReturn, instance, vaultPath }) => {
|
||||||
const t = useTranslate()
|
const t = useTranslate()
|
||||||
@ -80,12 +81,14 @@ export const Unlock: React.FC<{
|
|||||||
const [profile, setProfile] = useState<string>()
|
const [profile, setProfile] = useState<string>()
|
||||||
const [password, setPassword] = useState("")
|
const [password, setPassword] = useState("")
|
||||||
|
|
||||||
|
const [{ animation, error }, setFeedback] = useWrongPasswordFeedback()
|
||||||
|
|
||||||
const unlock = useCallback(
|
const unlock = useCallback(
|
||||||
(e?: React.FormEvent) => {
|
(e?: React.FormEvent) => {
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
if (!profile) return
|
if (!profile) return
|
||||||
onUnlock(profile, password)
|
onUnlock(profile, password).then(setFeedback)
|
||||||
setPassword("")
|
setPassword("")
|
||||||
},
|
},
|
||||||
[onUnlock, profile, password]
|
[onUnlock, profile, password]
|
||||||
@ -132,12 +135,14 @@ export const Unlock: React.FC<{
|
|||||||
onChange={e => setPassword(e.currentTarget.value)}
|
onChange={e => setPassword(e.currentTarget.value)}
|
||||||
placeholder={t.label.password_placeholder}
|
placeholder={t.label.password_placeholder}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
|
style={{ animation, borderColor: error ? "var(--red-border)" : undefined }}
|
||||||
/>
|
/>
|
||||||
<Submit
|
<Submit
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!profile || !password}
|
disabled={!profile || !password}
|
||||||
onClick={unlock}
|
onClick={unlock}
|
||||||
title={t.action.unlock}
|
title={t.action.unlock}
|
||||||
|
style={{ animation }}
|
||||||
>
|
>
|
||||||
<FaUnlock />
|
<FaUnlock />
|
||||||
</Submit>
|
</Submit>
|
||||||
|
@ -24,8 +24,13 @@ export const VaultPicker: React.FC<VaultPickerProps> = ({
|
|||||||
const unlock = useCallback(
|
const unlock = useCallback(
|
||||||
async (profile: string, password: string) => {
|
async (profile: string, password: string) => {
|
||||||
const vault = await instance!.getProfile(profile!)
|
const vault = await instance!.getProfile(profile!)
|
||||||
await vault.unlock(password)
|
try {
|
||||||
setVault(vault)
|
await vault.unlock(password)
|
||||||
|
setVault(vault)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[instance, setVault]
|
[instance, setVault]
|
||||||
)
|
)
|
||||||
|
48
packages/web/src/utils/useWrongPasswordFeedback.ts
Normal file
48
packages/web/src/utils/useWrongPasswordFeedback.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import styled from "@emotion/styled"
|
||||||
|
import { keyframes } from "@emotion/css"
|
||||||
|
import React, { useCallback, useRef, useState } from "react"
|
||||||
|
|
||||||
|
const shake = keyframes`
|
||||||
|
8%, 41% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
25%, 58% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
92% {
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const animation = `${shake} .5s linear`
|
||||||
|
|
||||||
|
export function useWrongPasswordFeedback() {
|
||||||
|
const timeout = useRef(0)
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
|
const callback = useCallback((success: boolean) => {
|
||||||
|
setShow(!success)
|
||||||
|
if (timeout.current) {
|
||||||
|
window.clearTimeout(timeout.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout.current = window.setTimeout(() => {
|
||||||
|
setShow(false)
|
||||||
|
}, 500)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
animation: show ? animation : undefined,
|
||||||
|
error: show,
|
||||||
|
},
|
||||||
|
callback,
|
||||||
|
] as const
|
||||||
|
}
|
Reference in New Issue
Block a user