Major deps updates: router, redux, intl, react-transition-group, flow-type, testing deps

This commit is contained in:
SleepWalker 2019-06-30 16:32:50 +03:00
parent 6821bcfe40
commit 627d503d43
76 changed files with 2819 additions and 1703 deletions

View File

@ -1,5 +1,5 @@
// flow-typed signature: e68caa23426dedefced5662fb92b4638 // flow-typed signature: 3902298e28ed22d8cd8d49828801a760
// flow-typed version: d13a175635/react-intl_v2.x.x/flow_>=v0.57.x // flow-typed version: eb50783110/react-intl_v2.x.x/flow_>=v0.63.x
/** /**
* Original implementation of this file by @marudor at https://github.com/marudor/flowInterfaces * Original implementation of this file by @marudor at https://github.com/marudor/flowInterfaces
@ -7,112 +7,110 @@
* https://github.com/marudor/flowInterfaces/issues/6 * https://github.com/marudor/flowInterfaces/issues/6
*/ */
// Mostly from https://github.com/yahoo/react-intl/wiki/API#react-intl-api // Mostly from https://github.com/yahoo/react-intl/wiki/API#react-intl-api
import type { Element, ChildrenArray } from "react";
type $npm$ReactIntl$LocaleData = {
locale: string,
[key: string]: any
};
type $npm$ReactIntl$MessageDescriptor = {
id: string,
description?: string,
defaultMessage?: string
};
type $npm$ReactIntl$IntlConfig = {
locale: string,
formats: Object,
messages: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
type $npm$ReactIntl$IntlProviderConfig = {
locale?: string,
formats?: Object,
messages?: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
type $npm$ReactIntl$IntlFormat = {
formatDate: (value: any, options?: Object) => string,
formatTime: (value: any, options?: Object) => string,
formatRelative: (value: any, options?: Object) => string,
formatNumber: (value: any, options?: Object) => string,
formatPlural: (value: any, options?: Object) => string,
formatMessage: (
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
values?: Object
) => string,
formatHTMLMessage: (
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
values?: Object
) => string
};
type $npm$ReactIntl$IntlShape = $npm$ReactIntl$IntlConfig &
$npm$ReactIntl$IntlFormat & { now: () => number };
type $npm$ReactIntl$DateTimeFormatOptions = {
localeMatcher?: "best fit" | "lookup",
formatMatcher?: "basic" | "best fit",
timeZone?: string,
hour12?: boolean,
weekday?: "narrow" | "short" | "long",
era?: "narrow" | "short" | "long",
year?: "numeric" | "2-digit",
month?: "numeric" | "2-digit" | "narrow" | "short" | "long",
day?: "numeric" | "2-digit",
hour?: "numeric" | "2-digit",
minute?: "numeric" | "2-digit",
second?: "numeric" | "2-digit",
timeZoneName?: "short" | "long"
};
type $npm$ReactIntl$RelativeFormatOptions = {
style?: "best fit" | "numeric",
units?: "second" | "minute" | "hour" | "day" | "month" | "year"
};
type $npm$ReactIntl$NumberFormatOptions = {
localeMatcher?: "best fit" | "lookup",
style?: "decimal" | "currency" | "percent",
currency?: string,
currencyDisplay?: "symbol" | "code" | "name",
useGrouping?: boolean,
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
minimumSignificantDigits?: number,
maximumSignificantDigits?: number
};
type $npm$ReactIntl$PluralFormatOptions = {
style?: "cardinal" | "ordinal"
};
type $npm$ReactIntl$PluralCategoryString =
| "zero"
| "one"
| "two"
| "few"
| "many"
| "other";
type $npm$ReactIntl$DateParseable = number | string | Date;
declare module "react-intl" { declare module "react-intl" {
import type { Element, ChildrenArray } from "react";
declare type $npm$ReactIntl$LocaleData = {
locale: string,
[key: string]: any
};
declare type $npm$ReactIntl$MessageDescriptor = {
id: string,
description?: string,
defaultMessage?: string
};
declare type $npm$ReactIntl$IntlConfig = {
locale: string,
formats: Object,
messages: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
declare type $npm$ReactIntl$IntlProviderConfig = {
locale?: string,
formats?: Object,
messages?: { [id: string]: string },
defaultLocale?: string,
defaultFormats?: Object
};
declare type $npm$ReactIntl$IntlFormat = {
formatDate: (value: any, options?: Object) => string,
formatTime: (value: any, options?: Object) => string,
formatRelative: (value: any, options?: Object) => string,
formatNumber: (value: any, options?: Object) => string,
formatPlural: (value: any, options?: Object) => string,
formatMessage: (
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
values?: Object
) => string,
formatHTMLMessage: (
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
values?: Object
) => string
};
declare type $npm$ReactIntl$IntlShape = $npm$ReactIntl$IntlConfig &
$npm$ReactIntl$IntlFormat & { now: () => number };
declare type $npm$ReactIntl$DateTimeFormatOptions = {
localeMatcher?: "best fit" | "lookup",
formatMatcher?: "basic" | "best fit",
timeZone?: string,
hour12?: boolean,
weekday?: "narrow" | "short" | "long",
era?: "narrow" | "short" | "long",
year?: "numeric" | "2-digit",
month?: "numeric" | "2-digit" | "narrow" | "short" | "long",
day?: "numeric" | "2-digit",
hour?: "numeric" | "2-digit",
minute?: "numeric" | "2-digit",
second?: "numeric" | "2-digit",
timeZoneName?: "short" | "long"
};
declare type $npm$ReactIntl$RelativeFormatOptions = {
style?: "best fit" | "numeric",
units?: "second" | "minute" | "hour" | "day" | "month" | "year"
};
declare type $npm$ReactIntl$NumberFormatOptions = {
localeMatcher?: "best fit" | "lookup",
style?: "decimal" | "currency" | "percent",
currency?: string,
currencyDisplay?: "symbol" | "code" | "name",
useGrouping?: boolean,
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
minimumSignificantDigits?: number,
maximumSignificantDigits?: number
};
declare type $npm$ReactIntl$PluralFormatOptions = {
style?: "cardinal" | "ordinal"
};
declare type $npm$ReactIntl$PluralCategoryString =
| "zero"
| "one"
| "two"
| "few"
| "many"
| "other";
declare type $npm$ReactIntl$DateParseable = number | string | Date;
// PropType checker // PropType checker
declare function intlShape( declare function intlShape(
props: Object, props: Object,
@ -123,7 +121,7 @@ declare module "react-intl" {
data: $npm$ReactIntl$LocaleData | Array<$npm$ReactIntl$LocaleData> data: $npm$ReactIntl$LocaleData | Array<$npm$ReactIntl$LocaleData>
): void; ): void;
declare function defineMessages< declare function defineMessages<
T: { [key: string]: $npm$ReactIntl$MessageDescriptor } T: { [key: string]: $Exact<$npm$ReactIntl$MessageDescriptor> }
>( >(
messageDescriptors: T messageDescriptors: T
): T; ): T;
@ -132,6 +130,10 @@ declare module "react-intl" {
intl: $npm$ReactIntl$IntlShape intl: $npm$ReactIntl$IntlShape
} }
declare type InjectIntlVoidProps = {
intl: $npm$ReactIntl$IntlShape | void
}
declare type ComponentWithDefaultProps<DefaultProps: {}, Props: {}> = declare type ComponentWithDefaultProps<DefaultProps: {}, Props: {}> =
| React$ComponentType<Props> | React$ComponentType<Props>
| React$StatelessFunctionalComponent<Props> | React$StatelessFunctionalComponent<Props>
@ -152,19 +154,12 @@ declare module "react-intl" {
IntlInjectedComponent<TOwnProps, TDefaultProps> IntlInjectedComponent<TOwnProps, TDefaultProps>
>; >;
declare function injectIntl<OriginalProps: InjectIntlProvidedProps, DefaultProps: {}> declare function injectIntl<P: {}, Component: React$ComponentType<P>>(
( WrappedComponent: Component,
component: ComponentWithDefaultProps<DefaultProps, OriginalProps>,
options?: InjectIntlOptions, options?: InjectIntlOptions,
): ): React$ComponentType<
IntlInjectedComponentClass<$Diff<OriginalProps, InjectIntlProvidedProps>, DefaultProps> $Diff<React$ElementConfig<Component>, InjectIntlVoidProps>
>;
declare function injectIntl<OriginalProps: InjectIntlProvidedProps>
(
component: React$ComponentType<OriginalProps>,
options?: InjectIntlOptions,
):
IntlInjectedComponentClass<$Diff<OriginalProps, InjectIntlProvidedProps>>;
declare function formatMessage( declare function formatMessage(
messageDescriptor: $npm$ReactIntl$MessageDescriptor, messageDescriptor: $npm$ReactIntl$MessageDescriptor,
@ -202,7 +197,9 @@ declare module "react-intl" {
$npm$ReactIntl$MessageDescriptor & { $npm$ReactIntl$MessageDescriptor & {
values?: Object, values?: Object,
tagName?: string, tagName?: string,
children?: (...formattedMessage: Array<React$Node>) => React$Node children?:
| ((...formattedMessage: Array<React$Node>) => React$Node)
| (string => React$Node)
} }
> {} > {}
declare class FormattedHTMLMessage extends React$Component< declare class FormattedHTMLMessage extends React$Component<

View File

@ -1,114 +1,276 @@
// flow-typed signature: c5fac64666f9589a0c1b2de956dc7919 // flow-typed signature: f06f00c3ad0cfedb90c0c6de04b219f3
// flow-typed version: 81d6274128/react-redux_v5.x.x/flow_>=v0.53.x // flow-typed version: 3a6d556e4b/react-redux_v5.x.x/flow_>=v0.89.x
// flow-typed signature: 8db7b853f57c51094bf0ab8b2650fd9c /**
// flow-typed version: ab8db5f14d/react-redux_v5.x.x/flow_>=v0.30.x The order of type arguments for connect() is as follows:
import type { Dispatch, Store } from "redux"; connect<Props, OwnProps, StateProps, DispatchProps, State, Dispatch>()
In Flow v0.89 only the first two are mandatory to specify. Other 4 can be repaced with the new awesome type placeholder:
connect<Props, OwnProps, _, _, _, _>()
But beware, in case of weird type errors somewhere in random places
just type everything and get to a green field and only then try to
remove the definitions you see bogus.
Decrypting the abbreviations:
WC = Component being wrapped
S = State
D = Dispatch
OP = OwnProps
SP = StateProps
DP = DispatchProps
MP = Merge props
RSP = Returned state props
RDP = Returned dispatch props
RMP = Returned merge props
CP = Props for returned component
Com = React Component
ST = Static properties of Com
EFO = Extra factory options (used only in connectAdvanced)
*/
declare module "react-redux" { declare module "react-redux" {
/* // ------------------------------------------------------------
// Typings for connect()
// ------------------------------------------------------------
S = State declare export type Options<S, OP, SP, MP> = {|
A = Action pure?: boolean,
OP = OwnProps withRef?: boolean,
SP = StateProps areStatesEqual?: (next: S, prev: S) => boolean,
DP = DispatchProps areOwnPropsEqual?: (next: OP, prev: OP) => boolean,
areStatePropsEqual?: (next: SP, prev: SP) => boolean,
areMergedPropsEqual?: (next: MP, prev: MP) => boolean,
storeKey?: string,
|};
*/ declare type MapStateToProps<-S, -OP, +SP> =
| ((state: S, ownProps: OP) => SP)
// If you want to use the factory function but get a strange error
// like "function is not an object" then just type the factory function
// like this:
// const factory: (State, OwnProps) => (State, OwnProps) => StateProps
// and provide the StateProps type to the SP type parameter.
| ((state: S, ownProps: OP) => (state: S, ownProps: OP) => SP);
declare type MapStateToProps<S, OP: Object, SP: Object> = ( declare type Bind<D> = <A, R>((...A) => R) => (...A) => $Call<D, R>;
state: S,
ownProps: OP
) => SP | MapStateToProps<S, OP, SP>;
declare type MapDispatchToProps<A, OP: Object, DP: Object> = declare type MapDispatchToPropsFn<D, -OP, +DP> =
| ((dispatch: Dispatch<A>, ownProps: OP) => DP) | ((dispatch: D, ownProps: OP) => DP)
| DP; // If you want to use the factory function but get a strange error
// like "function is not an object" then just type the factory function
// like this:
// const factory: (Dispatch, OwnProps) => (Dispatch, OwnProps) => DispatchProps
// and provide the DispatchProps type to the DP type parameter.
| ((dispatch: D, ownProps: OP) => (dispatch: D, ownProps: OP) => DP);
declare type MergeProps<SP, DP: Object, OP: Object, P: Object> = ( declare class ConnectedComponent<OP, +WC> extends React$Component<OP> {
static +WrappedComponent: WC;
getWrappedInstance(): React$ElementRef<WC>;
}
// The connection of the Wrapped Component and the Connected Component
// happens here in `MP: P`. It means that type wise MP belongs to P,
// so to say MP >= P.
declare type Connector<P, OP, MP: P> = <WC: React$ComponentType<P>>(
WC,
) => Class<ConnectedComponent<OP, WC>> & WC;
// No `mergeProps` argument
// Got error like inexact OwnProps is incompatible with exact object type?
// Just make the OP parameter for `connect()` an exact object.
declare type MergeOP<OP, D> = {| ...$Exact<OP>, dispatch: D |};
declare type MergeOPSP<OP, SP, D> = {| ...$Exact<OP>, ...SP, dispatch: D |};
declare type MergeOPDP<OP, DP> = {| ...$Exact<OP>, ...DP |};
declare type MergeOPSPDP<OP, SP, DP> = {| ...$Exact<OP>, ...SP, ...DP |};
declare export function connect<-P, -OP, -SP, -DP, -S, -D>(
mapStateToProps?: null | void,
mapDispatchToProps?: null | void,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOP<OP, D>>,
): Connector<P, OP, MergeOP<OP, D>>;
declare export function connect<-P, -OP, -SP, -DP, -S, -D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps?: null | void,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, MergeOPSP<OP, SP, D>>,
): Connector<P, OP, MergeOPSP<OP, SP, D>>;
// In this case DP is an object of functions which has been bound to dispatch
// by the given mapDispatchToProps function.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOPDP<OP, DP>>,
): Connector<P, OP, MergeOPDP<OP, DP>>;
// In this case DP is an object of action creators not yet bound to dispatch,
// this difference is not important in the vanila redux,
// but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: DP,
mergeProps?: null | void,
options?: ?Options<S, OP, {||}, MergeOPDP<OP, DP>>,
): Connector<P, OP, MergeOPDP<OP, $ObjMap<DP, Bind<D>>>>;
declare export function connect<-P, -OP, -SP, -DP, S, D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, {| ...OP, ...SP, ...DP |}>,
): Connector<P, OP, {| ...OP, ...SP, ...DP |}>;
declare export function connect<-P, -OP, -SP, -DP, S, D>(
// If you get error here try adding return type to your mapStateToProps function
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: DP,
mergeProps?: null | void,
options?: ?Options<S, OP, SP, MergeOPSPDP<OP, SP, DP>>,
): Connector<P, OP, MergeOPSPDP<OP, SP, $ObjMap<DP, Bind<D>>>>;
// With `mergeProps` argument
declare type MergeProps<+P, -OP, -SP, -DP> = (
stateProps: SP, stateProps: SP,
dispatchProps: DP, dispatchProps: DP,
ownProps: OP ownProps: OP,
) => P; ) => P;
declare type Context = { store: Store<*, *> }; declare export function connect<-P, -OP, -SP: {||}, -DP: {||}, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: null | void,
// If you get error here try adding return type to you mapStateToProps function
mergeProps: MergeProps<P, OP, {||}, {| dispatch: D |}>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare class ConnectedComponent<OP, P> extends React$Component<OP> { declare export function connect<-P, -OP, -SP, -DP: {||}, S, D>(
static WrappedComponent: Class<React$Component<P>>, mapStateToProps: MapStateToProps<S, OP, SP>,
getWrappedInstance(): React$Component<P>, mapDispatchToProps: null | void,
props: OP, // If you get error here try adding return type to you mapStateToProps function
state: void mergeProps: MergeProps<P, OP, SP, {| dispatch: D |}>,
} options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
declare type ConnectedComponentClass<OP, P> = Class< // In this case DP is an object of functions which has been bound to dispatch
ConnectedComponent<OP, P> // by the given mapDispatchToProps function.
>; declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps: MergeProps<P, OP, {||}, DP>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare type Connector<OP, P> = ( // In this case DP is an object of action creators not yet bound to dispatch,
component: React$ComponentType<P> // this difference is not important in the vanila redux,
) => ConnectedComponentClass<OP, P>; // but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP: {||}, -DP, S, D>(
mapStateToProps: null | void,
mapDispatchToProps: DP,
mergeProps: MergeProps<P, OP, {||}, $ObjMap<DP, Bind<D>>>,
options?: ?Options<S, OP, {||}, P>,
): Connector<P, OP, P>;
declare class Provider<S, A> extends React$Component<{ // In this case DP is an object of functions which has been bound to dispatch
store: Store<S, A>, // by the given mapDispatchToProps function.
children?: any declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToPropsFn<D, OP, DP>,
mergeProps: MergeProps<P, OP, SP, DP>,
options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
// In this case DP is an object of action creators not yet bound to dispatch,
// this difference is not important in the vanila redux,
// but in case of usage with redux-thunk, the return type may differ.
declare export function connect<-P, -OP, -SP, -DP, S, D>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: DP,
mergeProps: MergeProps<P, OP, SP, $ObjMap<DP, Bind<D>>>,
options?: ?Options<S, OP, SP, P>,
): Connector<P, OP, P>;
// ------------------------------------------------------------
// Typings for Provider
// ------------------------------------------------------------
declare export class Provider<Store> extends React$Component<{
store: Store,
children?: React$Node,
}> {} }> {}
declare function createProvider( declare export function createProvider(
storeKey?: string, storeKey?: string,
subKey?: string subKey?: string,
): Provider<*, *>; ): Class<Provider<*>>;
declare type ConnectOptions = { // ------------------------------------------------------------
pure?: boolean, // Typings for connectAdvanced()
withRef?: boolean // ------------------------------------------------------------
declare type ConnectAdvancedOptions = {
getDisplayName?: (name: string) => string,
methodName?: string,
renderCountProp?: string,
shouldHandleStateChanges?: boolean,
storeKey?: string,
withRef?: boolean,
}; };
declare type Null = null | void; declare type SelectorFactoryOptions<Com> = {
getDisplayName: (name: string) => string,
methodName: string,
renderCountProp: ?string,
shouldHandleStateChanges: boolean,
storeKey: string,
withRef: boolean,
displayName: string,
wrappedComponentName: string,
WrappedComponent: Com,
};
declare function connect<A, OP>( declare type MapStateToPropsEx<S: Object, SP: Object, RSP: Object> = (
...rest: Array<void> // <= workaround for https://github.com/facebook/flow/issues/2360 state: S,
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>; props: SP,
) => RSP;
declare function connect<A, OP>( declare type SelectorFactory<
mapStateToProps: Null, Com: React$ComponentType<*>,
mapDispatchToProps: Null, Dispatch,
mergeProps: Null, S: Object,
options: ConnectOptions OP: Object,
): Connector<OP, $Supertype<{ dispatch: Dispatch<A> } & OP>>; EFO: Object,
CP: Object,
> = (
dispatch: Dispatch,
factoryOptions: SelectorFactoryOptions<Com> & EFO,
) => MapStateToPropsEx<S, OP, CP>;
declare function connect<S, A, OP, SP>( declare export function connectAdvanced<
mapStateToProps: MapStateToProps<S, OP, SP>, Com: React$ComponentType<*>,
mapDispatchToProps: Null, D,
mergeProps: Null, S: Object,
options?: ConnectOptions OP: Object,
): Connector<OP, $Supertype<SP & { dispatch: Dispatch<A> } & OP>>; CP: Object,
EFO: Object,
ST: { [_: $Keys<Com>]: any },
>(
selectorFactory: SelectorFactory<Com, D, S, OP, EFO, CP>,
connectAdvancedOptions: ?(ConnectAdvancedOptions & EFO),
): (component: Com) => React$ComponentType<OP> & $Shape<ST>;
declare function connect<A, OP, DP>( declare export default {
mapStateToProps: Null, Provider: typeof Provider,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>, createProvider: typeof createProvider,
mergeProps: Null, connect: typeof connect,
options?: ConnectOptions connectAdvanced: typeof connectAdvanced,
): Connector<OP, $Supertype<DP & OP>>; };
declare function connect<S, A, OP, SP, DP>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: Null,
options?: ConnectOptions
): Connector<OP, $Supertype<SP & DP & OP>>;
declare function connect<S, A, OP, SP, DP, P>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: Null,
mergeProps: MergeProps<SP, DP, OP, P>,
options?: ConnectOptions
): Connector<OP, P>;
declare function connect<S, A, OP, SP, DP, P>(
mapStateToProps: MapStateToProps<S, OP, SP>,
mapDispatchToProps: MapDispatchToProps<A, OP, DP>,
mergeProps: MergeProps<SP, DP, OP, P>,
options?: ConnectOptions
): Connector<OP, P>;
} }

View File

@ -0,0 +1,176 @@
// flow-typed signature: 9987f80c12a2cad7dfa2b08cc14d2edc
// flow-typed version: 2973a15489/react-router-dom_v5.x.x/flow_>=v0.98.x
declare module "react-router-dom" {
declare export var BrowserRouter: React$ComponentType<{|
basename?: string,
forceRefresh?: boolean,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Node
|}>
declare export var HashRouter: React$ComponentType<{|
basename?: string,
getUserConfirmation?: GetUserConfirmation,
hashType?: "slash" | "noslash" | "hashbang",
children?: React$Node
|}>
declare export var Link: React$ComponentType<{
className?: string,
to: string | LocationShape,
replace?: boolean,
children?: React$Node
}>
declare export var NavLink: React$ComponentType<{
to: string | LocationShape,
activeClassName?: string,
className?: string,
activeStyle?: { +[string]: mixed },
style?: { +[string]: mixed },
isActive?: (match: Match, location: Location) => boolean,
children?: React$Node,
exact?: boolean,
strict?: boolean
}>
// NOTE: Below are duplicated from react-router. If updating these, please
// update the react-router and react-router-native types as well.
declare export type Location = {
pathname: string,
search: string,
hash: string,
state?: any,
key?: string
};
declare export type LocationShape = {
pathname?: string,
search?: string,
hash?: string,
state?: any
};
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = {
length: number,
location: Location,
action: HistoryAction,
listen(
callback: (location: Location, action: HistoryAction) => void
): () => void,
push(path: string | LocationShape, state?: any): void,
replace(path: string | LocationShape, state?: any): void,
go(n: number): void,
goBack(): void,
goForward(): void,
canGo?: (n: number) => boolean,
block(
callback: string | (location: Location, action: HistoryAction) => ?string
): () => void,
// createMemoryHistory
index?: number,
entries?: Array<Location>
};
declare export type Match = {
params: { [key: string]: ?string },
isExact: boolean,
path: string,
url: string
};
declare export type ContextRouter = {|
history: RouterHistory,
location: Location,
match: Match,
staticContext?: StaticRouterContext
|};
declare type ContextRouterVoid = {
history: RouterHistory | void,
location: Location | void,
match: Match | void,
staticContext?: StaticRouterContext | void
};
declare export type GetUserConfirmation = (
message: string,
callback: (confirmed: boolean) => void
) => void;
declare export type StaticRouterContext = {
url?: string
};
declare export var StaticRouter: React$ComponentType<{|
basename?: string,
location?: string | Location,
context: StaticRouterContext,
children?: React$Node
|}>
declare export var MemoryRouter: React$ComponentType<{|
initialEntries?: Array<LocationShape | string>,
initialIndex?: number,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Node
|}>
declare export var Router: React$ComponentType<{|
history: RouterHistory,
children?: React$Node
|}>
declare export var Prompt: React$ComponentType<{|
message: string | ((location: Location) => string | boolean),
when?: boolean
|}>
declare export var Redirect: React$ComponentType<{|
to: string | LocationShape,
push?: boolean,
from?: string,
exact?: boolean,
strict?: boolean
|}>
declare export var Route: React$ComponentType<{|
component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Node,
children?: React$ComponentType<ContextRouter> | React$Node,
path?: string | Array<string>,
exact?: boolean,
strict?: boolean,
location?: LocationShape,
sensitive?: boolean
|}>
declare export var Switch: React$ComponentType<{|
children?: React$Node,
location?: Location
|}>
declare export function withRouter<Props: {}, Component: React$ComponentType<Props>>(
WrappedComponent: Component
): React$ComponentType<$Diff<React$ElementConfig<Component>, ContextRouterVoid>>;
declare type MatchPathOptions = {
path?: string,
exact?: boolean,
sensitive?: boolean,
strict?: boolean
};
declare export function matchPath(
pathname: string,
options?: MatchPathOptions | string,
parent?: Match
): null | Match;
declare export function generatePath(pattern?: string, params?: { +[string]: mixed }): string;
}

View File

@ -1,139 +0,0 @@
// flow-typed signature: b45080e7e6a55f1c9092f07c205cd527
// flow-typed version: 3e35e41eb5/react-router_v4.x.x/flow_v0.53.x
// flow-typed signature: b701192ca557cf27adf1b295517299fd
// flow-typed version: b43dff3e0e/react-router_v4.x.x/flow_>=v0.53.x
import * as React from "react";
declare module "react-router" {
// NOTE: many of these are re-exported by react-router-dom and
// react-router-native, so when making changes, please be sure to update those
// as well.
declare export type Location = {
pathname: string,
search: string,
hash: string,
state?: any,
key?: string
};
declare export type LocationShape = {
pathname?: string,
search?: string,
hash?: string,
state?: any
};
declare export type HistoryAction = "PUSH" | "REPLACE" | "POP";
declare export type RouterHistory = {
length: number,
location: Location,
action: HistoryAction,
listen(
callback: (location: Location, action: HistoryAction) => void
): () => void,
push(path: string | LocationShape, state?: any): void,
replace(path: string | LocationShape, state?: any): void,
go(n: number): void,
goBack(): void,
goForward(): void,
canGo?: (n: number) => boolean,
block(
callback: (location: Location, action: HistoryAction) => boolean
): void,
// createMemoryHistory
index?: number,
entries?: Array<Location>
};
declare export type Match = {
params: { [key: string]: ?string },
isExact: boolean,
path: string,
url: string
};
declare export type ContextRouter = {
history: RouterHistory,
location: Location,
match: Match
};
declare export type GetUserConfirmation = (
message: string,
callback: (confirmed: boolean) => void
) => void;
declare type StaticRouterContext = {
url?: string
};
declare type StaticRouterProps = {
basename?: string,
location?: string | Location,
context: StaticRouterContext,
children?: React$Element<*>
};
declare export class StaticRouter extends React$Component<
StaticRouterProps
> {}
declare type MemoryRouterProps = {
initialEntries?: Array<LocationShape | string>,
initialIndex?: number,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Element<*>
};
declare export class MemoryRouter extends React$Component<
MemoryRouterProps
> {}
declare type RouterProps = {
history: RouterHistory,
children?: React$Element<*>
};
declare export class Router extends React$Component<RouterProps> {}
declare type PromptProps = {
message: string | ((location: Location) => string | true),
when?: boolean
};
declare export class Prompt extends React$Component<PromptProps> {}
declare type RedirectProps = {
to: string | LocationShape,
push?: boolean
};
declare export class Redirect extends React$Component<RedirectProps> {}
declare type RouteProps = {
component?: React$ComponentType<*>,
render?: (router: ContextRouter) => React$Element<*>,
children?: (router: ContextRouter) => React$Element<*>,
path?: string,
exact?: boolean,
strict?: boolean
};
declare export class Route extends React$Component<RouteProps> {}
declare type SwithcProps = {
children?: Array<React$Element<*>>
};
declare export class Switch extends React$Component<SwithcProps> {}
declare export function withRouter<P>(
Component: React$ComponentType<ContextRouter & P>
): React$ComponentType<P>;
declare type MatchPathOptions = {
exact?: boolean,
strict?: boolean
};
declare export function matchPath(
pathname: string,
path: string,
options?: MatchPathOptions
): null | Match;
}

View File

@ -1,109 +0,0 @@
// flow-typed signature: 86993bd000012d3e1ef10d757d16952d
// flow-typed version: a165222d28/redux_v3.x.x/flow_>=v0.33.x
declare module 'redux' {
/*
S = State
A = Action
D = Dispatch
*/
declare type DispatchAPI<A> = (action: A) => A;
declare type Dispatch<A: { type: $Subtype<string> }> = DispatchAPI<A>;
declare type MiddlewareAPI<S, A, D = Dispatch<A>> = {
dispatch: D;
getState(): S;
};
declare type Store<S, A, D = Dispatch<A>> = {
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
dispatch: D;
getState(): S;
subscribe(listener: () => void): () => void;
replaceReducer(nextReducer: Reducer<S, A>): void
};
declare type Reducer<S, A> = (state: S, action: A) => S;
declare type CombinedReducer<S, A> = (state: $Shape<S> & {} | void, action: A) => S;
declare type Middleware<S, A, D = Dispatch<A>> =
(api: MiddlewareAPI<S, A, D>) =>
(next: D) => D;
declare type StoreCreator<S, A, D = Dispatch<A>> = {
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
};
declare type StoreEnhancer<S, A, D = Dispatch<A>> = (next: StoreCreator<S, A, D>) => StoreCreator<S, A, D>;
declare function createStore<S, A, D>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
declare function createStore<S, A, D>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>;
declare function applyMiddleware<S, A, D>(...middlewares: Array<Middleware<S, A, D>>): StoreEnhancer<S, A, D>;
declare type ActionCreator<A, B> = (...args: Array<B>) => A;
declare type ActionCreators<K, A> = { [key: K]: ActionCreator<A, any> };
declare function bindActionCreators<A, C: ActionCreator<A, any>, D: DispatchAPI<A>>(actionCreator: C, dispatch: D): C;
declare function bindActionCreators<A, K, C: ActionCreators<K, A>, D: DispatchAPI<A>>(actionCreators: C, dispatch: D): C;
declare function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare function compose<A, B>(ab: (a: A) => B): (a: A) => B
declare function compose<A, B, C>(
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => C
declare function compose<A, B, C, D>(
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => D
declare function compose<A, B, C, D, E>(
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => E
declare function compose<A, B, C, D, E, F>(
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => F
declare function compose<A, B, C, D, E, F, G>(
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => G
declare function compose<A, B, C, D, E, F, G, H>(
gh: (g: G) => H,
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => H
declare function compose<A, B, C, D, E, F, G, H, I>(
hi: (h: H) => I,
gh: (g: G) => H,
fg: (f: F) => G,
ef: (e: E) => F,
de: (d: D) => E,
cd: (c: C) => D,
bc: (b: B) => C,
ab: (a: A) => B
): (a: A) => I
}

100
flow-typed/npm/redux_v4.x.x.js vendored Normal file
View File

@ -0,0 +1,100 @@
// flow-typed signature: a49a6c96fe8a8bb3330cce2028588f4c
// flow-typed version: de5b3a01c6/redux_v4.x.x/flow_>=v0.89.x
declare module 'redux' {
/*
S = State
A = Action
D = Dispatch
*/
declare export type Action<T> = {
type: T
}
declare export type DispatchAPI<A> = (action: A) => A;
declare export type Dispatch<A: { type: * }> = DispatchAPI<A>;
declare export type MiddlewareAPI<S, A, D = Dispatch<A>> = {
dispatch: D,
getState(): S,
};
declare export type Store<S, A, D = Dispatch<A>> = {
// rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
dispatch: D,
getState(): S,
subscribe(listener: () => void): () => void,
replaceReducer(nextReducer: Reducer<S, A>): void,
};
declare export type Reducer<S, A> = (state: S | void, action: A) => S;
declare export type CombinedReducer<S, A> = (
state: ($Shape<S> & {}) | void,
action: A
) => S;
declare export type Middleware<S, A, D = Dispatch<A>> = (
api: MiddlewareAPI<S, A, D>
) => (next: D) => D;
declare export type StoreCreator<S, A, D = Dispatch<A>> = {
(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<S, A, D>): Store<S, A, D>,
(
reducer: Reducer<S, A>,
preloadedState: S,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>,
};
declare export type StoreEnhancer<S, A, D = Dispatch<A>> = (
next: StoreCreator<S, A, D>
) => StoreCreator<S, A, D>;
declare export function createStore<S, A, D>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>;
declare export function createStore<S, A, D>(
reducer: Reducer<S, A>,
preloadedState?: S,
enhancer?: StoreEnhancer<S, A, D>
): Store<S, A, D>;
declare export function applyMiddleware<S, A, D>(
...middlewares: Array<Middleware<S, A, D>>
): StoreEnhancer<S, A, D>;
declare export type ActionCreator<A, B> = (...args: Array<B>) => A;
declare export type ActionCreators<K, A> = {
[key: K]: ActionCreator<A, any>,
};
declare export function bindActionCreators<
A,
C: ActionCreator<A, any>,
D: DispatchAPI<A>
>(
actionCreator: C,
dispatch: D
): C;
declare export function bindActionCreators<
A,
K,
C: ActionCreators<K, A>,
D: DispatchAPI<A>
>(
actionCreators: C,
dispatch: D
): C;
declare export function combineReducers<O: {}, A>(
reducers: O
): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare export var compose: $Compose;
}

View File

@ -21,6 +21,7 @@
"scripts": { "scripts": {
"start": "yarn run clean && yarn run build:dll && NODE_PATH=./src webpack-dev-server --progress --colors", "start": "yarn run clean && yarn run build:dll && NODE_PATH=./src webpack-dev-server --progress --colors",
"clean": "rm -rf dist/", "clean": "rm -rf dist/",
"e2e": "yarn --cwd ./tests-e2e test",
"test": "yarn run build:dll && NODE_PATH=./src karma start ./karma.conf.js", "test": "yarn run build:dll && NODE_PATH=./src karma start ./karma.conf.js",
"lint": "eslint ./src", "lint": "eslint ./src",
"flow": "flow", "flow": "flow",
@ -39,9 +40,7 @@
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",
"debounce": "^1.0.0", "debounce": "^1.0.0",
"flag-icon-css": "^2.8.0", "flag-icon-css": "^2.8.0",
"intl": "^1.2.2", "intl": "^1.2.5",
"intl-format-cache": "^2.0.4",
"intl-messageformat": "^2.1.0",
"promise.prototype.finally": "3.1.0", "promise.prototype.finally": "3.1.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"raf": "^3.4.1", "raf": "^3.4.1",
@ -51,14 +50,14 @@
"react-helmet": "^5.0.0", "react-helmet": "^5.0.0",
"react-intl": "^2.7.2", "react-intl": "^2.7.2",
"react-motion": "^0.5.0", "react-motion": "^0.5.0",
"react-redux": "^5.0.6", "react-redux": "^7.1.0",
"react-router-dom": "^4.3.1", "react-router-dom": "^5.0.1",
"react-textarea-autosize": "^6.0.0", "react-textarea-autosize": "^6.0.0",
"react-transition-group": "^1.1.3", "react-transition-group": "^4.2.0",
"redux": "^3.0.4", "redux": "^4.0.1",
"redux-localstorage": "^0.4.1", "redux-localstorage": "^0.4.1",
"redux-thunk": "^2.0.0", "redux-thunk": "^2.0.0",
"url-search-params-polyfill": "^5.0.0", "url-search-params-polyfill": "^6.0.0",
"webfontloader": "^1.6.26", "webfontloader": "^1.6.26",
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.0.0"
}, },
@ -90,22 +89,22 @@
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0", "extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.11.0", "file-loader": "^0.11.0",
"flow-bin": "~0.80.0", "flow-bin": "~0.102.0",
"fontgen-loader": "^0.2.1", "fontgen-loader": "^0.2.1",
"html-loader": "^0.4.3", "html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0", "html-webpack-plugin": "^2.0.0",
"imports-loader": "^0.7.0", "imports-loader": "^0.7.0",
"jsdom": "^9.8.3", "jsdom": "^15.1.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"karma": "^1.1.0", "karma": "^4.1.0",
"karma-jsdom-launcher": "^6.0.0", "karma-jsdom-launcher": "^7.1.1",
"karma-mocha": "^1.0.0", "karma-mocha": "^1.0.0",
"karma-nyan-reporter": "^0.2.3", "karma-nyan-reporter": "^0.2.3",
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "*", "karma-sourcemap-loader": "*",
"karma-webpack": "^2.0.0", "karma-webpack": "^2.0.0",
"loader-utils": "^1.0.0", "loader-utils": "^1.0.0",
"mocha": "^3.0.2", "mocha": "^6.1.4",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"postcss-import": "^9.0.0", "postcss-import": "^9.0.0",
"postcss-loader": "^1.2.0", "postcss-loader": "^1.2.0",
@ -115,10 +114,10 @@
"react-test-renderer": "^16.7.0", "react-test-renderer": "^16.7.0",
"sass-loader": "^4.0.0", "sass-loader": "^4.0.0",
"scripts": "file:./scripts", "scripts": "file:./scripts",
"sinon": "^3.2.1", "sinon": "^7.3.2",
"sitemap-webpack-plugin": "^0.5.1", "sitemap-webpack-plugin": "^0.5.1",
"style-loader": "^0.18.0", "style-loader": "^0.18.0",
"unexpected": "^10.33.2", "unexpected": "^11.6.1",
"unexpected-sinon": "^10.5.1", "unexpected-sinon": "^10.5.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"webpack": "^1.12.9", "webpack": "^1.12.9",

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import { browserHistory } from 'services/history'; import { browserHistory } from 'services/history';
@ -445,8 +445,9 @@ describe('components/accounts/actions', () => {
return logoutStrangers()(dispatch, getState) return logoutStrangers()(dispatch, getState)
.then(() => .then(() =>
expect(dispatch, 'was always called with', expect(dispatch, 'not to have calls satisfying',
expect.it('not to satisfy', activate(account))) [activate(account)]
)
); );
}); });
@ -511,8 +512,8 @@ describe('components/accounts/actions', () => {
}); });
it('should not log out', () => it('should not log out', () =>
expect(dispatch, 'was always called with', expect(dispatch, 'not to have calls satisfying',
expect.it('not to equal', {payload: foreignAccount}) [{payload: foreignAccount}]
) )
); );
}); });

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import accounts from 'components/accounts/reducer'; import accounts from 'components/accounts/reducer';
import { import {

View File

@ -1,7 +1,7 @@
// @flow // @flow
import type { User } from 'components/user'; import type { User } from 'components/user';
import type { AccountsState } from 'components/accounts'; import type { AccountsState } from 'components/accounts';
import type { Node, Element } from 'react'; import type { Element } from 'react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -24,6 +24,8 @@ const opacitySpringConfig = {stiffness: 300, damping: 20};
const transformSpringConfig = {stiffness: 500, damping: 50, precision: 0.5}; const transformSpringConfig = {stiffness: 500, damping: 50, precision: 0.5};
const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5}; const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
type PanelId = string;
/** /**
* Definition of relation between contexts and panels * Definition of relation between contexts and panels
* *
@ -35,7 +37,7 @@ const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
* - Panel index defines the direction of X transition of both panels * - Panel index defines the direction of X transition of both panels
* (e.g. the panel with lower index will slide from left side, and with greater from right side) * (e.g. the panel with lower index will slide from left side, and with greater from right side)
*/ */
const contexts = [ const contexts: Array<PanelId[]> = [
['login', 'password', 'forgotPassword', 'mfa', 'recoverPassword'], ['login', 'password', 'forgotPassword', 'mfa', 'recoverPassword'],
['register', 'activation', 'resendActivation'], ['register', 'activation', 'resendActivation'],
['acceptRules'], ['acceptRules'],
@ -71,18 +73,27 @@ type AnimationProps = {|
|}; |};
type AnimationContext = { type AnimationContext = {
key: string, key: PanelId,
style: AnimationProps, style: AnimationProps,
data: { data: {
Title: Node, Title: Element<any>,
Body: Element<any>, Body: Element<any>,
Footer: Node, Footer: Element<any>,
Links: Node, Links: Element<any>,
hasBackButton: bool, hasBackButton: bool,
} }
}; };
type OwnProps = {|
Title: Element<any>,
Body: Element<any>,
Footer: Element<any>,
Links: Element<any>,
children?: Element<any>
|};
type Props = { type Props = {
...OwnProps,
// context props // context props
auth: { auth: {
error: string | { error: string | {
@ -98,20 +109,12 @@ type Props = {
clearErrors: () => void, clearErrors: () => void,
resolve: () => void, resolve: () => void,
reject: () => void, reject: () => void,
// local props
Title: Node,
Body: typeof Component,
Footer: Node,
Links: Node,
children: Node
}; };
type State = { type State = {
contextHeight: number, contextHeight: number,
panelId: string | void, panelId: PanelId | void,
prevPanelId: string | void, prevPanelId: PanelId | void,
isHeightDirty: bool, isHeightDirty: bool,
forceHeight: 1 | 0, forceHeight: 1 | 0,
direction: 'X' | 'Y', direction: 'X' | 'Y',
@ -187,7 +190,7 @@ class PanelTransition extends Component<Props, State> {
return { return {
auth: this.props.auth, auth: this.props.auth,
user: this.props.user, user: this.props.user,
requestRedraw: () => requestRedraw: (): Promise<void> =>
new Promise((resolve) => new Promise((resolve) =>
this.setState( this.setState(
{isHeightDirty: true}, {isHeightDirty: true},
@ -207,9 +210,9 @@ class PanelTransition extends Component<Props, State> {
}; };
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps: Props) {
const nextPanel = nextProps.Body && (nextProps.Body: any).type.panelId; const nextPanel: PanelId = nextProps.Body && (nextProps.Body: any).type.panelId;
const prevPanel = this.props.Body && (this.props.Body: any).type.panelId; const prevPanel: PanelId = this.props.Body && (this.props.Body: any).type.panelId;
if (nextPanel !== prevPanel) { if (nextPanel !== prevPanel) {
const direction = this.getDirection(nextPanel, prevPanel); const direction = this.getDirection(nextPanel, prevPanel);
@ -250,7 +253,7 @@ class PanelTransition extends Component<Props, State> {
} }
const {panelId, hasGoBack}: { const {panelId, hasGoBack}: {
panelId: string, panelId: PanelId,
hasGoBack: bool, hasGoBack: bool,
} = (Body: any).type; } = (Body: any).type;
@ -336,10 +339,10 @@ class PanelTransition extends Component<Props, State> {
} }
}; };
onFormInvalid = (errors) => this.props.setErrors(errors); onFormInvalid = (errors: { [key: string]: ValidationError }) => this.props.setErrors(errors);
willEnter = (config) => this.getTransitionStyles(config); willEnter = (config: AnimationContext) => this.getTransitionStyles(config);
willLeave = (config) => this.getTransitionStyles(config, {isLeave: true}); willLeave = (config: AnimationContext) => this.getTransitionStyles(config, {isLeave: true});
/** /**
* @param {object} config * @param {object} config
@ -349,7 +352,7 @@ class PanelTransition extends Component<Props, State> {
* *
* @return {object} * @return {object}
*/ */
getTransitionStyles({key}, options = {}): {| getTransitionStyles({key}: AnimationContext, options: { isLeave?: bool } = {}): {|
transformSpring: number, transformSpring: number,
opacitySpring: number, opacitySpring: number,
|} { |} {
@ -380,7 +383,7 @@ class PanelTransition extends Component<Props, State> {
}; };
} }
getDirection(next, prev): 'X' | 'Y' { getDirection(next: PanelId, prev: PanelId): 'X' | 'Y' {
const context = contexts.find((context) => context.includes(prev)); const context = contexts.find((context) => context.includes(prev));
if (!context) { if (!context) {
@ -390,7 +393,7 @@ class PanelTransition extends Component<Props, State> {
return context.includes(next) ? 'X' : 'Y'; return context.includes(next) ? 'X' : 'Y';
} }
onUpdateHeight = (height, key) => { onUpdateHeight = (height: number, key: PanelId) => {
const heightKey = `formHeight${key}`; const heightKey = `formHeight${key}`;
this.setState({ this.setState({
@ -398,13 +401,13 @@ class PanelTransition extends Component<Props, State> {
}); });
}; };
onUpdateContextHeight = (height) => { onUpdateContextHeight = (height: number) => {
this.setState({ this.setState({
contextHeight: height contextHeight: height
}); });
}; };
onGoBack = (event) => { onGoBack = (event: SyntheticEvent<HTMLElement>) => {
event.preventDefault(); event.preventDefault();
authFlow.goBack(); authFlow.goBack();
@ -415,7 +418,7 @@ class PanelTransition extends Component<Props, State> {
* *
* @param {number} length number of panels transitioned * @param {number} length number of panels transitioned
*/ */
tryToAutoFocus(length) { tryToAutoFocus(length: number) {
if (!this.body) { if (!this.body) {
return; return;
} }
@ -588,7 +591,7 @@ class PanelTransition extends Component<Props, State> {
* *
* @return {object} * @return {object}
*/ */
translate(value, direction = 'X', unit = '%') { translate(value: number, direction: 'X' | 'Y' = 'X', unit: '%' | 'px' = '%') {
return { return {
WebkitTransform: `translate${direction}(${value}${unit})`, WebkitTransform: `translate${direction}(${value}${unit})`,
transform: `translate${direction}(${value}${unit})` transform: `translate${direction}(${value}${unit})`
@ -596,7 +599,7 @@ class PanelTransition extends Component<Props, State> {
} }
} }
export default connect((state) => { export default connect<Props, OwnProps, _, _, _, _>((state) => {
const login = getLogin(state); const login = getLogin(state);
let user = { let user = {
...state.user ...state.user

View File

@ -1,5 +1,5 @@
// @flow // @flow
import type { OauthData } from 'services/api/oauth'; import type { OauthData, Client, Scope } from 'services/api/oauth';
import type { OAuthResponse } from 'services/api/authentication'; import type { OAuthResponse } from 'services/api/authentication';
import { browserHistory } from 'services/history'; import { browserHistory } from 'services/history';
import logger from 'services/logger'; import logger from 'services/logger';
@ -19,8 +19,14 @@ import signup from 'services/api/signup';
import dispatchBsod from 'components/ui/bsod/dispatchBsod'; import dispatchBsod from 'components/ui/bsod/dispatchBsod';
import { create as createPopup } from 'components/ui/popup/actions'; import { create as createPopup } from 'components/ui/popup/actions';
import ContactForm from 'components/contact/ContactForm'; import ContactForm from 'components/contact/ContactForm';
import { getCredentials } from './reducer'; import { getCredentials } from './reducer';
type ValidationError = string | {
type: string,
payload: { [key: string]: any },
};
export { updateUser } from 'components/user/actions'; export { updateUser } from 'components/user/actions';
export { export {
authenticate, authenticate,
@ -212,7 +218,7 @@ export function resendActivation({
} }
export function contactUs() { export function contactUs() {
return createPopup(ContactForm); return createPopup({ Popup: ContactForm });
} }
export const SET_CREDENTIALS = 'auth:setCredentials'; export const SET_CREDENTIALS = 'auth:setCredentials';
@ -283,7 +289,7 @@ export function setAccountSwitcher(isOn: bool) {
} }
export const ERROR = 'auth:error'; export const ERROR = 'auth:error';
export function setErrors(errors: ?{[key: string]: string}) { export function setErrors(errors: ?{[key: string]: ValidationError}) {
return { return {
type: ERROR, type: ERROR,
payload: errors, payload: errors,
@ -413,11 +419,7 @@ export function setClient({
id, id,
name, name,
description description
}: { }: Client) {
id: string,
name: string,
description: string
}) {
return { return {
type: SET_CLIENT, type: SET_CLIENT,
payload: {id, name, description} payload: {id, name, description}
@ -502,7 +504,7 @@ export function requirePermissionsAccept() {
} }
export const SET_SCOPES = 'set_scopes'; export const SET_SCOPES = 'set_scopes';
export function setScopes(scopes: Array<string>) { export function setScopes(scopes: Scope[]) {
if (!(scopes instanceof Array)) { if (!(scopes instanceof Array)) {
throw new Error('Scopes must be array'); throw new Error('Scopes must be array');
} }

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'unexpected'; import expect from 'test/unexpected';
import request from 'services/request'; import request from 'services/request';

View File

@ -19,10 +19,10 @@ type Credentials = {
rememberMe?: bool, rememberMe?: bool,
returnUrl?: string, returnUrl?: string,
isRelogin?: bool, isRelogin?: bool,
isTotpRequired?: bool, isTotpRequired?: bool
}; };
export default combineReducers({ export default combineReducers<_, { action: string, payload?: mixed }>({
credentials, credentials,
error, error,
isLoading, isLoading,
@ -32,10 +32,7 @@ export default combineReducers({
scopes scopes
}); });
function error( function error(state = null, { type, payload = null, error = false }) {
state = null,
{type, payload = null, error = false}
) {
switch (type) { switch (type) {
case ERROR: case ERROR:
if (!error) { if (!error) {
@ -51,15 +48,16 @@ function error(
function credentials( function credentials(
state = {}, state = {},
{type, payload}: { {
type,
payload
}: {
type: string, type: string,
payload: ?Credentials payload: ?Credentials
} }
) { ) {
if (type === SET_CREDENTIALS) { if (type === SET_CREDENTIALS) {
if (payload if (payload && typeof payload === 'object') {
&& typeof payload === 'object'
) {
return { return {
...payload ...payload
}; };
@ -71,10 +69,7 @@ function credentials(
return state; return state;
} }
function isSwitcherEnabled( function isSwitcherEnabled(state = true, { type, payload = false }) {
state = true,
{type, payload = false}
) {
switch (type) { switch (type) {
case SET_SWITCHER: case SET_SWITCHER:
if (typeof payload !== 'boolean') { if (typeof payload !== 'boolean') {
@ -88,10 +83,7 @@ function isSwitcherEnabled(
} }
} }
function isLoading( function isLoading(state = false, { type, payload = null }) {
state = false,
{type, payload = null}
) {
switch (type) { switch (type) {
case SET_LOADING_STATE: case SET_LOADING_STATE:
return !!payload; return !!payload;
@ -101,10 +93,7 @@ function isLoading(
} }
} }
function client( function client(state = null, { type, payload = {} }) {
state = null,
{type, payload = {}}
) {
switch (type) { switch (type) {
case SET_CLIENT: case SET_CLIENT:
return { return {
@ -118,10 +107,7 @@ function client(
} }
} }
function oauth( function oauth(state = null, { type, payload = {} }) {
state = null,
{type, payload = {}}
) {
switch (type) { switch (type) {
case SET_OAUTH: case SET_OAUTH:
return { return {
@ -153,10 +139,7 @@ function oauth(
} }
} }
function scopes( function scopes(state = [], { type, payload = [] }) {
state = [],
{type, payload = []}
) {
switch (type) { switch (type) {
case SET_SCOPES: case SET_SCOPES:
return payload; return payload;

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import auth from 'components/auth/reducer'; import auth from 'components/auth/reducer';
import { import {

View File

@ -1,9 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage as Message } from 'react-intl'; import { FormattedMessage as Message } from 'react-intl';
import { Input, TextArea, Button, Form, FormModel, Dropdown } from 'components/ui/form'; import { Input, TextArea, Button, Form, FormModel, Dropdown } from 'components/ui/form';
import feedback from 'services/api/feedback'; import feedback from 'services/api/feedback';
import icons from 'components/ui/icons.scss'; import icons from 'components/ui/icons.scss';
@ -174,8 +173,6 @@ export class ContactForm extends Component {
}; };
} }
import { connect } from 'react-redux';
export default connect((state) => ({ export default connect((state) => ({
user: state.user user: state.user
}))(ContactForm); }))(ContactForm);

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';

View File

@ -1,16 +1,21 @@
// @flow // @flow
import type { ElementConfig } from 'react';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions'; import { create as createPopup } from 'components/ui/popup/actions';
import ContactForm from './ContactForm'; import ContactForm from './ContactForm';
type OwnProps = $Exact<ElementConfig<'a'>>;
type Props = {
...OwnProps,
createContactPopup: () => void,
};
function ContactLink({ function ContactLink({
createContactPopup, createContactPopup,
...props ...props
}: { }: Props) {
createContactPopup: () => void,
props: Object
}) {
return ( return (
<a <a
href="#" href="#"
@ -25,9 +30,9 @@ function ContactLink({
); );
} }
export default connect( export default connect<Props, OwnProps, _, _, _, _>(
null, null,
{ {
createContactPopup: () => createPopup(ContactForm) createContactPopup: () => createPopup({ Popup: ContactForm })
} }
)(ContactLink); )(ContactLink);

View File

@ -97,7 +97,7 @@ export default class ApplicationForm extends Component<{
</div> </div>
</div> </div>
{FormComponent && ( {!!FormComponent && (
<Button <Button
color={COLOR_GREEN} color={COLOR_GREEN}
block block

View File

@ -63,7 +63,7 @@ export default class ApplicationItem extends Component<
[styles.appExpanded]: expand [styles.appExpanded]: expand
})} })}
data-e2e="appItem" data-e2e="appItem"
data-e2e-app={app.clientId} data-e2e-app-name={app.name}
> >
<div className={styles.appItemTile} onClick={this.onTileToggle}> <div className={styles.appItemTile} onClick={this.onTileToggle}>
<div className={styles.appTileTitle}> <div className={styles.appTileTitle}>

View File

@ -1,10 +1,11 @@
// @flow // @flow
import type { OauthAppResponse } from 'services/api/oauth'; import type { OauthAppResponse } from 'services/api/oauth';
import type { Action } from './actions'; import type { Action } from './actions';
export type Apps = { export type Apps = {|
+available: Array<OauthAppResponse>, +available: OauthAppResponse[],
}; |};
const defaults: Apps = { const defaults: Apps = {
available: [], available: [],
@ -13,7 +14,7 @@ const defaults: Apps = {
export default function apps( export default function apps(
state: Apps = defaults, state: Apps = defaults,
action: Action action: Action
) { ): Apps {
switch (action.type) { switch (action.type) {
case 'apps:setAvailable': case 'apps:setAvailable':
return { return {
@ -23,7 +24,7 @@ export default function apps(
case 'apps:addApp': { case 'apps:addApp': {
const { payload } = action; const { payload } = action;
const available: Array<OauthAppResponse> = [...state.available]; const available = [...state.available];
let index = available.findIndex((app) => app.clientId === payload.clientId); let index = available.findIndex((app) => app.clientId === payload.clientId);
if (index === -1) { if (index === -1) {

View File

@ -10,10 +10,16 @@ import { ContactLink } from 'components/contact';
import styles from './footerMenu.scss'; import styles from './footerMenu.scss';
import messages from './footerMenu.intl.json'; import messages from './footerMenu.intl.json';
class FooterMenu extends Component<{ type OwnProps = {|
createContactPopup: () => void, createContactPopup: () => void,
|};
type Props = {
...OwnProps,
createLanguageSwitcherPopup: () => void, createLanguageSwitcherPopup: () => void,
}> { };
class FooterMenu extends Component<Props> {
static displayName = 'FooterMenu'; static displayName = 'FooterMenu';
render() { render() {
@ -50,6 +56,6 @@ class FooterMenu extends Component<{
// mark this component, as not pure, because it is stateless, // mark this component, as not pure, because it is stateless,
// but should be re-rendered, if current lang was changed // but should be re-rendered, if current lang was changed
export default connect(null, { export default connect<Props, OwnProps, _, _, _, _>(null, {
createLanguageSwitcherPopup: () => createPopup(LanguageSwitcher), createLanguageSwitcherPopup: () => createPopup({ Popup: LanguageSwitcher }),
}, null, {pure: false})(FooterMenu); }, null, {pure: false})(FooterMenu);

View File

@ -2,11 +2,13 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { FormattedMessage as Message, intlShape } from 'react-intl'; import { FormattedMessage as Message, intlShape } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { connect } from 'react-redux';
import { changeLang } from 'components/user/actions';
import LANGS from 'i18n/index.json'; import LANGS from 'i18n/index.json';
import formStyles from 'components/ui/form/form.scss'; import formStyles from 'components/ui/form/form.scss';
import popupStyles from 'components/ui/popup/popup.scss'; import popupStyles from 'components/ui/popup/popup.scss';
import icons from 'components/ui/icons.scss'; import icons from 'components/ui/icons.scss';
import styles from './languageSwitcher.scss'; import styles from './languageSwitcher.scss';
import messages from './languageSwitcher.intl.json'; import messages from './languageSwitcher.intl.json';
import LanguageList from './LanguageList'; import LanguageList from './LanguageList';
@ -23,21 +25,27 @@ export type LocaleData = {
export type LocalesMap = {[code: string]: LocaleData}; export type LocalesMap = {[code: string]: LocaleData};
class LanguageSwitcher extends Component<{ type OwnProps = {|
onClose: Function, onClose: () => void,
selectedLocale: string,
changeLang: (lang: string) => void,
langs: LocalesMap, langs: LocalesMap,
emptyCaptions: Array<{ emptyCaptions: Array<{
src: string, src: string,
caption: string, caption: string,
}>, }>,
}, { |};
type Props = {
...OwnProps,
selectedLocale: string,
changeLang: (lang: string) => void,
};
class LanguageSwitcher extends Component<Props, {
filter: string, filter: string,
filteredLangs: LocalesMap, filteredLangs: LocalesMap,
}> { }> {
static contextTypes = { static contextTypes = {
intl: intlShape.isRequired, intl: intlShape,
}; };
state = { state = {
@ -108,7 +116,7 @@ class LanguageSwitcher extends Component<{
onChangeLang = this.changeLang.bind(this); onChangeLang = this.changeLang.bind(this);
changeLang(lang) { changeLang(lang: string) {
this.props.changeLang(lang); this.props.changeLang(lang);
setTimeout(this.props.onClose, 300); setTimeout(this.props.onClose, 300);
@ -153,10 +161,7 @@ class LanguageSwitcher extends Component<{
} }
} }
import { connect } from 'react-redux'; export default connect<Props, OwnProps, _, _, _, _>((state) => ({
import { changeLang } from 'components/user/actions';
export default connect((state) => ({
selectedLocale: state.i18n.locale, selectedLocale: state.i18n.locale,
}), { }), {
changeLang, changeLang,

View File

@ -3,17 +3,27 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { localeFlags } from 'components/i18n'; import { localeFlags } from 'components/i18n';
import LANGS from 'i18n/index.json'; import LANGS from 'i18n/index.json';
import { connect } from 'react-redux';
import { create as createPopup } from 'components/ui/popup/actions';
import LanguageSwitcher from 'components/languageSwitcher';
import styles from './link.scss'; import styles from './link.scss';
type OwnProps = {|
|};
type Props = {
...OwnProps,
userLang: string;
interfaceLocale: string;
showLanguageSwitcherPopup: Function;
};
function LanguageLink({ function LanguageLink({
userLang, userLang,
interfaceLocale, interfaceLocale,
showLanguageSwitcherPopup, showLanguageSwitcherPopup,
}: { }: Props) {
userLang: string;
interfaceLocale: string;
showLanguageSwitcherPopup: Function;
}) {
const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale]; const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale];
return ( return (
@ -31,13 +41,9 @@ function LanguageLink({
); );
} }
import { connect } from 'react-redux'; export default connect<Props, OwnProps, _, _, _, _>((state) => ({
import { create as createPopup } from 'components/ui/popup/actions';
import LanguageSwitcher from 'components/languageSwitcher';
export default connect((state) => ({
userLang: state.user.lang, userLang: state.user.lang,
interfaceLocale: state.i18n.locale, interfaceLocale: state.i18n.locale,
}), { }), {
showLanguageSwitcherPopup: () => createPopup(LanguageSwitcher), showLanguageSwitcherPopup: () => createPopup({ Popup: LanguageSwitcher }),
})(LanguageLink); })(LanguageLink);

View File

@ -13,10 +13,16 @@ import RulesPage from 'pages/rules/RulesPage';
import type { User } from 'components/user'; import type { User } from 'components/user';
class Profile extends Component<{ type OwnProps = {|
|};
type Props = {
...OwnProps,
user: User; user: User;
interfaceLocale: string; interfaceLocale: string;
}> { };
class Profile extends Component<Props> {
UUID: ?HTMLElement; UUID: ?HTMLElement;
render() { render() {
@ -150,10 +156,7 @@ class Profile extends Component<{
} }
} }
export default connect(({ user, i18n }): { export default connect<Props, OwnProps, _, _, _, _>(({ user, i18n }) => ({
user: User;
interfaceLocale: string;
} => ({
user, user,
interfaceLocale: i18n.locale, interfaceLocale: i18n.locale,
}))(Profile); }))(Profile);

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';

View File

@ -8,7 +8,7 @@ const ABORT_ERR = 20;
export default function BsodMiddleware(dispatchBsod: Function, logger: Logger) { export default function BsodMiddleware(dispatchBsod: Function, logger: Logger) {
return { return {
catch<T: Resp<*> | InternalServerError | Error>(resp?: T): Promise<T> { catch<T: Resp<any> | InternalServerError | Error>(resp?: T): Promise<T> {
const originalResponse: Object = (resp && resp.originalResponse) || {}; const originalResponse: Object = (resp && resp.originalResponse) || {};
if ( if (

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import BsodMiddleware from 'components/ui/bsod/BsodMiddleware'; import BsodMiddleware from 'components/ui/bsod/BsodMiddleware';

View File

@ -16,7 +16,7 @@ export default class Captcha extends FormInputComponent<{
}, { }, {
code: string, code: string,
}> { }> {
el: ?HTMLDivElement; elRef = React.createRef<HTMLDivElement>();
captchaId: CaptchaID; captchaId: CaptchaID;
static defaultProps = { static defaultProps = {
@ -26,7 +26,9 @@ export default class Captcha extends FormInputComponent<{
componentDidMount() { componentDidMount() {
setTimeout(() => { setTimeout(() => {
this.el && captcha.render(this.el, { const {current: el} = this.elRef;
el && captcha.render(el, {
skin: this.props.skin, skin: this.props.skin,
onSetCode: this.setCode onSetCode: this.setCode
}) })
@ -48,7 +50,7 @@ export default class Captcha extends FormInputComponent<{
<ComponentLoader /> <ComponentLoader />
</div> </div>
<div ref={this.setEl} className={classNames( <div ref={this.elRef} className={classNames(
styles.captcha, styles.captcha,
styles[`${skin}Captcha`] styles[`${skin}Captcha`]
)} /> )} />

View File

@ -19,6 +19,8 @@ export default class Checkbox extends FormInputComponent<{
skin: SKIN_DARK, skin: SKIN_DARK,
}; };
elRef = React.createRef<HTMLInputElement>();
render() { render() {
const { color, skin } = this.props; const { color, skin } = this.props;
let { label } = this.props; let { label } = this.props;
@ -30,7 +32,7 @@ export default class Checkbox extends FormInputComponent<{
return ( return (
<div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}> <div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
<label className={styles.markableContainer}> <label className={styles.markableContainer}>
<input ref={this.setEl} <input ref={this.elRef}
className={styles.markableInput} className={styles.markableInput}
type="checkbox" type="checkbox"
{...props} {...props}
@ -44,13 +46,13 @@ export default class Checkbox extends FormInputComponent<{
} }
getValue() { getValue() {
const { el } = this; const { current: el } = this.elRef;
return el && el.checked ? 1 : 0; return el && el.checked ? 1 : 0;
} }
focus() { focus() {
const { el } = this; const { current: el } = this.elRef;
el && el.focus(); el && el.focus();
} }

View File

@ -1,22 +1,20 @@
// @flow // @flow
import type { Node } from 'react';
import React, { Component } from 'react'; import React, { Component } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import logger from 'services/logger'; import logger from 'services/logger';
import type FormModel from './FormModel';
import styles from './form.scss'; import styles from './form.scss';
import type FormModel from './FormModel'; type Props = {|
type Props = {
id: string, id: string,
isLoading: bool, isLoading: bool,
form?: FormModel, form?: FormModel,
onSubmit: Function, onSubmit: (form: FormModel | FormData) => void | Promise<void>,
onInvalid: (errors: {[errorKey: string]: string}) => void, onInvalid: (errors: {[errorKey: string]: string}) => void,
children: * children: Node
}; |};
type State = { type State = {
isTouched: bool, isTouched: bool,
isLoading: bool isLoading: bool
@ -130,7 +128,7 @@ export default class Form extends Component<Props, State> {
); );
} }
} else { } else {
const invalidEls = form.querySelectorAll(':invalid'); const invalidEls: NodeList<InputElement> = (form.querySelectorAll(':invalid'): any);
const errors = {}; const errors = {};
invalidEls[0].focus(); // focus on first error invalidEls[0].focus(); // focus on first error

View File

@ -5,7 +5,7 @@ import { intlShape } from 'react-intl';
export default class FormComponent<P, S = void> extends Component<P, S> { export default class FormComponent<P, S = void> extends Component<P, S> {
static contextTypes = { static contextTypes = {
intl: intlShape.isRequired intl: intlShape,
}; };
/** /**

View File

@ -12,8 +12,6 @@ export default class FormInputComponent<P, S = void> extends FormComponent<P & {
}, S & { }, S & {
error?: Error, error?: Error,
}> { }> {
el: ?HTMLDivElement;
componentWillReceiveProps() { componentWillReceiveProps() {
if (this.state && this.state.error) { if (this.state && this.state.error) {
Reflect.deleteProperty(this.state, 'error'); Reflect.deleteProperty(this.state, 'error');
@ -22,10 +20,6 @@ export default class FormInputComponent<P, S = void> extends FormComponent<P & {
} }
} }
setEl = (el: ?HTMLDivElement) => {
this.el = el;
};
renderError() { renderError() {
const error = this.state && this.state.error || this.props.error; const error = this.state && this.state.error || this.props.error;

View File

@ -1,10 +1,12 @@
// @flow // @flow
import FormInputComponent from './FormInputComponent'; import FormInputComponent from './FormInputComponent';
type LoadingListener = (isLoading: bool) => void;
export default class FormModel { export default class FormModel {
fields = {}; fields = {};
errors = {}; errors = {};
handlers = []; handlers: LoadingListener[] = [];
renderErrors: bool; renderErrors: bool;
_isLoading: bool; _isLoading: bool;
@ -145,7 +147,7 @@ export default class FormModel {
* *
* @return {object} * @return {object}
*/ */
serialize() { serialize(): {[key: string]: any} {
return Object.keys(this.fields).reduce((acc, fieldId) => { return Object.keys(this.fields).reduce((acc, fieldId) => {
const field = this.fields[fieldId]; const field = this.fields[fieldId];
@ -164,7 +166,7 @@ export default class FormModel {
* *
* @param {function} fn * @param {function} fn
*/ */
addLoadingListener(fn: Function) { addLoadingListener(fn: LoadingListener) {
this.removeLoadingListener(fn); this.removeLoadingListener(fn);
this.handlers.push(fn); this.handlers.push(fn);
} }
@ -174,7 +176,7 @@ export default class FormModel {
* *
* @param {function} fn * @param {function} fn
*/ */
removeLoadingListener(fn: Function) { removeLoadingListener(fn: LoadingListener) {
this.handlers = this.handlers.filter((handler) => handler !== fn); this.handlers = this.handlers.filter((handler) => handler !== fn);
} }

View File

@ -37,6 +37,8 @@ export default class Input extends FormInputComponent<{
wasCopied: false, wasCopied: false,
}; };
elRef = React.createRef<HTMLInputElement>();
render() { render() {
const { color, skin, center } = this.props; const { color, skin, center } = this.props;
let { icon, label, copy } = this.props; let { icon, label, copy } = this.props;
@ -89,7 +91,7 @@ export default class Input extends FormInputComponent<{
<div className={baseClass}> <div className={baseClass}>
{label} {label}
<div className={styles.textFieldContainer}> <div className={styles.textFieldContainer}>
<input ref={this.setEl} <input ref={this.elRef}
className={classNames( className={classNames(
styles[`${skin}TextField`], styles[`${skin}TextField`],
styles[`${color}TextField`], styles[`${color}TextField`],
@ -108,11 +110,13 @@ export default class Input extends FormInputComponent<{
} }
getValue() { getValue() {
return this.el && this.el.value; const { current: el } = this.elRef;
return el && el.value;
} }
focus() { focus() {
const el = this.el; const { current: el } = this.elRef;
if (!el) { if (!el) {
return; return;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import expect from 'unexpected'; import expect from 'test/unexpected';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import Input from './Input'; import Input from './Input';

View File

@ -1,12 +1,15 @@
// @flow // @flow
import type { ElementProps } from 'react'; import type { ElementConfig } from 'react';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Button from './Button'; import Button from './Button';
export default function LinkButton( export default function LinkButton(
props: ElementProps<typeof Button> & ElementProps<typeof Link> props: {
...$Exact<ElementConfig<typeof Button>>,
...$Exact<ElementConfig<typeof Link>>
}
) { ) {
const { to, ...restProps } = props; const { to, ...restProps } = props;

View File

@ -19,6 +19,8 @@ export default class Radio extends FormInputComponent<{
skin: SKIN_DARK, skin: SKIN_DARK,
}; };
elRef = React.createRef<HTMLInputElement>();
render() { render() {
const { color, skin } = this.props; const { color, skin } = this.props;
let { label } = this.props; let { label } = this.props;
@ -30,7 +32,7 @@ export default class Radio extends FormInputComponent<{
return ( return (
<div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}> <div className={classNames(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
<label className={styles.markableContainer}> <label className={styles.markableContainer}>
<input ref={this.setEl} <input ref={this.elRef}
className={styles.markableInput} className={styles.markableInput}
type="radio" type="radio"
{...props} {...props}
@ -44,13 +46,13 @@ export default class Radio extends FormInputComponent<{
} }
getValue() { getValue() {
const { el } = this; const { current: el } = this.elRef;
return el && el.checked ? 1 : 0; return el && el.checked ? 1 : 0;
} }
focus() { focus() {
const { el } = this; const { current: el } = this.elRef;
el && el.focus(); el && el.focus();
} }

View File

@ -30,6 +30,8 @@ export default class TextArea extends FormInputComponent<{
skin: SKIN_DARK, skin: SKIN_DARK,
}; };
elRef = React.createRef<HTMLTextAreaElement>();
render() { render() {
const { color, skin } = this.props; const { color, skin } = this.props;
let { label } = this.props; let { label } = this.props;
@ -59,7 +61,7 @@ export default class TextArea extends FormInputComponent<{
<div className={styles.formRow}> <div className={styles.formRow}>
{label} {label}
<div className={styles.textAreaContainer}> <div className={styles.textAreaContainer}>
<TextareaAutosize inputRef={this.setEl} <TextareaAutosize inputRef={this.elRef}
className={classNames( className={classNames(
styles.textArea, styles.textArea,
styles[`${skin}TextField`], styles[`${skin}TextField`],
@ -74,11 +76,13 @@ export default class TextArea extends FormInputComponent<{
} }
getValue() { getValue() {
return this.el && this.el.value; const { current: el } = this.elRef;
return el && el.value;
} }
focus() { focus() {
const { el } = this; const { current: el } = this.elRef;
if (!el) { if (!el) {
return; return;

View File

@ -1,19 +1,20 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { CSSTransitionGroup } from 'react-transition-group';
import { browserHistory } from 'services/history'; import { browserHistory } from 'services/history';
import { connect } from 'react-redux';
import { destroy } from 'components/ui/popup/actions';
import styles from './popup.scss'; import styles from './popup.scss';
export class PopupStack extends Component { export class PopupStack extends Component {
static displayName = 'PopupStack';
static propTypes = { static propTypes = {
popups: PropTypes.arrayOf(PropTypes.shape({ popups: PropTypes.arrayOf(
type: PropTypes.func, PropTypes.shape({
props: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) type: PropTypes.func,
})), props: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
})
),
destroy: PropTypes.func.isRequired destroy: PropTypes.func.isRequired
}; };
@ -28,31 +29,34 @@ export class PopupStack extends Component {
} }
render() { render() {
const {popups} = this.props; const { popups } = this.props;
return ( return (
<CSSTransitionGroup <TransitionGroup>
transitionName={{
enter: styles.trEnter,
enterActive: styles.trEnterActive,
leave: styles.trLeave,
leaveActive: styles.trLeaveActive
}}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{popups.map((popup, index) => { {popups.map((popup, index) => {
const {Popup} = popup; const { Popup } = popup;
return ( return (
<div className={styles.overlay} key={index} <CSSTransition
onClick={this.onOverlayClick(popup)} key={index}
classNames={{
enter: styles.trEnter,
enterActive: styles.trEnterActive,
exit: styles.trExit,
exitActive: styles.trExitActive
}}
timeout={500}
> >
<Popup onClose={this.onClose(popup)} /> <div
</div> className={styles.overlay}
onClick={this.onOverlayClick(popup)}
>
<Popup onClose={this.onClose(popup)} />
</div>
</CSSTransition>
); );
})} })}
</CSSTransitionGroup> </TransitionGroup>
); );
} }
@ -62,7 +66,10 @@ export class PopupStack extends Component {
onOverlayClick(popup) { onOverlayClick(popup) {
return (event) => { return (event) => {
if (event.target !== event.currentTarget || popup.disableOverlayClose) { if (
event.target !== event.currentTarget
|| popup.disableOverlayClose
) {
return; return;
} }
@ -81,7 +88,8 @@ export class PopupStack extends Component {
} }
onKeyPress = (event) => { onKeyPress = (event) => {
if (event.which === 27) { // ESC key if (event.which === 27) {
// ESC key
this.popStack(); this.popStack();
} }
}; };
@ -93,11 +101,11 @@ export class PopupStack extends Component {
}; };
} }
import { connect } from 'react-redux'; export default connect(
import { destroy } from 'components/ui/popup/actions'; (state) => ({
...state.popup
export default connect((state) => ({ }),
...state.popup {
}), { destroy
destroy }
})(PopupStack); )(PopupStack);

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'unexpected'; import expect from 'test/unexpected';
import React from 'react'; import React from 'react';

View File

@ -1,5 +1,6 @@
// @flow // @flow
import type { ElementType } from 'react'; import type { ElementType } from 'react';
export const POPUP_CREATE = 'POPUP_CREATE'; export const POPUP_CREATE = 'POPUP_CREATE';
/** /**
@ -9,16 +10,10 @@ export const POPUP_CREATE = 'POPUP_CREATE';
* *
* @return {object} * @return {object}
*/ */
export function create(payload: ElementType | { export function create(payload: {
Popup: ElementType, Popup: ElementType,
disableOverlayClose?: bool, disableOverlayClose?: bool
}) { }) {
if (typeof payload === 'function') {
payload = {
Popup: payload
};
}
return { return {
type: POPUP_CREATE, type: POPUP_CREATE,
payload payload

View File

@ -141,7 +141,7 @@ $popupInitPosition: translateY(10%) rotateX(-8deg);
} }
} }
.trLeave { .trExit {
opacity: 1; opacity: 1;
overflow: hidden; overflow: hidden;
@ -163,7 +163,7 @@ $popupInitPosition: translateY(10%) rotateX(-8deg);
} }
.trEnter, .trEnter,
.trLeave { .trExit {
.close { .close {
// do not show close during transition, because transform forces position: fixed // do not show close during transition, because transform forces position: fixed
// to layout relative container, instead of body // to layout relative container, instead of body

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import reducer from 'components/ui/popup/reducer'; import reducer from 'components/ui/popup/reducer';
import {create, destroy} from 'components/ui/popup/actions'; import {create, destroy} from 'components/ui/popup/actions';
@ -22,14 +22,6 @@ describe('popup/reducer', () => {
}); });
}); });
it('should support shortcut popup creation', () => {
const actual = reducer(undefined, create(FakeComponent));
expect(actual.popups[0], 'to equal', {
Popup: FakeComponent
});
});
it('should create multiple popups', () => { it('should create multiple popups', () => {
let actual = reducer(undefined, create({ let actual = reducer(undefined, create({
Popup: FakeComponent Popup: FakeComponent
@ -44,7 +36,7 @@ describe('popup/reducer', () => {
}); });
it('throws when no type provided', () => { it('throws when no type provided', () => {
expect(() => reducer(undefined, create()), 'to throw', 'Popup is required'); expect(() => reducer(undefined, create({})), 'to throw', 'Popup is required');
}); });
}); });
@ -53,7 +45,7 @@ describe('popup/reducer', () => {
let popup; let popup;
beforeEach(() => { beforeEach(() => {
state = reducer(state, create(FakeComponent)); state = reducer(state, create({ Popup: FakeComponent }));
popup = state.popups[0]; popup = state.popups[0];
}); });

View File

@ -1,10 +1,11 @@
// @flow // @flow
import type { Location } from 'react-router-dom';
import React from 'react'; import React from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { restoreScroll } from './scroll'; import { restoreScroll } from './scroll';
class ScrollIntoView extends React.Component<{ class ScrollIntoView extends React.PureComponent<{
location: string, location: Location,
top?: bool, // do not touch any DOM and simply scroll to top on location change top?: bool, // do not touch any DOM and simply scroll to top on location change
}> { }> {
componentDidMount() { componentDidMount() {

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import bearerHeaderMiddleware from 'components/user/middlewares/bearerHeaderMiddleware'; import bearerHeaderMiddleware from 'components/user/middlewares/bearerHeaderMiddleware';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware'; import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'unexpected'; import expect from 'test/unexpected';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import authFlow from 'services/authFlow'; import authFlow from 'services/authFlow';
@ -18,15 +17,13 @@ describe('AuthFlowRouteContents', () => {
}); });
function Component() { function Component() {
return ( return <div />;
<div />
);
} }
it('should render component if route allowed', () => { it('should render component if route allowed', () => {
const request = { const request = {
path: '/path', path: '/path',
params: {foo: 1}, params: { foo: 1 },
query: new URLSearchParams() query: new URLSearchParams()
}; };
@ -36,23 +33,25 @@ describe('AuthFlowRouteContents', () => {
query: request.query query: request.query
}, },
match: { match: {
params: request.params, params: request.params
} }
}; };
authFlow.handleRequest.callsArg(2); authFlow.handleRequest.callsArg(2);
const wrapper = mount(<AuthFlowRouteContents const wrapper = mount(
routerProps={routerProps} <AuthFlowRouteContents
component={Component} routerProps={routerProps}
/>); component={Component}
/>
);
const component = wrapper.find(Component); const component = wrapper.find(Component);
expect(authFlow.handleRequest, 'to have a call satisfying', [ expect(authFlow.handleRequest, 'to have a call satisfying', [
request, request,
function() {}, expect.it('to be a function'),
function() {} expect.it('to be a function')
]); ]);
expect(component.exists(), 'to be true'); expect(component.exists(), 'to be true');

View File

@ -1,26 +1,35 @@
// @flow // @flow
import type { ComponentType, ElementConfig } from 'react';
import type { Account } from 'components/accounts';
import type { Location } from 'react-router-dom';
import React from 'react'; import React from 'react';
import { Route, Redirect } from 'react-router-dom'; import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getActiveAccount } from 'components/accounts/reducer'; import { getActiveAccount } from 'components/accounts/reducer';
import type { ComponentType } from 'react';
import type { Account } from 'components/accounts';
const PrivateRoute = ({account, component: Component, ...rest}: { type OwnProps = {|
...$Exact<ElementConfig<typeof Route>>,
component: ComponentType<any>, component: ComponentType<any>,
|};
type Props = {
...OwnProps,
account: ?Account account: ?Account
}) => ( };
<Route {...rest} render={(props: {location: string}) => (
!account || !account.token ? ( const PrivateRoute = ({ account, component: Component, ...rest }: Props) => (
<Redirect to="/login" /> <Route
) : ( {...rest}
<Component {...props}/> render={(props: { location: Location }) =>
) !account || !account.token ? (
)}/> <Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
); );
export default connect((state): { export default connect<Props, OwnProps, _, _, _, _>((state) => ({
account: ?Account,
} => ({
account: getActiveAccount(state) account: getActiveAccount(state)
}))(PrivateRoute); }))(PrivateRoute);

View File

@ -18,6 +18,8 @@ import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
import RecoverPassword from 'components/auth/recoverPassword/RecoverPassword'; import RecoverPassword from 'components/auth/recoverPassword/RecoverPassword';
import Mfa from 'components/auth/mfa/Mfa'; import Mfa from 'components/auth/mfa/Mfa';
import Finish from 'components/auth/finish/Finish'; import Finish from 'components/auth/finish/Finish';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import styles from './auth.scss'; import styles from './auth.scss';
@ -27,13 +29,19 @@ import styles from './auth.scss';
// so that it persist disregarding remounts // so that it persist disregarding remounts
let isSidebarHiddenCache = false; let isSidebarHiddenCache = false;
class AuthPage extends Component<{ type OwnProps = {|
|};
type Props = {
...OwnProps,
client: { client: {
id: string, id: string,
name: string, name: string,
description: string description: string
} }
}, { };
class AuthPage extends Component<Props, {
isSidebarHidden: bool isSidebarHidden: bool
}> { }> {
state = { state = {
@ -91,20 +99,10 @@ function renderPanelTransition(factory) {
Body={<Body {...props} />} Body={<Body {...props} />}
Footer={<Footer />} Footer={<Footer />}
Links={<Links />} Links={<Links />}
{...props}
/> />
); );
} }
import { connect } from 'react-redux'; export default withRouter(connect<Props, OwnProps, _, _, _, _>((state) => ({
import { withRouter } from 'react-router';
export default withRouter(connect((state): {
client: {
id: string,
name: string,
description: string
}
} => ({
client: state.auth.client client: state.auth.client
}))(AuthPage)); }))(AuthPage));

View File

@ -1,5 +1,5 @@
// @flow // @flow
import type { Location, RouterHistory } from 'react-router'; import type { Location, RouterHistory } from 'react-router-dom';
import type { User } from 'components/user'; import type { User } from 'components/user';
import type { OauthAppResponse } from 'services/api/oauth'; import type { OauthAppResponse } from 'services/api/oauth';
import React, { Component } from 'react'; import React, { Component } from 'react';
@ -11,17 +11,21 @@ import {
} from 'components/dev/apps/actions'; } from 'components/dev/apps/actions';
import ApplicationsIndex from 'components/dev/apps/ApplicationsIndex'; import ApplicationsIndex from 'components/dev/apps/ApplicationsIndex';
interface Props { type OwnProps = {|
location: Location; location: Location;
history: RouterHistory; history: RouterHistory;
|};
type Props = {
...OwnProps,
user: User; user: User;
apps: Array<OauthAppResponse>; apps: OauthAppResponse[];
fetchAvailableApps: () => Promise<void>; fetchAvailableApps: () => Promise<void>;
deleteApp: string => Promise<void>; deleteApp: string => Promise<void>;
resetApp: (string, bool) => Promise<void>; resetApp: (string, bool) => Promise<void>;
} }
interface State { type State = {
isLoading: bool; isLoading: bool;
forceUpdate: bool; forceUpdate: bool;
} }
@ -36,7 +40,7 @@ class ApplicationsListPage extends Component<Props, State> {
!this.props.user.isGuest && this.loadApplicationsList(); !this.props.user.isGuest && this.loadApplicationsList();
} }
componentDidUpdate({ user }) { componentDidUpdate({ user }: Props) {
if (this.props.user !== user) { if (this.props.user !== user) {
// eslint-disable-next-line react/no-did-update-set-state // eslint-disable-next-line react/no-did-update-set-state
this.setState({ forceUpdate: true }); this.setState({ forceUpdate: true });
@ -80,10 +84,12 @@ class ApplicationsListPage extends Component<Props, State> {
}; };
} }
export default connect((state) => ({ export default connect<Props, OwnProps, _, _, _, _>((state) => ({
user: state.user, user: state.user,
apps: state.apps.available, apps: state.apps.available,
}), { }),
// $FlowFixMe: we need a better action typings for thunks
{
fetchAvailableApps, fetchAvailableApps,
resetApp, resetApp,
deleteApp, deleteApp,

View File

@ -11,18 +11,21 @@ import PageNotFound from 'pages/404/PageNotFound';
import { getApp, fetchApp } from 'components/dev/apps/actions'; import { getApp, fetchApp } from 'components/dev/apps/actions';
import ApplicationForm from 'components/dev/apps/applicationForm/ApplicationForm'; import ApplicationForm from 'components/dev/apps/applicationForm/ApplicationForm';
type MatchType = { type OwnProps = {|
match: { match: {
params: { params: {
clientId: string, clientId: string,
}, },
}, },
}; |}
class UpdateApplicationPage extends Component<{ type Props = {
...OwnProps,
app: ?OauthAppResponse, app: ?OauthAppResponse,
fetchApp: (string) => Promise<void>, fetchApp: (string) => Promise<void>,
} & MatchType, { }
class UpdateApplicationPage extends Component<Props, {
isNotFound: bool, isNotFound: bool,
}> { }> {
form: FormModel = new FormModel(); form: FormModel = new FormModel();
@ -104,7 +107,7 @@ class UpdateApplicationPage extends Component<{
goToMainPage = (hash?: string) => browserHistory.push(`/dev/applications${hash ? `#${hash}` : ''}`); goToMainPage = (hash?: string) => browserHistory.push(`/dev/applications${hash ? `#${hash}` : ''}`);
} }
export default connect((state, props: MatchType) => ({ export default connect<Props, OwnProps, _, _, _, _>((state, props) => ({
app: getApp(state, props.match.params.clientId), app: getApp(state, props.match.params.clientId),
}), { }), {
fetchApp, fetchApp,

View File

@ -1,15 +1,13 @@
// @flow // @flow
import type { RouterHistory, Match } from 'react-router'; import type { RouterHistory, Match } from 'react-router-dom';
import type FormModel from 'components/ui/form/FormModel';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux';
import ChangeEmail from 'components/profile/changeEmail/ChangeEmail'; import ChangeEmail from 'components/profile/changeEmail/ChangeEmail';
import { requestEmailChange, setNewEmail, confirmNewEmail } from 'services/api/accounts'; import { requestEmailChange, setNewEmail, confirmNewEmail } from 'services/api/accounts';
interface Props { type OwnProps = {|
lang: string;
email: string;
history: RouterHistory; history: RouterHistory;
match: { match: {
...Match; ...Match;
@ -18,6 +16,12 @@ interface Props {
code: string; code: string;
}; };
}; };
|};
type Props = {
...OwnProps,
lang: string;
email: string;
} }
class ChangeEmailPage extends Component<Props> { class ChangeEmailPage extends Component<Props> {
@ -53,11 +57,11 @@ class ChangeEmailPage extends Component<Props> {
); );
} }
onChangeStep = (step) => { onChangeStep = (step: number) => {
this.props.history.push(`/profile/change-email/step${++step}`); this.props.history.push(`/profile/change-email/step${++step}`);
}; };
onSubmit = (step: number, form) => { onSubmit = (step: number, form: FormModel) => {
return this.context.onSubmit({ return this.context.onSubmit({
form, form,
sendData: () => { sendData: () => {
@ -102,10 +106,7 @@ function handleErrors(repeatUrl) {
}; };
} }
import { connect } from 'react-redux'; export default connect<Props, OwnProps, _, _, _, _>((state) => ({
export default connect((state) => ({
email: state.user.email, email: state.user.email,
lang: state.user.lang lang: state.user.lang
}), { }))(ChangeEmailPage);
})(ChangeEmailPage);

View File

@ -7,7 +7,11 @@ import { changePassword } from 'services/api/accounts';
import { FormModel } from 'components/ui/form'; import { FormModel } from 'components/ui/form';
import ChangePassword from 'components/profile/changePassword/ChangePassword'; import ChangePassword from 'components/profile/changePassword/ChangePassword';
interface Props { type OwnProps = {|
|};
type Props = {
...OwnProps,
updateUser: (fields: $Shape<User>) => void; updateUser: (fields: $Shape<User>) => void;
} }
@ -45,6 +49,6 @@ class ChangePasswordPage extends Component<Props> {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { updateUser } from 'components/user/actions'; import { updateUser } from 'components/user/actions';
export default connect(null, { export default connect<Props, OwnProps, _, _, _, _>(null, {
updateUser, updateUser,
})(ChangePasswordPage); })(ChangePasswordPage);

View File

@ -6,14 +6,15 @@ import { changeUsername } from 'services/api/accounts';
import { FormModel } from 'components/ui/form'; import { FormModel } from 'components/ui/form';
import ChangeUsername from 'components/profile/changeUsername/ChangeUsername'; import ChangeUsername from 'components/profile/changeUsername/ChangeUsername';
interface Props { type OwnProps = {|
|};
type Props = {
username: string; username: string;
updateUsername: (username: string) => void; updateUsername: (username: string) => void;
} };
class ChangeUsernamePage extends Component<Props> { class ChangeUsernamePage extends Component<Props> {
static displayName = 'ChangeUsernamePage';
static contextTypes = { static contextTypes = {
userId: PropTypes.number.isRequired, userId: PropTypes.number.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
@ -42,7 +43,7 @@ class ChangeUsernamePage extends Component<Props> {
); );
} }
onUsernameChange = (username) => { onUsernameChange = (username: string) => {
this.props.updateUsername(username); this.props.updateUsername(username);
}; };
@ -70,7 +71,7 @@ class ChangeUsernamePage extends Component<Props> {
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { updateUser } from 'components/user/actions'; import { updateUser } from 'components/user/actions';
export default connect((state) => ({ export default connect<Props, OwnProps, _, _, _, _>((state) => ({
username: state.user.username, username: state.user.username,
}), { }), {
updateUsername: (username) => updateUser({username}), updateUsername: (username) => updateUser({username}),

View File

@ -1,5 +1,5 @@
// @flow // @flow
import type { RouterHistory, Match } from 'react-router'; import type { RouterHistory, Match } from 'react-router-dom';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -9,8 +9,7 @@ import type { MfaStep } from 'components/profile/multiFactorAuth';
import type { FormModel } from 'components/ui/form'; import type { FormModel } from 'components/ui/form';
import type { User } from 'components/user'; import type { User } from 'components/user';
interface Props { type OwnProps = {|
user: User;
history: RouterHistory; history: RouterHistory;
match: { match: {
...Match; ...Match;
@ -18,6 +17,11 @@ interface Props {
step?: '1' | '2' | '3'; step?: '1' | '2' | '3';
}; };
}; };
|};
type Props = {
...OwnProps,
user: User;
} }
class MultiFactorAuthPage extends Component<Props> { class MultiFactorAuthPage extends Component<Props> {
@ -86,4 +90,6 @@ class MultiFactorAuthPage extends Component<Props> {
}; };
} }
export default connect(({user}): { user: User } => ({user}))(MultiFactorAuthPage); export default connect<Props, OwnProps, _, _, _, _>(
({user}) => ({user})
)(MultiFactorAuthPage);

View File

@ -20,7 +20,11 @@ import styles from './profile.scss';
import type { FormModel } from 'components/ui/form'; import type { FormModel } from 'components/ui/form';
interface Props { type OwnProps = {|
|};
type Props = {
...OwnProps,
userId: number; userId: number;
onSubmit: ({form: FormModel, sendData: () => Promise<*>}) => void; onSubmit: ({form: FormModel, sendData: () => Promise<*>}) => void;
fetchUserData: () => Promise<*>; fetchUserData: () => Promise<*>;
@ -65,7 +69,7 @@ class ProfilePage extends Component<Props> {
goToProfile = () => browserHistory.push('/'); goToProfile = () => browserHistory.push('/');
} }
export default connect((state) => ({ export default connect<Props, OwnProps, _, _, _, _>((state) => ({
userId: state.user.id, userId: state.user.id,
}), { }), {
fetchUserData, fetchUserData,

View File

@ -4,7 +4,7 @@ import type { Account } from 'components/accounts/reducer';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { resetAuth } from 'components/auth/actions'; import { resetAuth } from 'components/auth/actions';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router-dom';
import { FormattedMessage as Message } from 'react-intl'; import { FormattedMessage as Message } from 'react-intl';
import { Route, Link, Switch } from 'react-router-dom'; import { Route, Link, Switch } from 'react-router-dom';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'unexpected'; import expect from 'test/unexpected';
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import RulesPage from './RulesPage'; import RulesPage from './RulesPage';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
describe('promise.prototype.finally', () => { describe('promise.prototype.finally', () => {
it('should be invoked after promise resolved', () => it('should be invoked after promise resolved', () =>

View File

@ -41,37 +41,37 @@ export function changePassword(id: number, {
}); });
} }
export function acceptRules(id: number) { export function acceptRules(id: number): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/rules`); return request.post(`/api/v1/accounts/${id}/rules`);
} }
export function changeUsername(id: number, username: ?string, password: ?string) { export function changeUsername(id: number, username: ?string, password: ?string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/username`, { return request.post(`/api/v1/accounts/${id}/username`, {
username, username,
password, password,
}); });
} }
export function changeLang(id: number, lang: string) { export function changeLang(id: number, lang: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/language`, { return request.post(`/api/v1/accounts/${id}/language`, {
lang, lang,
}); });
} }
export function requestEmailChange(id: number, password: string) { export function requestEmailChange(id: number, password: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/email-verification`, { return request.post(`/api/v1/accounts/${id}/email-verification`, {
password, password,
}); });
} }
export function setNewEmail(id: number, email: string, key: string) { export function setNewEmail(id: number, email: string, key: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/new-email-verification`, { return request.post(`/api/v1/accounts/${id}/new-email-verification`, {
email, email,
key, key,
}); });
} }
export function confirmNewEmail(id: number, key: string) { export function confirmNewEmail(id: number, key: string): Promise<{ success: bool }> {
return request.post(`/api/v1/accounts/${id}/email`, { return request.post(`/api/v1/accounts/${id}/email`, {
key, key,
}); });

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import request from 'services/request'; import request from 'services/request';

View File

@ -1,5 +1,5 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import request from 'services/request'; import request from 'services/request';

View File

@ -4,6 +4,18 @@ import type { Resp } from 'services/request';
import type { ApplicationType } from 'components/dev/apps'; import type { ApplicationType } from 'components/dev/apps';
import request from 'services/request'; import request from 'services/request';
export type Scope =
| 'minecraft_server_session'
| 'offline_access'
| 'account_info'
| 'account_email';
export type Client = {|
id: string,
name: string,
description: string
|};
export type OauthAppResponse = { export type OauthAppResponse = {
clientId: string, clientId: string,
clientSecret: string, clientSecret: string,
@ -24,7 +36,7 @@ type OauthRequestData = {
redirect_uri: string, redirect_uri: string,
response_type: string, response_type: string,
description: string, description: string,
scope: string, scope: Scope,
prompt: string, prompt: string,
login_hint?: string, login_hint?: string,
state?: string, state?: string,
@ -35,7 +47,7 @@ export type OauthData = {
redirectUrl: string, redirectUrl: string,
responseType: string, responseType: string,
description: string, description: string,
scope: string, scope: Scope,
prompt: 'none' | 'consent' | 'select_account', prompt: 'none' | 'consent' | 'select_account',
loginHint?: string, loginHint?: string,
state?: string state?: string
@ -51,19 +63,28 @@ type FormPayloads = {
const api = { const api = {
validate(oauthData: OauthData) { validate(oauthData: OauthData) {
return request.get( return request.get<{|
session: {|
scopes: Scope[],
|},
client: Client,
oAuth: {||},
|}>(
'/api/oauth2/v1/validate', '/api/oauth2/v1/validate',
getOAuthRequest(oauthData) getOAuthRequest(oauthData)
).catch(handleOauthParamsValidation); ).catch(handleOauthParamsValidation);
}, },
complete(oauthData: OauthData, params: {accept?: bool} = {}): Promise<Resp<{ complete(oauthData: OauthData, params: {accept?: bool} = {}): Promise<{
success: bool, success: bool,
redirectUri: string, redirectUri: string,
}>> { }> {
const query = request.buildQuery(getOAuthRequest(oauthData)); const query = request.buildQuery(getOAuthRequest(oauthData));
return request.post( return request.post<{
success: bool,
redirectUri: string,
}>(
`/api/oauth2/v1/complete?${query}`, `/api/oauth2/v1/complete?${query}`,
typeof params.accept === 'undefined' ? {} : {accept: params.accept} typeof params.accept === 'undefined' ? {} : {accept: params.accept}
).catch((resp = {}) => { ).catch((resp = {}) => {
@ -92,28 +113,28 @@ const api = {
}); });
}, },
create(type: string, formParams: FormPayloads): Promise<Resp<{success: bool, data: OauthAppResponse}>> { create(type: string, formParams: FormPayloads) {
return request.post(`/api/v1/oauth2/${type}`, formParams); return request.post<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${type}`, formParams);
}, },
update(clientId: string, formParams: FormPayloads): Promise<Resp<{success: bool, data: OauthAppResponse}>> { update(clientId: string, formParams: FormPayloads) {
return request.put(`/api/v1/oauth2/${clientId}`, formParams); return request.put<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${clientId}`, formParams);
}, },
getApp(clientId: string): Promise<Resp<OauthAppResponse>> { getApp(clientId: string) {
return request.get(`/api/v1/oauth2/${clientId}`); return request.get<OauthAppResponse>(`/api/v1/oauth2/${clientId}`);
}, },
getAppsByUser(userId: number): Promise<Resp<Array<OauthAppResponse>>> { getAppsByUser(userId: number): Promise<OauthAppResponse[]> {
return request.get(`/api/v1/accounts/${userId}/oauth2/clients`); return request.get(`/api/v1/accounts/${userId}/oauth2/clients`);
}, },
reset(clientId: string, regenerateSecret: bool = false): Promise<Resp<{success: bool, data: OauthAppResponse}>> { reset(clientId: string, regenerateSecret: bool = false) {
return request.post(`/api/v1/oauth2/${clientId}/reset${regenerateSecret ? '?regenerateSecret' : ''}`); return request.post<{success: bool, data: OauthAppResponse}>(`/api/v1/oauth2/${clientId}/reset${regenerateSecret ? '?regenerateSecret' : ''}`);
}, },
delete(clientId: string): Promise<Resp<{success: bool}>> { delete(clientId: string) {
return request.delete(`/api/v1/oauth2/${clientId}`); return request.delete<{success: bool}>(`/api/v1/oauth2/${clientId}`);
}, },
}; };

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import request from 'services/request'; import request from 'services/request';
import options from './options'; import options from './options';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import AuthFlow from 'services/authFlow/AuthFlow'; import AuthFlow from 'services/authFlow/AuthFlow';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import AuthFlow from 'services/authFlow/AuthFlow'; import AuthFlow from 'services/authFlow/AuthFlow';
@ -354,9 +354,9 @@ describe('AuthFlow', () => {
flow.handleRequest(request); flow.handleRequest(request);
expect(flow.getRequest(), 'to equal', { expect(flow.getRequest(), 'to satisfy', {
...request, ...request,
query: new URLSearchParams(), query: expect.it('to be an', URLSearchParams),
params: {} params: {}
}); });
}); });

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import CompleteState from 'services/authFlow/CompleteState'; import CompleteState from 'services/authFlow/CompleteState';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import MfaState from './MfaState'; import MfaState from './MfaState';

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import PasswordState from 'services/authFlow/PasswordState'; import PasswordState from 'services/authFlow/PasswordState';

View File

@ -2,7 +2,7 @@
* A helper wrapper service around window.history * A helper wrapper service around window.history
*/ */
import createBrowserHistory from 'history/createBrowserHistory'; import { createBrowserHistory } from 'history';
export const browserHistory = createBrowserHistory(); export const browserHistory = createBrowserHistory();
@ -11,9 +11,9 @@ browserHistory.listen(() => {
}); });
function patchHistory(history) { function patchHistory(history) {
Object.assign(history.location, Object.assign(history.location, {
{query: new URLSearchParams(history.location.search)} query: new URLSearchParams(history.location.search)
); });
} }
patchHistory(browserHistory); patchHistory(browserHistory);
@ -27,7 +27,10 @@ export default {
* @return {bool} - whether history.back() can be safetly called * @return {bool} - whether history.back() can be safetly called
*/ */
canGoBack() { canGoBack() {
return document.referrer.includes(`${location.protocol}//${location.host}`) return (
|| this.initialLength < window.history.length; document.referrer.includes(
`${location.protocol}//${location.host}`
) || this.initialLength < window.history.length
);
} }
}; };

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'; import sinon from 'sinon';
import expect from 'unexpected'; import expect from 'test/unexpected';
import PromiseMiddlewareLayer from 'services/request/PromiseMiddlewareLayer'; import PromiseMiddlewareLayer from 'services/request/PromiseMiddlewareLayer';

View File

@ -6,8 +6,9 @@ import RequestAbortedError from './RequestAbortedError';
const middlewareLayer = new PromiseMiddlewareLayer(); const middlewareLayer = new PromiseMiddlewareLayer();
export type Resp<T> = { export type Resp<T> = {
...$Exact<T>,
originalResponse: Response originalResponse: Response
} & T; };
type Middleware = { type Middleware = {
before?: () => Promise<*>, before?: () => Promise<*>,

View File

@ -1,4 +1,4 @@
import expect from 'unexpected'; import expect from 'test/unexpected';
import sinon from 'sinon'; import sinon from 'sinon';
import request from 'services/request'; import request from 'services/request';

View File

@ -1,9 +1,7 @@
import 'polyfills'; import 'polyfills';
import { configure } from 'enzyme'; import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'; import Adapter from 'enzyme-adapter-react-16';
import expect from 'unexpected';
expect.use(require('unexpected-sinon'));
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
if (!window.localStorage) { if (!window.localStorage) {

7
src/test/unexpected.js Normal file
View File

@ -0,0 +1,7 @@
import unexpected from 'unexpected';
const expect = unexpected.clone();
expect.use(require('unexpected-sinon'));
export default expect;

2600
yarn.lock

File diff suppressed because it is too large Load Diff