add captcha component
This commit is contained in:
parent
375fcb38b4
commit
1b8c335cac
|
|
@ -31,6 +31,7 @@
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-draggable": "^4.2.0",
|
"react-draggable": "^4.2.0",
|
||||||
"react-hot-loader": "^4.12.18",
|
"react-hot-loader": "^4.12.18",
|
||||||
|
"reaptcha": "^1.7.2",
|
||||||
"rxjs": "^6.5.3",
|
"rxjs": "^6.5.3",
|
||||||
"skinview-utils": "^0.2.1",
|
"skinview-utils": "^0.2.1",
|
||||||
"skinview3d": "^1.2.1",
|
"skinview3d": "^1.2.1",
|
||||||
|
|
|
||||||
3
resources/assets/src/components/Captcha.module.scss
Normal file
3
resources/assets/src/components/Captcha.module.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.captcha {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
102
resources/assets/src/components/Captcha.tsx
Normal file
102
resources/assets/src/components/Captcha.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Reaptcha from 'reaptcha'
|
||||||
|
import { emit, on } from '@/scripts/event'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import styles from './Captcha.module.scss'
|
||||||
|
|
||||||
|
const eventId = Symbol()
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
value: string
|
||||||
|
time: number
|
||||||
|
sitekey: string
|
||||||
|
invisible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class Captcha extends React.Component<{}, State> {
|
||||||
|
state: State
|
||||||
|
ref: React.MutableRefObject<Reaptcha | null>
|
||||||
|
|
||||||
|
constructor(props: {}) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
value: '',
|
||||||
|
time: Date.now(),
|
||||||
|
sitekey: blessing.extra.recaptcha,
|
||||||
|
invisible: blessing.extra.invisible,
|
||||||
|
}
|
||||||
|
this.ref = React.createRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
execute = async () => {
|
||||||
|
const recaptcha = this.ref.current
|
||||||
|
if (recaptcha && this.state.invisible) {
|
||||||
|
return new Promise<string>(resolve => {
|
||||||
|
const off = on(eventId, (value: string) => {
|
||||||
|
resolve(value)
|
||||||
|
off()
|
||||||
|
})
|
||||||
|
recaptcha.execute()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.state.value
|
||||||
|
}
|
||||||
|
|
||||||
|
reset = () => {
|
||||||
|
const recaptcha = this.ref.current
|
||||||
|
if (recaptcha) {
|
||||||
|
recaptcha.reset()
|
||||||
|
} else {
|
||||||
|
this.setState({ time: Date.now() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ value: event.target.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVerify = (value: string) => {
|
||||||
|
emit(eventId, value)
|
||||||
|
this.setState({ value })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRefresh = () => {
|
||||||
|
this.setState({ time: Date.now() })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.state.sitekey ? (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Reaptcha
|
||||||
|
ref={this.ref}
|
||||||
|
sitekey={this.state.sitekey}
|
||||||
|
size={this.state.invisible ? 'invisible' : 'normal'}
|
||||||
|
onVerify={this.handleVerify}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex">
|
||||||
|
<div className="form-group mb-3 mr-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('auth.captcha')}
|
||||||
|
required
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.handleValueChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src={`${blessing.base_url}/auth/captcha?v=${this.state.time}`}
|
||||||
|
alt={t('auth.captcha')}
|
||||||
|
className={styles.captcha}
|
||||||
|
height={34}
|
||||||
|
title={t('auth.change-captcha')}
|
||||||
|
onClick={this.handleRefresh}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Captcha
|
||||||
16
resources/assets/tests/__mocks__/reaptcha.tsx
Normal file
16
resources/assets/tests/__mocks__/reaptcha.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react'
|
||||||
|
import type { ReaptchaProps } from 'reaptcha'
|
||||||
|
|
||||||
|
class Reaptcha extends React.Component<ReaptchaProps, {}> {
|
||||||
|
execute() {
|
||||||
|
this.props.onVerify('token')
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Reaptcha
|
||||||
65
resources/assets/tests/components/Captcha.test.tsx
Normal file
65
resources/assets/tests/components/Captcha.test.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
|
import Reaptcha from 'reaptcha'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import Captcha from '@/components/Captcha'
|
||||||
|
|
||||||
|
describe('picture captcha', () => {
|
||||||
|
it('retrieve value', async () => {
|
||||||
|
const ref = React.createRef<Captcha>()
|
||||||
|
const { getByPlaceholderText } = render(<Captcha ref={ref} />)
|
||||||
|
|
||||||
|
fireEvent.input(getByPlaceholderText(t('auth.captcha')), {
|
||||||
|
target: { value: 'abc' },
|
||||||
|
})
|
||||||
|
expect(await ref.current?.execute()).toBe('abc')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refresh on click', async () => {
|
||||||
|
const spy = jest.spyOn(Date, 'now')
|
||||||
|
|
||||||
|
const ref = React.createRef<Captcha>()
|
||||||
|
const { getByAltText } = render(<Captcha ref={ref} />)
|
||||||
|
|
||||||
|
fireEvent.click(getByAltText(t('auth.captcha')))
|
||||||
|
expect(spy).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refresh programatically', async () => {
|
||||||
|
const spy = jest.spyOn(Date, 'now')
|
||||||
|
|
||||||
|
const ref = React.createRef<Captcha>()
|
||||||
|
render(<Captcha ref={ref} />)
|
||||||
|
|
||||||
|
ref.current?.reset()
|
||||||
|
expect(spy).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('recaptcha', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
window.blessing.extra = { recaptcha: 'sitekey', invisible: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
it('retrieve value', async () => {
|
||||||
|
window.blessing.extra.invisible = true
|
||||||
|
const spy = jest.spyOn(Reaptcha.prototype, 'execute')
|
||||||
|
|
||||||
|
const ref = React.createRef<Captcha>()
|
||||||
|
render(<Captcha ref={ref} />)
|
||||||
|
|
||||||
|
const value = await ref.current?.execute()
|
||||||
|
expect(spy).toBeCalled()
|
||||||
|
expect(value).toBe('token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refresh programatically', async () => {
|
||||||
|
const spy = jest.spyOn(Reaptcha.prototype, 'reset')
|
||||||
|
|
||||||
|
const ref = React.createRef<Captcha>()
|
||||||
|
render(<Captcha ref={ref} />)
|
||||||
|
|
||||||
|
ref.current?.reset()
|
||||||
|
expect(spy).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -8571,6 +8571,11 @@ realpath-native@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
util.promisify "^1.0.0"
|
util.promisify "^1.0.0"
|
||||||
|
|
||||||
|
reaptcha@^1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/reaptcha/-/reaptcha-1.7.2.tgz#d829f54270c241f46501e92a5a7badeb1fcf372d"
|
||||||
|
integrity sha512-/RXiPeMd+fPUGByv+kAaQlCXCsSflZ9bKX5Fcwv9IYGS1oyT2nntL/8zn9IaiUFHL66T1jBtOABcb92g2+3w8w==
|
||||||
|
|
||||||
redent@^3.0.0:
|
redent@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user