Add support for password reset links
This commit is contained in:
parent
54cfe7e981
commit
4eeec58c59
|
@ -10,6 +10,7 @@
|
||||||
"feather-icons": "^4.10.0",
|
"feather-icons": "^4.10.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
|
"query-string": "^6.7.0",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-hot-loader": "^4.9.0",
|
"react-hot-loader": "^4.9.0",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"@types/classnames": "^2.2.8",
|
"@types/classnames": "^2.2.8",
|
||||||
"@types/feather-icons": "^4.7.0",
|
"@types/feather-icons": "^4.7.0",
|
||||||
"@types/lodash": "^4.14.119",
|
"@types/lodash": "^4.14.119",
|
||||||
|
"@types/query-string": "^6.3.0",
|
||||||
"@types/react": "^16.8.19",
|
"@types/react": "^16.8.19",
|
||||||
"@types/react-dom": "^16.8.4",
|
"@types/react-dom": "^16.8.4",
|
||||||
"@types/react-router-dom": "^4.3.3",
|
"@types/react-router-dom": "^4.3.3",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.login-box {
|
.login-box {
|
||||||
@apply .bg-white .shadow-lg .rounded-lg .pt-10 .px-8 .pb-6 .mb-4;
|
@apply .bg-white .shadow-lg .rounded-lg .p-6;
|
||||||
|
|
||||||
@screen xsx {
|
@screen xsx {
|
||||||
@apply .rounded-none;
|
@apply .rounded-none;
|
||||||
|
|
|
@ -17,11 +17,11 @@ input[type=number] {
|
||||||
* is input and then sinks back down into the field if left empty.
|
* is input and then sinks back down into the field if left empty.
|
||||||
*/
|
*/
|
||||||
.input-open {
|
.input-open {
|
||||||
@apply .w-full .px-3 .relative;
|
@apply .w-full .relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-open > .input {
|
.input-open > .input, .input-open > .input:disabled {
|
||||||
@apply .appearance-none .block .w-full .text-neutral-800 .border-b-2 .border-neutral-200 .py-3 .mb-3;
|
@apply .appearance-none .block .w-full .text-neutral-800 .border-b-2 .border-neutral-200 .py-3 .px-2 .bg-white;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@apply .border-primary-400;
|
@apply .border-primary-400;
|
||||||
|
@ -40,9 +40,9 @@ input[type=number] {
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-open > label {
|
.input-open > label {
|
||||||
@apply .block .uppercase .tracking-wide .text-neutral-500 .text-xs .mb-2 .absolute;
|
@apply .block .uppercase .tracking-wide .text-neutral-500 .text-xs .mb-2 .px-2 .absolute;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
transition: transform 200ms ease-out;
|
transition: padding 200ms linear, transform 200ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import http from '@/api/http';
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
token: string;
|
||||||
|
password: string;
|
||||||
|
passwordConfirmation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PasswordResetResponse {
|
||||||
|
redirectTo?: string | null;
|
||||||
|
sendToLogin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (email: string, data: Data): Promise<PasswordResetResponse> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.post('/auth/password/reset', {
|
||||||
|
email,
|
||||||
|
token: data.token,
|
||||||
|
password: data.password,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||||
|
password_confirmation: data.passwordConfirmation,
|
||||||
|
})
|
||||||
|
.then(response => resolve({
|
||||||
|
redirectTo: response.data.redirect_to,
|
||||||
|
sendToLogin: response.data.send_to_login,
|
||||||
|
}))
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||||
import MessageBox from '@/components/MessageBox';
|
import MessageBox from '@/components/MessageBox';
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
|
spacerClass?: string;
|
||||||
flashes: FlashMessage[];
|
flashes: FlashMessage[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -16,18 +17,17 @@ class FlashMessageRender extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
this.props.flashes.map(flash => (
|
this.props.flashes.map((flash, index) => (
|
||||||
<MessageBox
|
<React.Fragment key={flash.id || flash.type + flash.message}>
|
||||||
key={flash.id || flash.type + flash.message}
|
{index > 0 && <div className={this.props.spacerClass || 'mt-2'}></div>}
|
||||||
type={flash.type}
|
<MessageBox type={flash.type} title={flash.title}>
|
||||||
title={flash.title}
|
{flash.message}
|
||||||
>
|
</MessageBox>
|
||||||
{flash.message}
|
</React.Fragment>
|
||||||
</MessageBox>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import MessageBox from '@/components/MessageBox';
|
||||||
|
|
||||||
|
export default ({ message }: { message: string | undefined | null }) => (
|
||||||
|
!message ?
|
||||||
|
null
|
||||||
|
:
|
||||||
|
<div className={'mb-4'}>
|
||||||
|
<MessageBox type={'error'} title={'Error'}>
|
||||||
|
{message}
|
||||||
|
</MessageBox>
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -58,9 +58,12 @@ class ForgotPasswordContainer extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<div>
|
||||||
|
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||||
|
Request Password Reset
|
||||||
|
</h2>
|
||||||
<form className={'login-box'} onSubmit={this.handleSubmission}>
|
<form className={'login-box'} onSubmit={this.handleSubmission}>
|
||||||
<div className={'-mx-3'}>
|
<div className={'mt-3'}>
|
||||||
<OpenInputField
|
<OpenInputField
|
||||||
ref={this.emailField}
|
ref={this.emailField}
|
||||||
id={'email'}
|
id={'email'}
|
||||||
|
@ -93,18 +96,14 @@ class ForgotPasswordContainer extends React.PureComponent<Props, State> {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</React.Fragment>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: ReduxState) => ({
|
|
||||||
flashes: state.flashes,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
pushFlashMessage,
|
pushFlashMessage,
|
||||||
clearAllFlashMessages,
|
clearAllFlashMessages,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ForgotPasswordContainer);
|
export default connect(null, mapDispatchToProps)(ForgotPasswordContainer);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import OpenInputField from '@/components/forms/OpenInputField';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import login from '@/api/auth/login';
|
import login from '@/api/auth/login';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
import MessageBox from '@/components/MessageBox';
|
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||||
|
|
||||||
type State = Readonly<{
|
type State = Readonly<{
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
@ -52,15 +52,12 @@ export default class LoginContainer extends React.PureComponent<{}, State> {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.state.errorMessage &&
|
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||||
<div className={'mb-4'}>
|
Login to Continue
|
||||||
<MessageBox type={'error'} title={'Error'}>
|
</h2>
|
||||||
{this.state.errorMessage}
|
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||||
</MessageBox>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<form className={'login-box'} onSubmit={this.submit}>
|
<form className={'login-box'} onSubmit={this.submit}>
|
||||||
<div className={'-mx-3'}>
|
<div className={'mt-3'}>
|
||||||
<OpenInputField
|
<OpenInputField
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
label={'Username or Email'}
|
label={'Username or Email'}
|
||||||
|
@ -71,7 +68,7 @@ export default class LoginContainer extends React.PureComponent<{}, State> {
|
||||||
disabled={this.state.isLoading}
|
disabled={this.state.isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'-mx-3 mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<OpenInputField
|
<OpenInputField
|
||||||
label={'Password'}
|
label={'Password'}
|
||||||
type={'password'}
|
type={'password'}
|
||||||
|
@ -96,7 +93,7 @@ export default class LoginContainer extends React.PureComponent<{}, State> {
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6 text-center'}>
|
<div className={'mt-6 text-center'}>
|
||||||
<Link
|
<Link
|
||||||
to={'/forgot-password'}
|
to={'/password'}
|
||||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import OpenInputField from '@/components/forms/OpenInputField';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import { parse } from 'query-string';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import NetworkErrorMessage from '@/components/NetworkErrorMessage';
|
||||||
|
import performPasswordReset from '@/api/auth/performPasswordReset';
|
||||||
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { pushFlashMessage, clearAllFlashMessages } from '@/redux/actions/flash';
|
||||||
|
|
||||||
|
type State = Readonly<{
|
||||||
|
email?: string;
|
||||||
|
password?: string;
|
||||||
|
passwordConfirm?: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
errorMessage?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type Props = Readonly<RouteComponentProps<{ token: string }> & {
|
||||||
|
pushFlashMessage: typeof pushFlashMessage;
|
||||||
|
clearAllFlashMessages: typeof clearAllFlashMessages;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
class ResetPasswordContainer extends React.PureComponent<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const parsed = parse(this.props.location.search);
|
||||||
|
|
||||||
|
this.setState({ email: parsed.email as string || undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
canSubmit () {
|
||||||
|
if (!this.state.password || !this.state.email) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.password.length >= 8 && this.state.password === this.state.passwordConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||||
|
password: e.target.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
onPasswordConfirmChange = (e: React.ChangeEvent<HTMLInputElement>) => this.setState({
|
||||||
|
passwordConfirm: e.target.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const { password, passwordConfirm, email } = this.state;
|
||||||
|
if (!password || !email || !passwordConfirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.clearAllFlashMessages();
|
||||||
|
this.setState({ isLoading: true }, () => {
|
||||||
|
performPasswordReset(email, {
|
||||||
|
token: this.props.match.params.token,
|
||||||
|
password: password,
|
||||||
|
passwordConfirmation: passwordConfirm,
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.redirectTo) {
|
||||||
|
// @ts-ignore
|
||||||
|
window.location = response.redirectTo;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.pushFlashMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Your password has been reset, please login to continue.',
|
||||||
|
});
|
||||||
|
this.props.history.push('/login');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
this.setState({ errorMessage: httpErrorToHuman(error) });
|
||||||
|
})
|
||||||
|
.then(() => this.setState({ isLoading: false }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className={'text-center text-neutral-100 font-medium py-4'}>
|
||||||
|
Reset Password
|
||||||
|
</h2>
|
||||||
|
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||||
|
<form className={'login-box'} onSubmit={this.onSubmit}>
|
||||||
|
<div className={'mt-3'}>
|
||||||
|
<OpenInputField
|
||||||
|
label={'Email'}
|
||||||
|
value={this.state.email || ''}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-6'}>
|
||||||
|
<OpenInputField
|
||||||
|
autoFocus={true}
|
||||||
|
label={'New Password'}
|
||||||
|
description={'Passwords must be at least 8 characters in length.'}
|
||||||
|
type={'password'}
|
||||||
|
required={true}
|
||||||
|
id={'password'}
|
||||||
|
onChange={this.onPasswordChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-6'}>
|
||||||
|
<OpenInputField
|
||||||
|
label={'Confirm New Password'}
|
||||||
|
type={'password'}
|
||||||
|
required={true}
|
||||||
|
id={'password-confirm'}
|
||||||
|
onChange={this.onPasswordConfirmChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-6'}>
|
||||||
|
<button
|
||||||
|
type={'submit'}
|
||||||
|
className={'btn btn-primary btn-jumbo'}
|
||||||
|
disabled={this.state.isLoading || !this.canSubmit()}
|
||||||
|
>
|
||||||
|
{this.state.isLoading ?
|
||||||
|
<span className={'spinner white'}> </span>
|
||||||
|
:
|
||||||
|
'Reset Password'
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={'mt-6 text-center'}>
|
||||||
|
<Link
|
||||||
|
to={'/login'}
|
||||||
|
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
||||||
|
>
|
||||||
|
Return to Login
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
pushFlashMessage,
|
||||||
|
clearAllFlashMessages,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(ResetPasswordContainer);
|
|
@ -4,13 +4,18 @@ import classNames from 'classnames';
|
||||||
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
|
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.forwardRef<HTMLInputElement, Props>(({ className, description, onChange, label, ...props }, ref) => {
|
export default React.forwardRef<HTMLInputElement, Props>(({ className, description, onChange, label, value, ...props }, ref) => {
|
||||||
const [ value, setValue ] = React.useState('');
|
const [ stateValue, setStateValue ] = React.useState(value);
|
||||||
|
|
||||||
|
if (value !== stateValue) {
|
||||||
|
setStateValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
const classes = classNames('input open-label', {
|
const classes = classNames('input open-label', {
|
||||||
'has-content': value && value.length > 0,
|
'has-content': stateValue && stateValue.length > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -19,16 +24,17 @@ export default React.forwardRef<HTMLInputElement, Props>(({ className, descripti
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classes}
|
className={classes}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setValue(e.target.value);
|
setStateValue(e.target.value);
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(e);
|
onChange(e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
value={typeof value !== 'undefined' ? (stateValue || '') : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={props.id}>{label}</label>
|
<label htmlFor={props.id}>{label}</label>
|
||||||
{description &&
|
{description &&
|
||||||
<p className={'text-xs text-neutral-500'}>
|
<p className={'mt-2 text-xs text-neutral-500'}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import LoginContainer from '@/components/auth/LoginContainer';
|
||||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||||
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
|
import ForgotPasswordContainer from '@/components/auth/ForgotPasswordContainer';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import ResetPasswordContainer from '@/components/auth/ResetPasswordContainer';
|
||||||
|
|
||||||
export default class AuthenticationRouter extends React.PureComponent {
|
export default class AuthenticationRouter extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
|
@ -14,12 +15,11 @@ export default class AuthenticationRouter extends React.PureComponent {
|
||||||
<TransitionGroup className={'route-transition-group mt-32'}>
|
<TransitionGroup className={'route-transition-group mt-32'}>
|
||||||
<CSSTransition key={location.key} timeout={150} classNames={'fade'}>
|
<CSSTransition key={location.key} timeout={150} classNames={'fade'}>
|
||||||
<section>
|
<section>
|
||||||
<div className={'mb-2'}>
|
<FlashMessageRender/>
|
||||||
<FlashMessageRender/>
|
|
||||||
</div>
|
|
||||||
<Switch location={location}>
|
<Switch location={location}>
|
||||||
<Route path={'/login'} component={LoginContainer}/>
|
<Route path={'/login'} component={LoginContainer}/>
|
||||||
<Route path={'/forgot-password'} component={ForgotPasswordContainer}/>
|
<Route path={'/password'} component={ForgotPasswordContainer} exact/>
|
||||||
|
<Route path={'/password/reset/:token'} component={ResetPasswordContainer}/>
|
||||||
<Route path={'/checkpoint'}/>
|
<Route path={'/checkpoint'}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
<p className={'text-center text-neutral-500 text-xs'}>
|
<p className={'text-center text-neutral-500 text-xs'}>
|
||||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -766,6 +766,12 @@
|
||||||
version "15.7.1"
|
version "15.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6"
|
||||||
|
|
||||||
|
"@types/query-string@^6.3.0":
|
||||||
|
version "6.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/query-string/-/query-string-6.3.0.tgz#b6fa172a01405abcaedac681118e78429d62ea39"
|
||||||
|
dependencies:
|
||||||
|
query-string "*"
|
||||||
|
|
||||||
"@types/react-dom@^16.8.4":
|
"@types/react-dom@^16.8.4":
|
||||||
version "16.8.4"
|
version "16.8.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.4.tgz#7fb7ba368857c7aa0f4e4511c4710ca2c5a12a88"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.4.tgz#7fb7ba368857c7aa0f4e4511c4710ca2c5a12a88"
|
||||||
|
@ -6064,6 +6070,14 @@ qs@6.5.2:
|
||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
query-string@*, query-string@^6.7.0:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.7.0.tgz#7e92bf8525140cf8c5ebf500f26716b0de5b7023"
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component "^0.2.0"
|
||||||
|
split-on-first "^1.0.0"
|
||||||
|
strict-uri-encode "^2.0.0"
|
||||||
|
|
||||||
querystring-es3@^0.2.0:
|
querystring-es3@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||||
|
@ -6918,6 +6932,10 @@ spdy@^4.0.0:
|
||||||
select-hose "^2.0.0"
|
select-hose "^2.0.0"
|
||||||
spdy-transport "^3.0.0"
|
spdy-transport "^3.0.0"
|
||||||
|
|
||||||
|
split-on-first@^1.0.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
|
||||||
|
|
||||||
split-string@^3.0.1, split-string@^3.0.2:
|
split-string@^3.0.1, split-string@^3.0.2:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
|
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
|
||||||
|
@ -6987,6 +7005,10 @@ stream-shift@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
|
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
|
||||||
|
|
||||||
|
strict-uri-encode@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||||
|
|
||||||
string-width@^1.0.1:
|
string-width@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||||
|
|
Loading…
Reference in New Issue