mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-24 05:59:51 +05:30
Migrate from flow to typescript
This commit is contained in:
parent
85a535430e
commit
d8d2df0702
73
.eslintrc.js
73
.eslintrc.js
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
@ -7,12 +7,14 @@ module.exports = {
|
||||
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:flowtype/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
|
||||
plugins: ['react', 'flowtype'],
|
||||
plugins: ['react'],
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
@ -56,7 +58,7 @@ module.exports = {
|
||||
|
||||
// @see: http://eslint.org/docs/rules/
|
||||
rules: {
|
||||
'no-prototype-builtins': ['warn'], // temporary set to warn
|
||||
'no-prototype-builtins': 'warn', // temporary set to warn
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
'localStorage',
|
||||
@ -67,7 +69,7 @@ module.exports = {
|
||||
'error',
|
||||
{ min: 2, exceptions: ['x', 'y', 'i', 'k', 'l', 'm', 'n', '$', '_'] },
|
||||
],
|
||||
'require-atomic-updates': ['warn'],
|
||||
'require-atomic-updates': 'warn',
|
||||
'guard-for-in': ['error'],
|
||||
'no-var': ['error'],
|
||||
'prefer-const': ['error'],
|
||||
@ -76,25 +78,18 @@ module.exports = {
|
||||
'no-multi-assign': ['error'],
|
||||
eqeqeq: ['error'],
|
||||
'prefer-rest-params': ['error'],
|
||||
'prefer-object-spread': ['warn'],
|
||||
'prefer-destructuring': ['warn'],
|
||||
'no-bitwise': ['warn'],
|
||||
'no-negated-condition': ['warn'],
|
||||
'no-nested-ternary': ['warn'],
|
||||
'no-unneeded-ternary': ['warn'],
|
||||
'no-shadow': ['warn'],
|
||||
'no-else-return': ['warn'],
|
||||
radix: ['warn'],
|
||||
'prefer-promise-reject-errors': ['warn'],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'object-shorthand': ['warn'],
|
||||
'prefer-object-spread': 'warn',
|
||||
'prefer-destructuring': 'warn',
|
||||
'no-bitwise': 'warn',
|
||||
'no-negated-condition': 'warn',
|
||||
'no-nested-ternary': 'warn',
|
||||
'no-unneeded-ternary': 'warn',
|
||||
'no-shadow': 'warn',
|
||||
'no-else-return': 'warn',
|
||||
radix: 'warn',
|
||||
'prefer-promise-reject-errors': 'warn',
|
||||
'object-shorthand': 'warn',
|
||||
'require-atomic-updates': 'off',
|
||||
|
||||
// force extra lines around if, else, for, while, switch, return etc
|
||||
'padding-line-between-statements': [
|
||||
@ -134,9 +129,8 @@ module.exports = {
|
||||
'warn',
|
||||
{ eventHandlerPrefix: 'on', eventHandlerPropPrefix: 'on' },
|
||||
],
|
||||
'react/jsx-indent-props': 'warn',
|
||||
'react/jsx-key': 'warn',
|
||||
'react/jsx-max-props-per-line': ['warn', { maximum: 3 }],
|
||||
'react/jsx-max-props-per-line': 'off',
|
||||
'react/jsx-no-bind': 'off',
|
||||
'react/jsx-no-duplicate-props': 'warn',
|
||||
'react/jsx-no-literals': 'off',
|
||||
@ -156,17 +150,24 @@ module.exports = {
|
||||
'react/no-string-refs': 'warn',
|
||||
'react/no-unknown-property': 'warn',
|
||||
'react/prefer-es6-class': 'warn',
|
||||
'react/prop-types': 'off', // using flowtype for this task
|
||||
'react/prop-types': 'off', // using ts for this task
|
||||
'react/self-closing-comp': 'warn',
|
||||
'react/sort-comp': [
|
||||
'off',
|
||||
{ order: ['lifecycle', 'render', 'everything-else'] },
|
||||
],
|
||||
'react/sort-comp': 'off',
|
||||
|
||||
'flowtype/space-after-type-colon': 'off',
|
||||
'flowtype/no-unused-expressions': [
|
||||
'warn',
|
||||
{ allowShortCircuit: true, allowTernary: true },
|
||||
// ts
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
20
.flowconfig
20
.flowconfig
@ -1,20 +0,0 @@
|
||||
[ignore]
|
||||
.*/node_modules/fbjs/lib/.*
|
||||
.*/node_modules/react-motion/lib/.*
|
||||
.*/tests-e2e/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
./flow-typed
|
||||
|
||||
[options]
|
||||
module.system.node.resolve_dirname=node_modules
|
||||
module.system.node.resolve_dirname=src
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.css
|
||||
module.file_ext=.scss
|
||||
module.ignore_non_literal_requires=true
|
||||
esproposal.optional_chaining=enable
|
61
@types/webpack-loaders.d.ts
vendored
Normal file
61
@types/webpack-loaders.d.ts
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
declare module '*.html' {
|
||||
const url: string;
|
||||
export = url;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const url: string;
|
||||
export = url;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const url: string;
|
||||
export = url;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const url: string;
|
||||
export = url;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const url: string;
|
||||
export = url;
|
||||
}
|
||||
|
||||
declare module '*.intl.json' {
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
|
||||
const descriptor: {
|
||||
[key: string]: MessageDescriptor;
|
||||
};
|
||||
|
||||
export = descriptor;
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const jsonContents: {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export = jsonContents;
|
||||
}
|
||||
|
||||
declare module '*.scss' {
|
||||
// TODO: replace with:
|
||||
// https://www.npmjs.com/package/css-modules-typescript-loader
|
||||
// https://github.com/Jimdo/typings-for-css-modules-loader
|
||||
const classNames: {
|
||||
[className: string]: string;
|
||||
};
|
||||
|
||||
export = classNames;
|
||||
}
|
||||
|
||||
declare module '*.css' {
|
||||
const classNames: {
|
||||
[className: string]: string;
|
||||
};
|
||||
|
||||
export = classNames;
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-react', '@babel/preset-flow', ['@babel/preset-env']],
|
||||
presets: [
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
['@babel/preset-env'],
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-proposal-function-bind',
|
||||
@ -28,11 +32,5 @@ module.exports = {
|
||||
],
|
||||
],
|
||||
},
|
||||
development: {
|
||||
presets: [],
|
||||
},
|
||||
test: {
|
||||
presets: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
29
flow-typed/Promise.js
vendored
29
flow-typed/Promise.js
vendored
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* This is a copypasted declaration from
|
||||
* https://github.com/facebook/flow/blob/master/lib/core.js
|
||||
* with addition of finally method
|
||||
*/
|
||||
declare class Promise<+R> {
|
||||
constructor(callback: (
|
||||
resolve: (result: Promise<R> | R) => void,
|
||||
reject: (error: any) => void
|
||||
) => mixed): void;
|
||||
|
||||
then<U>(
|
||||
onFulfill?: (value: R) => Promise<U> | U,
|
||||
onReject?: (error: any) => Promise<U> | U
|
||||
): Promise<U>;
|
||||
|
||||
catch<U>(
|
||||
onReject?: (error: any) => Promise<U> | U
|
||||
): Promise<R | U>;
|
||||
|
||||
static resolve<T>(object: Promise<T> | T): Promise<T>;
|
||||
static reject<T>(error?: any): Promise<T>;
|
||||
static all<Elem, T:Iterable<Elem>>(promises: T): Promise<$TupleMap<T, typeof $await>>;
|
||||
static race<T, Elem: Promise<T> | T>(promises: Array<Elem>): Promise<T>;
|
||||
|
||||
finally<T>(
|
||||
onSettled?: ?(value: any) => Promise<T> | T
|
||||
): Promise<T>;
|
||||
}
|
278
flow-typed/npm/react-intl_v2.x.x.js
vendored
278
flow-typed/npm/react-intl_v2.x.x.js
vendored
@ -1,278 +0,0 @@
|
||||
// flow-typed signature: 3902298e28ed22d8cd8d49828801a760
|
||||
// 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
|
||||
* Copied here based on intention to merge with flow-typed expressed here:
|
||||
* https://github.com/marudor/flowInterfaces/issues/6
|
||||
*/
|
||||
// Mostly from https://github.com/yahoo/react-intl/wiki/API#react-intl-api
|
||||
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,
|
||||
formatRelativeTime: (value: number, options?: $npm$ReactIntl$RelativeFormatOptions & {
|
||||
format: string
|
||||
}) => 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 = {
|
||||
numeric?: "auto" | "always",
|
||||
style?: "short" | "narrow" | "numeric",
|
||||
unit?: "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
|
||||
declare function intlShape(
|
||||
props: Object,
|
||||
propName: string,
|
||||
componentName: string
|
||||
): void;
|
||||
declare function addLocaleData(
|
||||
data: $npm$ReactIntl$LocaleData | Array<$npm$ReactIntl$LocaleData>
|
||||
): void;
|
||||
declare function defineMessages<
|
||||
T: { [key: string]: $Exact<$npm$ReactIntl$MessageDescriptor> }
|
||||
>(
|
||||
messageDescriptors: T
|
||||
): T;
|
||||
|
||||
declare type InjectIntlProvidedProps = {
|
||||
intl: $npm$ReactIntl$IntlShape
|
||||
}
|
||||
|
||||
declare type InjectIntlVoidProps = {
|
||||
intl: $npm$ReactIntl$IntlShape | void
|
||||
}
|
||||
|
||||
declare type ComponentWithDefaultProps<DefaultProps: {}, Props: {}> =
|
||||
| React$ComponentType<Props>
|
||||
| React$StatelessFunctionalComponent<Props>
|
||||
| ChildrenArray<void | null | boolean | string | number | Element<any>>;
|
||||
|
||||
declare type InjectIntlOptions = {
|
||||
intlPropName?: string,
|
||||
withRef?: boolean
|
||||
}
|
||||
|
||||
declare class IntlInjectedComponent<TOwnProps, TDefaultProps> extends React$Component<TOwnProps> {
|
||||
static WrappedComponent: Class<React$Component<TOwnProps & InjectIntlProvidedProps>>,
|
||||
static defaultProps: TDefaultProps,
|
||||
props: TOwnProps
|
||||
}
|
||||
|
||||
declare type IntlInjectedComponentClass<TOwnProps, TDefaultProps: {} = {}> = Class<
|
||||
IntlInjectedComponent<TOwnProps, TDefaultProps>
|
||||
>;
|
||||
|
||||
declare function injectIntl<P: {}, Component: React$ComponentType<P>>(
|
||||
WrappedComponent: Component,
|
||||
options?: InjectIntlOptions,
|
||||
): React$ComponentType<
|
||||
$Diff<React$ElementConfig<Component>, InjectIntlVoidProps>
|
||||
>;
|
||||
|
||||
declare type IntlCache = any;
|
||||
|
||||
declare function createIntlCache(): IntlCache;
|
||||
|
||||
declare function createIntl(options: {
|
||||
locale: string,
|
||||
messages?: {[key: string]: string}
|
||||
}, cache?: IntlCache): IntlShape;
|
||||
|
||||
declare function formatMessage(
|
||||
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
|
||||
values?: Object
|
||||
): string;
|
||||
declare function formatHTMLMessage(
|
||||
messageDescriptor: $npm$ReactIntl$MessageDescriptor,
|
||||
values?: Object
|
||||
): string;
|
||||
declare function formatDate(
|
||||
value: any,
|
||||
options?: $npm$ReactIntl$DateTimeFormatOptions & { format: string }
|
||||
): string;
|
||||
declare function formatTime(
|
||||
value: any,
|
||||
options?: $npm$ReactIntl$DateTimeFormatOptions & { format: string }
|
||||
): string;
|
||||
declare function formatRelativeTime(
|
||||
value: number, // delta
|
||||
options?: $npm$ReactIntl$RelativeFormatOptions & {
|
||||
format: string
|
||||
}
|
||||
): string;
|
||||
declare function formatNumber(
|
||||
value: any,
|
||||
options?: $npm$ReactIntl$NumberFormatOptions & { format: string }
|
||||
): string;
|
||||
declare function formatPlural(
|
||||
value: any,
|
||||
options?: $npm$ReactIntl$PluralFormatOptions
|
||||
): $npm$ReactIntl$PluralCategoryString;
|
||||
|
||||
declare class FormattedMessage extends React$Component<
|
||||
$npm$ReactIntl$MessageDescriptor & {
|
||||
values?: Object,
|
||||
tagName?: string,
|
||||
children?:
|
||||
| ((...formattedMessage: Array<React$Node>) => React$Node)
|
||||
| (string => React$Node)
|
||||
}
|
||||
> {}
|
||||
declare class FormattedHTMLMessage extends React$Component<
|
||||
$npm$ReactIntl$DateTimeFormatOptions & {
|
||||
values?: Object,
|
||||
tagName?: string,
|
||||
children?: (...formattedMessage: Array<React$Node>) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class FormattedDate extends React$Component<
|
||||
$npm$ReactIntl$DateTimeFormatOptions & {
|
||||
value: $npm$ReactIntl$DateParseable,
|
||||
format?: string,
|
||||
children?: (formattedDate: string) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class FormattedTime extends React$Component<
|
||||
$npm$ReactIntl$DateTimeFormatOptions & {
|
||||
value: $npm$ReactIntl$DateParseable,
|
||||
format?: string,
|
||||
children?: (formattedDate: string) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class FormattedRelativeTime extends React$Component<
|
||||
$npm$ReactIntl$RelativeFormatOptions & {
|
||||
value: number, // delta
|
||||
format?: string,
|
||||
updateIntervalInSeconds?: number,
|
||||
children?: (formattedDate: string) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class FormattedNumber extends React$Component<
|
||||
$npm$ReactIntl$NumberFormatOptions & {
|
||||
value: number | string,
|
||||
format?: string,
|
||||
children?: (formattedNumber: string) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class FormattedPlural extends React$Component<
|
||||
$npm$ReactIntl$PluralFormatOptions & {
|
||||
value: number | string,
|
||||
other: React$Node,
|
||||
zero?: React$Node,
|
||||
one?: React$Node,
|
||||
two?: React$Node,
|
||||
few?: React$Node,
|
||||
many?: React$Node,
|
||||
children?: (formattedPlural: React$Node) => React$Node
|
||||
}
|
||||
> {}
|
||||
declare class IntlProvider extends React$Component<
|
||||
$npm$ReactIntl$IntlProviderConfig & {
|
||||
children?: React$Node,
|
||||
initialNow?: $npm$ReactIntl$DateParseable
|
||||
}
|
||||
> {}
|
||||
declare class RawIntlProvider extends React$Component<
|
||||
{|
|
||||
children?: React$Node,
|
||||
value?: IntlShape
|
||||
|}
|
||||
> {}
|
||||
declare type IntlShape = $npm$ReactIntl$IntlShape;
|
||||
declare type MessageDescriptor = $npm$ReactIntl$MessageDescriptor;
|
||||
}
|
123
flow-typed/npm/react-motion_vx.x.x.js
vendored
123
flow-typed/npm/react-motion_vx.x.x.js
vendored
@ -1,123 +0,0 @@
|
||||
// flow-typed signature: f7ed1ad96a453a021e6d98c1d144ef43
|
||||
// flow-typed version: <<STUB>>/react-motion_v0.5.x/flow_v0.53.1
|
||||
|
||||
/**
|
||||
* This is an autogenerated libdef stub for:
|
||||
*
|
||||
* 'react-motion'
|
||||
*
|
||||
* Fill this stub out by replacing all the `any` types.
|
||||
*
|
||||
* Once filled out, we encourage you to share your work with the
|
||||
* community by sending a pull request to:
|
||||
* https://github.com/flowtype/flow-typed
|
||||
*/
|
||||
|
||||
declare module 'react-motion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* We include stubs for each file inside this npm package in case you need to
|
||||
* require those files directly. Feel free to delete any files that aren't
|
||||
* needed.
|
||||
*/
|
||||
declare module 'react-motion/build/react-motion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/mapToZero' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/mergeDiff' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/Motion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/presets' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/react-motion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/reorderKeys' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/shouldStopAnimation' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/spring' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/StaggeredMotion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/stepper' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/stripStyle' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/TransitionMotion' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
declare module 'react-motion/lib/Types' {
|
||||
declare module.exports: any;
|
||||
}
|
||||
|
||||
// Filename aliases
|
||||
declare module 'react-motion/build/react-motion.js' {
|
||||
declare module.exports: $Exports<'react-motion/build/react-motion'>;
|
||||
}
|
||||
declare module 'react-motion/lib/mapToZero.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/mapToZero'>;
|
||||
}
|
||||
declare module 'react-motion/lib/mergeDiff.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/mergeDiff'>;
|
||||
}
|
||||
declare module 'react-motion/lib/Motion.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/Motion'>;
|
||||
}
|
||||
declare module 'react-motion/lib/presets.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/presets'>;
|
||||
}
|
||||
declare module 'react-motion/lib/react-motion.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/react-motion'>;
|
||||
}
|
||||
declare module 'react-motion/lib/reorderKeys.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/reorderKeys'>;
|
||||
}
|
||||
declare module 'react-motion/lib/shouldStopAnimation.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/shouldStopAnimation'>;
|
||||
}
|
||||
declare module 'react-motion/lib/spring.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/spring'>;
|
||||
}
|
||||
declare module 'react-motion/lib/StaggeredMotion.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/StaggeredMotion'>;
|
||||
}
|
||||
declare module 'react-motion/lib/stepper.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/stepper'>;
|
||||
}
|
||||
declare module 'react-motion/lib/stripStyle.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/stripStyle'>;
|
||||
}
|
||||
declare module 'react-motion/lib/TransitionMotion.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/TransitionMotion'>;
|
||||
}
|
||||
declare module 'react-motion/lib/Types.js' {
|
||||
declare module.exports: $Exports<'react-motion/lib/Types'>;
|
||||
}
|
276
flow-typed/npm/react-redux_v5.x.x.js
vendored
276
flow-typed/npm/react-redux_v5.x.x.js
vendored
@ -1,276 +0,0 @@
|
||||
// flow-typed signature: f06f00c3ad0cfedb90c0c6de04b219f3
|
||||
// flow-typed version: 3a6d556e4b/react-redux_v5.x.x/flow_>=v0.89.x
|
||||
|
||||
/**
|
||||
The order of type arguments for connect() is as follows:
|
||||
|
||||
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" {
|
||||
// ------------------------------------------------------------
|
||||
// Typings for connect()
|
||||
// ------------------------------------------------------------
|
||||
|
||||
declare export type Options<S, OP, SP, MP> = {|
|
||||
pure?: boolean,
|
||||
withRef?: boolean,
|
||||
areStatesEqual?: (next: S, prev: S) => boolean,
|
||||
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 Bind<D> = <A, R>((...A) => R) => (...A) => $Call<D, R>;
|
||||
|
||||
declare type MapDispatchToPropsFn<D, -OP, +DP> =
|
||||
| ((dispatch: D, ownProps: OP) => 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 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,
|
||||
dispatchProps: DP,
|
||||
ownProps: OP,
|
||||
) => P;
|
||||
|
||||
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 export function connect<-P, -OP, -SP, -DP: {||}, S, D>(
|
||||
mapStateToProps: MapStateToProps<S, OP, SP>,
|
||||
mapDispatchToProps: null | void,
|
||||
// If you get error here try adding return type to you mapStateToProps function
|
||||
mergeProps: MergeProps<P, OP, SP, {| dispatch: D |}>,
|
||||
options?: ?Options<S, OP, SP, P>,
|
||||
): Connector<P, OP, P>;
|
||||
|
||||
// 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: MergeProps<P, OP, {||}, DP>,
|
||||
options?: ?Options<S, OP, {||}, 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: null | void,
|
||||
mapDispatchToProps: DP,
|
||||
mergeProps: MergeProps<P, OP, {||}, $ObjMap<DP, Bind<D>>>,
|
||||
options?: ?Options<S, OP, {||}, P>,
|
||||
): Connector<P, OP, P>;
|
||||
|
||||
// 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: 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 export function createProvider(
|
||||
storeKey?: string,
|
||||
subKey?: string,
|
||||
): Class<Provider<*>>;
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Typings for connectAdvanced()
|
||||
// ------------------------------------------------------------
|
||||
|
||||
declare type ConnectAdvancedOptions = {
|
||||
getDisplayName?: (name: string) => string,
|
||||
methodName?: string,
|
||||
renderCountProp?: string,
|
||||
shouldHandleStateChanges?: boolean,
|
||||
storeKey?: string,
|
||||
withRef?: boolean,
|
||||
};
|
||||
|
||||
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 type MapStateToPropsEx<S: Object, SP: Object, RSP: Object> = (
|
||||
state: S,
|
||||
props: SP,
|
||||
) => RSP;
|
||||
|
||||
declare type SelectorFactory<
|
||||
Com: React$ComponentType<*>,
|
||||
Dispatch,
|
||||
S: Object,
|
||||
OP: Object,
|
||||
EFO: Object,
|
||||
CP: Object,
|
||||
> = (
|
||||
dispatch: Dispatch,
|
||||
factoryOptions: SelectorFactoryOptions<Com> & EFO,
|
||||
) => MapStateToPropsEx<S, OP, CP>;
|
||||
|
||||
declare export function connectAdvanced<
|
||||
Com: React$ComponentType<*>,
|
||||
D,
|
||||
S: Object,
|
||||
OP: Object,
|
||||
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 export default {
|
||||
Provider: typeof Provider,
|
||||
createProvider: typeof createProvider,
|
||||
connect: typeof connect,
|
||||
connectAdvanced: typeof connectAdvanced,
|
||||
};
|
||||
}
|
176
flow-typed/npm/react-router-dom_v5.x.x.js
vendored
176
flow-typed/npm/react-router-dom_v5.x.x.js
vendored
@ -1,176 +0,0 @@
|
||||
// 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;
|
||||
}
|
100
flow-typed/npm/redux_v4.x.x.js
vendored
100
flow-typed/npm/redux_v4.x.x.js
vendored
@ -1,100 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
const path = require('path');
|
||||
const { transform } = require('../../webpack-utils/intl-loader');
|
||||
@ -11,7 +13,6 @@ module.exports = {
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
process(src, filename, config, options) {
|
||||
return transform(src, filename, path.resolve(`${__dirname}/../../..`));
|
||||
},
|
||||
|
16
package.json
16
package.json
@ -34,8 +34,8 @@
|
||||
"lint:check": "eslint --quiet .",
|
||||
"prettier": "prettier --write \"{src/**/*,scripts/**/*,tests-e2e/**/*,webpack-utils/**/*,jest/**/*,config/**/*,*}.{js,ts,tsx,json,md,scss,css}\"",
|
||||
"prettier:check": "prettier --check \"{src/**/*,scripts/**/*,tests-e2e/**/*,webpack-utils/**/*,jest/**/*,config/**/*,*}.{js,ts,tsx,json,md,scss,css}\"",
|
||||
"flow:check": "flow",
|
||||
"ci:check": "yarn lint:check && yarn flow:check && yarn test",
|
||||
"ts:check": "tsc",
|
||||
"ci:check": "yarn lint:check && yarn ts:check && yarn test",
|
||||
"analyze": "yarn run clean && yarn run build:webpack --analyze",
|
||||
"i18n:collect": "babel-node ./scripts/i18n-collect.js",
|
||||
"i18n:push": "babel-node ./scripts/i18n-crowdin.js push",
|
||||
@ -90,6 +90,8 @@
|
||||
"@formatjs/intl-pluralrules": "^1.3.7",
|
||||
"@formatjs/intl-relativetimeformat": "^4.4.6",
|
||||
"@hot-loader/react-dom": "^16.11.0",
|
||||
"@types/react-redux": "^7.1.5",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"debounce": "^1.0.0",
|
||||
@ -138,10 +140,13 @@
|
||||
"@babel/plugin-syntax-import-meta": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.4",
|
||||
"@babel/preset-flow": "^7.7.4",
|
||||
"@babel/preset-react": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"@babel/runtime-corejs3": "^7.7.4",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.9.0",
|
||||
"@typescript-eslint/parser": "^2.9.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-react-intl": "^5.1.8",
|
||||
"core-js": "3.4.3",
|
||||
@ -154,13 +159,11 @@
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"eslint": "^6.7.1",
|
||||
"eslint-config-prettier": "^6.7.0",
|
||||
"eslint-plugin-flowtype": "^4.5.2",
|
||||
"eslint-plugin-jsdoc": "^18.1.5",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"file-loader": "^4.2.0",
|
||||
"flow-bin": "~0.102.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^3.1.0",
|
||||
@ -184,6 +187,7 @@
|
||||
"sitemap-webpack-plugin": "^0.6.0",
|
||||
"speed-measure-webpack-plugin": "^1.3.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"typescript": "^3.7.2",
|
||||
"unexpected": "^11.8.1",
|
||||
"unexpected-sinon": "^10.5.1",
|
||||
"url-loader": "^2.2.0",
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
const path = require('path');
|
||||
const loaderUtils = require('loader-utils');
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { sync as mkdirpSync } from 'mkdirp';
|
||||
import chalk from 'chalk';
|
||||
import prompt from 'prompt';
|
||||
|
||||
import localesMap from './../src/i18n/index.json';
|
||||
import localesMap from './../src/i18n';
|
||||
|
||||
const MESSAGES_PATTERN = `${__dirname}/../dist/messages/**/*.json`;
|
||||
const LANG_DIR = `${__dirname}/../src/i18n`;
|
||||
@ -19,8 +19,8 @@ const SUPPORTED_LANGS = [DEFAULT_LOCALE, ...Object.keys(localesMap)];
|
||||
* there are messages in different components that use the same `id`. The result
|
||||
* is a flat collection of `id: message` pairs for the app's default locale.
|
||||
*/
|
||||
let idToFileMap = {};
|
||||
let duplicateIds = [];
|
||||
let idToFileMap: { [key: string]: string[] } = {};
|
||||
let duplicateIds: string[] = [];
|
||||
const collectedMessages = globSync(MESSAGES_PATTERN)
|
||||
.map(filename => [filename, JSON.parse(fs.readFileSync(filename, 'utf8'))])
|
||||
.reduce((collection, [file, descriptors]) => {
|
||||
@ -42,23 +42,24 @@ if (duplicateIds.length) {
|
||||
console.log(`${chalk.yellow(id)}:\n - ${idToFileMap[id].join('\n - ')}\n`),
|
||||
);
|
||||
console.log(chalk.red('Please correct the errors above to proceed further!'));
|
||||
process.exit();
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
duplicateIds = null;
|
||||
idToFileMap = null;
|
||||
duplicateIds = [];
|
||||
idToFileMap = {};
|
||||
|
||||
/**
|
||||
* Making a diff with the previous DEFAULT_LOCALE version
|
||||
*/
|
||||
const defaultMessagesPath = `${LANG_DIR}/${DEFAULT_LOCALE}.json`;
|
||||
let keysToUpdate = [];
|
||||
let keysToAdd = [];
|
||||
let keysToRemove = [];
|
||||
const keysToRename = [];
|
||||
const isNotMarked = value => value.slice(0, 2) !== '--';
|
||||
let keysToUpdate: string[] = [];
|
||||
let keysToAdd: string[] = [];
|
||||
let keysToRemove: string[] = [];
|
||||
const keysToRename: Array<[string, string]> = [];
|
||||
const isNotMarked = (value: string) => value.slice(0, 2) !== '--';
|
||||
|
||||
const prevMessages = readJSON(defaultMessagesPath);
|
||||
const prevMessages: { [key: string]: string } = readJSON(defaultMessagesPath);
|
||||
const prevMessagesMap = Object.entries(prevMessages).reduce(
|
||||
(acc, [key, value]) => {
|
||||
if (acc[value]) {
|
||||
@ -69,8 +70,9 @@ const prevMessagesMap = Object.entries(prevMessages).reduce(
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
{} as { [key: string]: string[] },
|
||||
);
|
||||
|
||||
keysToAdd = Object.keys(collectedMessages).filter(key => !prevMessages[key]);
|
||||
keysToRemove = Object.keys(prevMessages)
|
||||
.filter(key => !collectedMessages[key])
|
||||
@ -80,13 +82,13 @@ keysToUpdate = Object.entries(prevMessages).reduce(
|
||||
acc.concat(
|
||||
collectedMessages[key] && collectedMessages[key] !== message ? key : [],
|
||||
),
|
||||
[],
|
||||
[] as string[],
|
||||
);
|
||||
|
||||
// detect keys to rename, mutating keysToAdd and keysToRemove
|
||||
[].concat(keysToAdd).forEach(toKey => {
|
||||
[...keysToAdd].forEach(toKey => {
|
||||
const keys = prevMessagesMap[collectedMessages[toKey]] || [];
|
||||
const fromKey = keys.find(fromKey => keysToRemove.indexOf(fromKey) > -1);
|
||||
const fromKey = keys.find(key => keysToRemove.indexOf(key) > -1);
|
||||
|
||||
if (fromKey) {
|
||||
keysToRename.push([fromKey, toKey]);
|
@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
@ -22,7 +21,7 @@ const PROJECT_KEY = config.crowdinApiKey;
|
||||
const CROWDIN_FILE_PATH = 'accounts/site.json';
|
||||
const SOURCE_LANG = 'en';
|
||||
const LANG_DIR = path.resolve(`${__dirname}/../src/i18n`);
|
||||
const INDEX_FILE_NAME = 'index.json';
|
||||
const INDEX_FILE_NAME = 'index.js';
|
||||
const MIN_RELEASE_PROGRESS = 80; // Minimal ready percent before translation can be published
|
||||
|
||||
const crowdin = new CrowdinApi({ apiKey: PROJECT_KEY });
|
||||
@ -81,8 +80,13 @@ function toInternalLocale(code: string): string {
|
||||
* @param {object} translates
|
||||
* @returns {string}
|
||||
*/
|
||||
function serializeToFormattedJson(translates: Object): string {
|
||||
return JSON.stringify(sortByKeys(translates), null, 4) + '\n'; // eslint-disable-line prefer-template
|
||||
function serializeToFormattedJson(
|
||||
translates: { [key: string]: any },
|
||||
{ asModule = false }: { asModule?: boolean } = {},
|
||||
): string {
|
||||
const src = JSON.stringify(sortByKeys(translates), null, 2);
|
||||
|
||||
return asModule ? `module.exports = ${src};\n` : `${src}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +95,7 @@ function serializeToFormattedJson(translates: Object): string {
|
||||
* @param {object} object
|
||||
* @returns {object}
|
||||
*/
|
||||
function sortByKeys(object: Object): Object {
|
||||
function sortByKeys(object: { [key: string]: any }): { [key: string]: any } {
|
||||
return Object.keys(object)
|
||||
.sort()
|
||||
.reduce((result, key) => {
|
||||
@ -121,32 +125,32 @@ interface ProjectInfoDirectory {
|
||||
interface ProjectInfoResponse {
|
||||
details: {
|
||||
source_language: {
|
||||
name: string,
|
||||
code: string,
|
||||
},
|
||||
name: string,
|
||||
identifier: string,
|
||||
created: string,
|
||||
description: string,
|
||||
join_policy: string,
|
||||
last_build: string | null,
|
||||
last_activity: string,
|
||||
participants_count: string, // it's number, but string in the response
|
||||
logo_url: string | null,
|
||||
total_strings_count: string, // it's number, but string in the response
|
||||
total_words_count: string, // it's number, but string in the response
|
||||
duplicate_strings_count: number,
|
||||
duplicate_words_count: number,
|
||||
name: string;
|
||||
code: string;
|
||||
};
|
||||
name: string;
|
||||
identifier: string;
|
||||
created: string;
|
||||
description: string;
|
||||
join_policy: string;
|
||||
last_build: string | null;
|
||||
last_activity: string;
|
||||
participants_count: string; // it's number, but string in the response
|
||||
logo_url: string | null;
|
||||
total_strings_count: string; // it's number, but string in the response
|
||||
total_words_count: string; // it's number, but string in the response
|
||||
duplicate_strings_count: number;
|
||||
duplicate_words_count: number;
|
||||
invite_url: {
|
||||
translator: string,
|
||||
proofreader: string,
|
||||
},
|
||||
translator: string;
|
||||
proofreader: string;
|
||||
};
|
||||
};
|
||||
languages: Array<{
|
||||
name: string, // English language name
|
||||
code: string,
|
||||
can_translate: 0 | 1,
|
||||
can_approve: 0 | 1,
|
||||
name: string; // English language name
|
||||
code: string;
|
||||
can_translate: 0 | 1;
|
||||
can_approve: 0 | 1;
|
||||
}>;
|
||||
files: Array<ProjectInfoFile | ProjectInfoDirectory>;
|
||||
}
|
||||
@ -277,7 +281,7 @@ async function pull() {
|
||||
|
||||
console.log('Locales are downloaded. Writing them to file system.');
|
||||
|
||||
const indexFileEntries: { [string]: IndexFileEntry } = {
|
||||
const indexFileEntries: { [key: string]: IndexFileEntry } = {
|
||||
en: {
|
||||
code: 'en',
|
||||
name: 'English',
|
||||
@ -286,7 +290,6 @@ async function pull() {
|
||||
isReleased: true,
|
||||
},
|
||||
};
|
||||
// $FlowFixMe
|
||||
await Promise.all(
|
||||
results.map(
|
||||
result =>
|
||||
@ -328,7 +331,7 @@ async function pull() {
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(LANG_DIR, INDEX_FILE_NAME),
|
||||
serializeToFormattedJson(indexFileEntries),
|
||||
serializeToFormattedJson(indexFileEntries, { asModule: true }),
|
||||
);
|
||||
|
||||
console.log(ch.green('The index file was successfully written'));
|
||||
@ -364,7 +367,7 @@ function push() {
|
||||
[CROWDIN_FILE_PATH]: path.join(LANG_DIR, `${SOURCE_LANG}.json`),
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
update_option: disapprove
|
||||
? 'update_as_unapproved'
|
||||
: 'update_without_changes',
|
@ -1,14 +1,20 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { Router, Route, Switch } from 'react-router-dom';
|
||||
import { IntlProvider } from 'components/i18n';
|
||||
import { Store } from 'redux';
|
||||
import AuthFlowRoute from 'containers/AuthFlowRoute';
|
||||
import RootPage from 'pages/root/RootPage';
|
||||
import SuccessOauthPage from 'pages/auth/SuccessOauthPage';
|
||||
|
||||
const App = ({ store, browserHistory }) => (
|
||||
const App = ({
|
||||
store,
|
||||
browserHistory,
|
||||
}: {
|
||||
store: Store;
|
||||
browserHistory: any;
|
||||
}) => (
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider>
|
||||
<Router history={browserHistory}>
|
@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { omit, debounce } from 'functions';
|
||||
|
||||
/**
|
||||
@ -23,20 +21,22 @@ import { omit, debounce } from 'functions';
|
||||
* </MeasureHeight>
|
||||
*/
|
||||
|
||||
type ChildState = mixed;
|
||||
type ChildState = any;
|
||||
|
||||
export default class MeasureHeight extends PureComponent<{
|
||||
shouldMeasure: (prevState: ChildState, newState: ChildState) => boolean,
|
||||
onMeasure: (height: number) => void,
|
||||
state: ChildState,
|
||||
}> {
|
||||
export default class MeasureHeight extends React.PureComponent<
|
||||
{
|
||||
shouldMeasure: (prevState: ChildState, newState: ChildState) => boolean;
|
||||
onMeasure: (height: number) => void;
|
||||
state: ChildState;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
> {
|
||||
static defaultProps = {
|
||||
shouldMeasure: (prevState: ChildState, newState: ChildState) =>
|
||||
prevState !== newState,
|
||||
onMeasure: (height: number) => {}, // eslint-disable-line
|
||||
onMeasure: () => {},
|
||||
};
|
||||
|
||||
el: ?HTMLDivElement;
|
||||
el: HTMLDivElement | null = null;
|
||||
|
||||
componentDidMount() {
|
||||
// we want to measure height immediately on first mount to avoid ui laggs
|
||||
@ -55,11 +55,7 @@ export default class MeasureHeight extends PureComponent<{
|
||||
}
|
||||
|
||||
render() {
|
||||
const props: Object = omit(this.props, [
|
||||
'shouldMeasure',
|
||||
'onMeasure',
|
||||
'state',
|
||||
]);
|
||||
const props = omit(this.props, ['shouldMeasure', 'onMeasure', 'state']);
|
||||
|
||||
return <div {...props} ref={(el: HTMLDivElement) => (this.el = el)} />;
|
||||
}
|
@ -1,34 +1,37 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import loader from 'services/loader';
|
||||
import { skins, SKIN_DARK, COLOR_WHITE } from 'components/ui';
|
||||
import { SKIN_DARK, COLOR_WHITE, Skin } from 'components/ui';
|
||||
import { Button } from 'components/ui/form';
|
||||
import { authenticate, revoke } from 'components/accounts/actions';
|
||||
import { getActiveAccount } from 'components/accounts/reducer';
|
||||
import { getActiveAccount, Account } from 'components/accounts/reducer';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
import styles from './accountSwitcher.scss';
|
||||
import messages from './AccountSwitcher.intl.json';
|
||||
|
||||
export class AccountSwitcher extends Component {
|
||||
static displayName = 'AccountSwitcher';
|
||||
interface Props {
|
||||
switchAccount: (account: Account) => Promise<Account>;
|
||||
removeAccount: (account: Account) => Promise<void>;
|
||||
// called after each action performed
|
||||
onAfterAction: () => void;
|
||||
// called after switching an account. The active account will be passed as arg
|
||||
onSwitch: (account: Account) => void;
|
||||
accounts: RootState['accounts'];
|
||||
skin: Skin;
|
||||
// whether active account should be expanded and shown on the top
|
||||
highlightActiveAccount: boolean;
|
||||
// whether to show logout icon near each account
|
||||
allowLogout: boolean;
|
||||
// whether to show add account button
|
||||
allowAdd: boolean;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
switchAccount: PropTypes.func.isRequired,
|
||||
removeAccount: PropTypes.func.isRequired,
|
||||
onAfterAction: PropTypes.func, // called after each action performed
|
||||
onSwitch: PropTypes.func, // called after switching an account. The active account will be passed as arg
|
||||
accounts: PropTypes.object, // eslint-disable-line
|
||||
skin: PropTypes.oneOf(skins),
|
||||
highlightActiveAccount: PropTypes.bool, // whether active account should be expanded and shown on the top
|
||||
allowLogout: PropTypes.bool, // whether to show logout icon near each account
|
||||
allowAdd: PropTypes.bool, // whether to show add account button
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
export class AccountSwitcher extends React.Component<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
skin: SKIN_DARK,
|
||||
highlightActiveAccount: true,
|
||||
allowLogout: true,
|
||||
@ -47,6 +50,10 @@ export class AccountSwitcher extends Component {
|
||||
} = this.props;
|
||||
const activeAccount = getActiveAccount({ accounts });
|
||||
|
||||
if (!activeAccount) {
|
||||
throw new Error('Can not find active account');
|
||||
}
|
||||
|
||||
let { available } = accounts;
|
||||
|
||||
if (highlightActiveAccount) {
|
||||
@ -157,7 +164,7 @@ export class AccountSwitcher extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
onSwitch = account => event => {
|
||||
onSwitch = (account: Account) => (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
loader.show();
|
||||
@ -172,7 +179,7 @@ export class AccountSwitcher extends Component {
|
||||
.finally(() => loader.hide());
|
||||
};
|
||||
|
||||
onRemove = account => event => {
|
||||
onRemove = (account: Account) => (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@ -181,7 +188,7 @@ export class AccountSwitcher extends Component {
|
||||
}
|
||||
|
||||
export default connect(
|
||||
({ accounts }) => ({
|
||||
({ accounts }: RootState) => ({
|
||||
accounts,
|
||||
}),
|
||||
{
|
@ -1,8 +1,6 @@
|
||||
import expect from 'test/unexpected';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { browserHistory } from 'services/history';
|
||||
|
||||
import { InternalServerError } from 'services/request';
|
||||
import { sessionStorage } from 'services/localStorage';
|
||||
import * as authentication from 'services/api/authentication';
|
||||
@ -21,9 +19,11 @@ import {
|
||||
reset,
|
||||
} from 'components/accounts/actions/pure-actions';
|
||||
import { SET_LOCALE } from 'components/i18n/actions';
|
||||
|
||||
import { updateUser, setUser } from 'components/user/actions';
|
||||
import { setLogin, setAccountSwitcher } from 'components/auth/actions';
|
||||
import { Dispatch, RootState } from 'reducers';
|
||||
|
||||
import { Account } from './reducer';
|
||||
|
||||
const token =
|
||||
'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbHl8MSJ9.pRJ7vakt2eIscjqwG__KhSxKb3qwGsdBBeDbBffJs_I';
|
||||
@ -46,8 +46,8 @@ const user = {
|
||||
};
|
||||
|
||||
describe('components/accounts/actions', () => {
|
||||
let dispatch;
|
||||
let getState;
|
||||
let dispatch: Dispatch;
|
||||
let getState: () => RootState;
|
||||
|
||||
beforeEach(() => {
|
||||
dispatch = sinon
|
||||
@ -55,7 +55,7 @@ describe('components/accounts/actions', () => {
|
||||
.named('store.dispatch');
|
||||
getState = sinon.stub().named('store.getState');
|
||||
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
available: [],
|
||||
active: null,
|
||||
@ -72,8 +72,8 @@ describe('components/accounts/actions', () => {
|
||||
sinon.stub(browserHistory, 'push').named('browserHistory.push');
|
||||
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||
|
||||
authentication.logout.returns(Promise.resolve());
|
||||
authentication.validateToken.returns(
|
||||
(authentication.logout as any).returns(Promise.resolve());
|
||||
(authentication.validateToken as any).returns(
|
||||
Promise.resolve({
|
||||
token: account.token,
|
||||
refreshToken: account.refreshToken,
|
||||
@ -83,14 +83,14 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
authentication.validateToken.restore();
|
||||
authentication.logout.restore();
|
||||
browserHistory.push.restore();
|
||||
(authentication.validateToken as any).restore();
|
||||
(authentication.logout as any).restore();
|
||||
(browserHistory.push as any).restore();
|
||||
});
|
||||
|
||||
describe('#authenticate()', () => {
|
||||
it('should request user state using token', () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||
account.id,
|
||||
account.token,
|
||||
@ -99,7 +99,11 @@ describe('components/accounts/actions', () => {
|
||||
));
|
||||
|
||||
it('should request user by extracting id from token', () =>
|
||||
authenticate({ token })(dispatch, getState).then(() =>
|
||||
authenticate({ token } as Account)(
|
||||
dispatch,
|
||||
getState,
|
||||
undefined,
|
||||
).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||
1,
|
||||
token,
|
||||
@ -108,7 +112,11 @@ describe('components/accounts/actions', () => {
|
||||
));
|
||||
|
||||
it('should request user by extracting id from legacy token', () =>
|
||||
authenticate({ token: legacyToken })(dispatch, getState).then(() =>
|
||||
authenticate({ token: legacyToken } as Account)(
|
||||
dispatch,
|
||||
getState,
|
||||
undefined,
|
||||
).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||
1,
|
||||
legacyToken,
|
||||
@ -117,39 +125,39 @@ describe('components/accounts/actions', () => {
|
||||
));
|
||||
|
||||
it(`dispatches ${ADD} action`, () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [add(account)]),
|
||||
));
|
||||
|
||||
it(`dispatches ${ACTIVATE} action`, () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [activate(account)]),
|
||||
));
|
||||
|
||||
it(`dispatches ${SET_LOCALE} action`, () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
{ type: SET_LOCALE, payload: { locale: 'be' } },
|
||||
]),
|
||||
));
|
||||
|
||||
it('should update user state', () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
updateUser({ ...user, isGuest: false }),
|
||||
]),
|
||||
));
|
||||
|
||||
it('resolves with account', () =>
|
||||
authenticate(account)(dispatch, getState).then(resp =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(resp =>
|
||||
expect(resp, 'to equal', account),
|
||||
));
|
||||
|
||||
it('rejects when bad auth data', () => {
|
||||
authentication.validateToken.returns(Promise.reject({}));
|
||||
(authentication.validateToken as any).returns(Promise.reject({}));
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState),
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected',
|
||||
).then(() => {
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
@ -161,12 +169,12 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('rejects when 5xx without logouting', () => {
|
||||
const resp = new InternalServerError(null, { status: 500 });
|
||||
const resp = new InternalServerError('500', { status: 500 });
|
||||
|
||||
authentication.validateToken.rejects(resp);
|
||||
(authentication.validateToken as any).rejects(resp);
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState),
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected with',
|
||||
resp,
|
||||
).then(() =>
|
||||
@ -178,14 +186,14 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
it('marks user as stranger, if there is no refreshToken', () => {
|
||||
const expectedKey = `stranger${account.id}`;
|
||||
authentication.validateToken.resolves({
|
||||
(authentication.validateToken as any).resolves({
|
||||
token: account.token,
|
||||
user,
|
||||
});
|
||||
|
||||
sessionStorage.removeItem(expectedKey);
|
||||
|
||||
return authenticate(account)(dispatch, getState).then(() => {
|
||||
return authenticate(account)(dispatch, getState, undefined).then(() => {
|
||||
expect(sessionStorage.getItem(expectedKey), 'not to be null');
|
||||
sessionStorage.removeItem(expectedKey);
|
||||
});
|
||||
@ -193,7 +201,7 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
describe('when user authenticated during oauth', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
available: [],
|
||||
active: null,
|
||||
@ -209,7 +217,7 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should dispatch setAccountSwitcher', () =>
|
||||
authenticate(account)(dispatch, getState).then(() =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setAccountSwitcher(false),
|
||||
]),
|
||||
@ -218,7 +226,7 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
describe('when one account available', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account.id,
|
||||
available: [account],
|
||||
@ -231,10 +239,12 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should activate account before auth api call', () => {
|
||||
authentication.validateToken.returns(Promise.reject({ error: 'foo' }));
|
||||
(authentication.validateToken as any).returns(
|
||||
Promise.reject({ error: 'foo' }),
|
||||
);
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState),
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected with',
|
||||
{ error: 'foo' },
|
||||
).then(() =>
|
||||
@ -247,7 +257,7 @@ describe('components/accounts/actions', () => {
|
||||
describe('#revoke()', () => {
|
||||
describe('when one account available', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account.id,
|
||||
available: [account],
|
||||
@ -260,19 +270,19 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should dispatch reset action', () =>
|
||||
revoke(account)(dispatch, getState).then(() =>
|
||||
revoke(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [reset()]),
|
||||
));
|
||||
|
||||
it('should call logout api method in background', () =>
|
||||
revoke(account)(dispatch, getState).then(() =>
|
||||
revoke(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.logout, 'to have a call satisfying', [
|
||||
account.token,
|
||||
]),
|
||||
));
|
||||
|
||||
it('should update user state', () =>
|
||||
revoke(account)(dispatch, getState).then(
|
||||
revoke(account)(dispatch, getState, undefined).then(
|
||||
() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setUser({ isGuest: true }),
|
||||
@ -289,7 +299,7 @@ describe('components/accounts/actions', () => {
|
||||
const account2 = { ...account, id: 2 };
|
||||
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account2.id,
|
||||
available: [account, account2],
|
||||
@ -299,17 +309,17 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should switch to the next account', () =>
|
||||
revoke(account2)(dispatch, getState).then(() =>
|
||||
revoke(account2)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [activate(account)]),
|
||||
));
|
||||
|
||||
it('should remove current account', () =>
|
||||
revoke(account2)(dispatch, getState).then(() =>
|
||||
revoke(account2)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [remove(account2)]),
|
||||
));
|
||||
|
||||
it('should call logout api method in background', () =>
|
||||
revoke(account2)(dispatch, getState).then(() =>
|
||||
revoke(account2)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.logout, 'to have a call satisfying', [
|
||||
account2.token,
|
||||
]),
|
||||
@ -321,7 +331,7 @@ describe('components/accounts/actions', () => {
|
||||
const account2 = { ...account, id: 2 };
|
||||
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account2.id,
|
||||
available: [account, account2],
|
||||
@ -334,7 +344,7 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should call logout api method for each account', () => {
|
||||
logoutAll()(dispatch, getState);
|
||||
logoutAll()(dispatch, getState, undefined);
|
||||
|
||||
expect(authentication.logout, 'to have calls satisfying', [
|
||||
[account.token],
|
||||
@ -343,18 +353,18 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should dispatch reset', () => {
|
||||
logoutAll()(dispatch, getState);
|
||||
logoutAll()(dispatch, getState, undefined);
|
||||
|
||||
expect(dispatch, 'to have a call satisfying', [reset()]);
|
||||
});
|
||||
|
||||
it('should redirect to /login', () =>
|
||||
logoutAll()(dispatch, getState).then(() => {
|
||||
logoutAll()(dispatch, getState, undefined).then(() => {
|
||||
expect(browserHistory.push, 'to have a call satisfying', ['/login']);
|
||||
}));
|
||||
|
||||
it('should change user to guest', () =>
|
||||
logoutAll()(dispatch, getState).then(() => {
|
||||
logoutAll()(dispatch, getState, undefined).then(() => {
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setUser({
|
||||
lang: user.lang,
|
||||
@ -368,7 +378,7 @@ describe('components/accounts/actions', () => {
|
||||
const foreignAccount = {
|
||||
...account,
|
||||
id: 2,
|
||||
refreshToken: undefined,
|
||||
refreshToken: null,
|
||||
};
|
||||
|
||||
const foreignAccount2 = {
|
||||
@ -377,7 +387,7 @@ describe('components/accounts/actions', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: foreignAccount.id,
|
||||
available: [account, foreignAccount, foreignAccount2],
|
||||
@ -387,14 +397,14 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should remove stranger accounts', () => {
|
||||
logoutStrangers()(dispatch, getState);
|
||||
logoutStrangers()(dispatch, getState, undefined);
|
||||
|
||||
expect(dispatch, 'to have a call satisfying', [remove(foreignAccount)]);
|
||||
expect(dispatch, 'to have a call satisfying', [remove(foreignAccount2)]);
|
||||
});
|
||||
|
||||
it('should logout stranger accounts', () => {
|
||||
logoutStrangers()(dispatch, getState);
|
||||
logoutStrangers()(dispatch, getState, undefined);
|
||||
|
||||
expect(authentication.logout, 'to have calls satisfying', [
|
||||
[foreignAccount.token],
|
||||
@ -403,12 +413,12 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should activate another account if available', () =>
|
||||
logoutStrangers()(dispatch, getState).then(() =>
|
||||
logoutStrangers()(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [activate(account)]),
|
||||
));
|
||||
|
||||
it('should not activate another account if active account is already not a stranger', () => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account.id,
|
||||
available: [account, foreignAccount],
|
||||
@ -416,13 +426,13 @@ describe('components/accounts/actions', () => {
|
||||
user,
|
||||
});
|
||||
|
||||
return logoutStrangers()(dispatch, getState).then(() =>
|
||||
return logoutStrangers()(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'not to have calls satisfying', [activate(account)]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not dispatch if no strangers', () => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: account.id,
|
||||
available: [account],
|
||||
@ -430,14 +440,14 @@ describe('components/accounts/actions', () => {
|
||||
user,
|
||||
});
|
||||
|
||||
return logoutStrangers()(dispatch, getState).then(() =>
|
||||
return logoutStrangers()(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'was not called'),
|
||||
);
|
||||
});
|
||||
|
||||
describe('when all accounts are strangers', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
active: foreignAccount.id,
|
||||
available: [foreignAccount, foreignAccount2],
|
||||
@ -448,7 +458,7 @@ describe('components/accounts/actions', () => {
|
||||
user,
|
||||
});
|
||||
|
||||
logoutStrangers()(dispatch, getState);
|
||||
logoutStrangers()(dispatch, getState, undefined);
|
||||
});
|
||||
|
||||
it('logouts all accounts', () => {
|
||||
@ -471,7 +481,7 @@ describe('components/accounts/actions', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.setItem(key, 1);
|
||||
|
||||
logoutStrangers()(dispatch, getState);
|
||||
logoutStrangers()(dispatch, getState, undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
import type { Account, State as AccountsState } from './reducer';
|
||||
import { getJwtPayloads } from 'functions';
|
||||
import { sessionStorage } from 'services/localStorage';
|
||||
import {
|
||||
@ -13,7 +11,9 @@ import { setLocale } from 'components/i18n/actions';
|
||||
import { setAccountSwitcher } from 'components/auth/actions';
|
||||
import { getActiveAccount } from 'components/accounts/reducer';
|
||||
import logger from 'services/logger';
|
||||
import { ThunkAction } from 'reducers';
|
||||
|
||||
import { Account } from './reducer';
|
||||
import {
|
||||
add,
|
||||
remove,
|
||||
@ -22,17 +22,6 @@ import {
|
||||
updateToken,
|
||||
} from './actions/pure-actions';
|
||||
|
||||
type Dispatch = (action: Object) => Promise<*>;
|
||||
|
||||
type State = {
|
||||
accounts: AccountsState,
|
||||
auth: {
|
||||
oauth?: {
|
||||
clientId?: string,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { updateToken, activate, remove };
|
||||
|
||||
/**
|
||||
@ -46,20 +35,17 @@ export function authenticate(
|
||||
account:
|
||||
| Account
|
||||
| {
|
||||
token: string,
|
||||
refreshToken: ?string,
|
||||
token: string;
|
||||
refreshToken: string | null;
|
||||
},
|
||||
) {
|
||||
): ThunkAction<Promise<Account>> {
|
||||
const { token, refreshToken } = account;
|
||||
const email = account.email || null;
|
||||
const email = 'email' in account ? account.email : null;
|
||||
|
||||
return async (
|
||||
dispatch: Dispatch,
|
||||
getState: () => State,
|
||||
): Promise<Account> => {
|
||||
return async (dispatch, getState) => {
|
||||
let accountId: number;
|
||||
|
||||
if (typeof account.id === 'number') {
|
||||
if ('id' in account && typeof account.id === 'number') {
|
||||
accountId = account.id;
|
||||
} else {
|
||||
accountId = findAccountIdFromToken(token);
|
||||
@ -80,18 +66,17 @@ export function authenticate(
|
||||
token: newToken,
|
||||
refreshToken: newRefreshToken,
|
||||
user,
|
||||
// $FlowFixMe have no idea why it's causes error about missing properties
|
||||
} = await validateToken(accountId, token, refreshToken);
|
||||
const { auth } = getState();
|
||||
const account: Account = {
|
||||
const newAccount: Account = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
token: newToken,
|
||||
refreshToken: newRefreshToken,
|
||||
};
|
||||
dispatch(add(account));
|
||||
dispatch(activate(account));
|
||||
dispatch(add(newAccount));
|
||||
dispatch(activate(newAccount));
|
||||
dispatch(
|
||||
updateUser({
|
||||
isGuest: false,
|
||||
@ -104,7 +89,7 @@ export function authenticate(
|
||||
|
||||
if (!newRefreshToken) {
|
||||
// mark user as stranger (user does not want us to remember his account)
|
||||
sessionStorage.setItem(`stranger${account.id}`, 1);
|
||||
sessionStorage.setItem(`stranger${newAccount.id}`, 1);
|
||||
}
|
||||
|
||||
if (auth && auth.oauth && auth.oauth.clientId) {
|
||||
@ -117,7 +102,7 @@ export function authenticate(
|
||||
|
||||
await dispatch(setLocale(user.lang));
|
||||
|
||||
return account;
|
||||
return newAccount;
|
||||
} catch (resp) {
|
||||
// all the logic to get the valid token was failed,
|
||||
// looks like we have some problems with token
|
||||
@ -156,13 +141,13 @@ function findAccountIdFromToken(token: string): number {
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function ensureToken() {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
export function ensureToken(): ThunkAction<Promise<void>> {
|
||||
return (dispatch, getState) => {
|
||||
const { token } = getActiveAccount(getState()) || {};
|
||||
|
||||
try {
|
||||
const SAFETY_FACTOR = 300; // ask new token earlier to overcome time desynchronization problem
|
||||
const { exp } = getJwtPayloads(token);
|
||||
const { exp } = getJwtPayloads(token as any);
|
||||
|
||||
if (exp - SAFETY_FACTOR < Date.now() / 1000) {
|
||||
return dispatch(requestNewToken());
|
||||
@ -192,12 +177,12 @@ export function ensureToken() {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function recoverFromTokenError(
|
||||
error: ?{
|
||||
status: number,
|
||||
message: string,
|
||||
},
|
||||
) {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
error: {
|
||||
status: number;
|
||||
message: string;
|
||||
} | void,
|
||||
): ThunkAction<Promise<void>> {
|
||||
return (dispatch, getState) => {
|
||||
if (error && error.status === 401) {
|
||||
const activeAccount = getActiveAccount(getState());
|
||||
|
||||
@ -234,8 +219,8 @@ export function recoverFromTokenError(
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function requestNewToken() {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
export function requestNewToken(): ThunkAction<Promise<void>> {
|
||||
return (dispatch, getState) => {
|
||||
const { refreshToken } = getActiveAccount(getState()) || {};
|
||||
|
||||
if (!refreshToken) {
|
||||
@ -266,14 +251,13 @@ export function requestNewToken() {
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function revoke(account: Account) {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
const accountToReplace: ?Account = getState().accounts.available.find(
|
||||
({ id }) => id !== account.id,
|
||||
);
|
||||
export function revoke(account: Account): ThunkAction<Promise<void>> {
|
||||
return async (dispatch, getState) => {
|
||||
const accountToReplace: Account | null =
|
||||
getState().accounts.available.find(({ id }) => id !== account.id) || null;
|
||||
|
||||
if (accountToReplace) {
|
||||
return dispatch(authenticate(accountToReplace))
|
||||
await dispatch(authenticate(accountToReplace))
|
||||
.finally(() => {
|
||||
// we need to logout user, even in case, when we can
|
||||
// not authenticate him with new account
|
||||
@ -285,14 +269,16 @@ export function revoke(account: Account) {
|
||||
.catch(() => {
|
||||
// we don't care
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return dispatch(logoutAll());
|
||||
};
|
||||
}
|
||||
|
||||
export function relogin(email?: string) {
|
||||
return (dispatch: Dispatch, getState: () => State) => {
|
||||
export function relogin(email?: string): ThunkAction<Promise<void>> {
|
||||
return async (dispatch, getState) => {
|
||||
const activeAccount = getActiveAccount(getState());
|
||||
|
||||
if (!email && activeAccount) {
|
||||
@ -303,8 +289,8 @@ export function relogin(email?: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function logoutAll() {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
export function logoutAll(): ThunkAction<Promise<void>> {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(setGuest());
|
||||
|
||||
const {
|
||||
@ -332,8 +318,8 @@ export function logoutAll() {
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function logoutStrangers() {
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<void> => {
|
||||
export function logoutStrangers(): ThunkAction<Promise<void>> {
|
||||
return async (dispatch, getState) => {
|
||||
const {
|
||||
accounts: { available },
|
||||
} = getState();
|
||||
@ -343,9 +329,7 @@ export function logoutStrangers() {
|
||||
!refreshToken && !sessionStorage.getItem(`stranger${id}`);
|
||||
|
||||
if (available.some(isStranger)) {
|
||||
const accountToReplace = available.filter(
|
||||
account => !isStranger(account),
|
||||
)[0];
|
||||
const accountToReplace = available.find(account => !isStranger(account));
|
||||
|
||||
if (accountToReplace) {
|
||||
available.filter(isStranger).forEach(account => {
|
||||
@ -354,10 +338,14 @@ export function logoutStrangers() {
|
||||
});
|
||||
|
||||
if (activeAccount && isStranger(activeAccount)) {
|
||||
return dispatch(authenticate(accountToReplace));
|
||||
await dispatch(authenticate(accountToReplace));
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return dispatch(logoutAll());
|
||||
await dispatch(logoutAll());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import type {
|
||||
import {
|
||||
Account,
|
||||
AddAction,
|
||||
RemoveAction,
|
||||
@ -10,7 +9,7 @@ import type {
|
||||
|
||||
export const ADD = 'accounts:add';
|
||||
/**
|
||||
* @api private
|
||||
* @private
|
||||
*
|
||||
* @param {Account} account
|
||||
*
|
||||
@ -25,7 +24,7 @@ export function add(account: Account): AddAction {
|
||||
|
||||
export const REMOVE = 'accounts:remove';
|
||||
/**
|
||||
* @api private
|
||||
* @private
|
||||
*
|
||||
* @param {Account} account
|
||||
*
|
||||
@ -40,7 +39,7 @@ export function remove(account: Account): RemoveAction {
|
||||
|
||||
export const ACTIVATE = 'accounts:activate';
|
||||
/**
|
||||
* @api private
|
||||
* @private
|
||||
*
|
||||
* @param {Account} account
|
||||
*
|
||||
@ -55,7 +54,7 @@ export function activate(account: Account): ActivateAction {
|
||||
|
||||
export const RESET = 'accounts:reset';
|
||||
/**
|
||||
* @api private
|
||||
* @private
|
||||
*
|
||||
* @returns {object} - action definition
|
||||
*/
|
@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
export type { State as AccountsState, Account } from './reducer';
|
||||
export { default as AccountSwitcher } from './AccountSwitcher';
|
2
src/components/accounts/index.ts
Normal file
2
src/components/accounts/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { State as AccountsState, Account } from './reducer';
|
||||
export { default as AccountSwitcher } from './AccountSwitcher';
|
@ -1,7 +1,6 @@
|
||||
import expect from 'test/unexpected';
|
||||
|
||||
import accounts from 'components/accounts/reducer';
|
||||
import { updateToken } from 'components/accounts/actions';
|
||||
import { updateToken } from './actions';
|
||||
import {
|
||||
add,
|
||||
remove,
|
||||
@ -12,30 +11,33 @@ import {
|
||||
ACTIVATE,
|
||||
UPDATE_TOKEN,
|
||||
RESET,
|
||||
} from 'components/accounts/actions/pure-actions';
|
||||
} from './actions/pure-actions';
|
||||
import accounts, { Account } from './reducer';
|
||||
|
||||
const account = {
|
||||
const account: Account = {
|
||||
id: 1,
|
||||
username: 'username',
|
||||
email: 'email@test.com',
|
||||
token: 'foo',
|
||||
};
|
||||
} as Account;
|
||||
|
||||
describe('Accounts reducer', () => {
|
||||
let initial;
|
||||
|
||||
beforeEach(() => {
|
||||
initial = accounts(undefined, {});
|
||||
initial = accounts(undefined, {} as any);
|
||||
});
|
||||
|
||||
it('should be empty', () =>
|
||||
expect(accounts(undefined, {}), 'to equal', {
|
||||
expect(accounts(undefined, {} as any), 'to equal', {
|
||||
active: null,
|
||||
available: [],
|
||||
}));
|
||||
|
||||
it('should return last state if unsupported action', () =>
|
||||
expect(accounts({ state: 'foo' }, {}), 'to equal', { state: 'foo' }));
|
||||
expect(accounts({ state: 'foo' } as any, {} as any), 'to equal', {
|
||||
state: 'foo',
|
||||
}));
|
||||
|
||||
describe(ACTIVATE, () => {
|
||||
it('sets active account', () => {
|
||||
@ -92,7 +94,12 @@ describe('Accounts reducer', () => {
|
||||
|
||||
it('throws, when account is invalid', () => {
|
||||
expect(
|
||||
() => accounts(initial, add()),
|
||||
() =>
|
||||
accounts(
|
||||
initial,
|
||||
// @ts-ignore
|
||||
add(),
|
||||
),
|
||||
'to throw',
|
||||
'Invalid or empty payload passed for accounts.add',
|
||||
);
|
||||
@ -109,7 +116,12 @@ describe('Accounts reducer', () => {
|
||||
|
||||
it('throws, when account is invalid', () => {
|
||||
expect(
|
||||
() => accounts(initial, remove()),
|
||||
() =>
|
||||
accounts(
|
||||
initial,
|
||||
// @ts-ignore
|
||||
remove(),
|
||||
),
|
||||
'to throw',
|
||||
'Invalid or empty payload passed for accounts.remove',
|
||||
);
|
@ -1,23 +1,22 @@
|
||||
// @flow
|
||||
export type Account = {
|
||||
id: number,
|
||||
username: string,
|
||||
email: string,
|
||||
token: string,
|
||||
refreshToken: ?string,
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
token: string;
|
||||
refreshToken: string | null;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
active: ?number,
|
||||
available: Array<Account>,
|
||||
active: number | null;
|
||||
available: Account[];
|
||||
};
|
||||
|
||||
export type AddAction = { type: 'accounts:add', payload: Account };
|
||||
export type RemoveAction = { type: 'accounts:remove', payload: Account };
|
||||
export type ActivateAction = { type: 'accounts:activate', payload: Account };
|
||||
export type AddAction = { type: 'accounts:add'; payload: Account };
|
||||
export type RemoveAction = { type: 'accounts:remove'; payload: Account };
|
||||
export type ActivateAction = { type: 'accounts:activate'; payload: Account };
|
||||
export type UpdateTokenAction = {
|
||||
type: 'accounts:updateToken',
|
||||
payload: string,
|
||||
type: 'accounts:updateToken';
|
||||
payload: string;
|
||||
};
|
||||
export type ResetAction = { type: 'accounts:reset' };
|
||||
|
||||
@ -28,14 +27,16 @@ type Action =
|
||||
| UpdateTokenAction
|
||||
| ResetAction;
|
||||
|
||||
export function getActiveAccount(state: { accounts: State }): ?Account {
|
||||
export function getActiveAccount(state: { accounts: State }): Account | null {
|
||||
const accountId = state.accounts.active;
|
||||
|
||||
return state.accounts.available.find(account => account.id === accountId);
|
||||
return (
|
||||
state.accounts.available.find(account => account.id === accountId) || null
|
||||
);
|
||||
}
|
||||
|
||||
export function getAvailableAccounts(state: {
|
||||
accounts: State,
|
||||
accounts: State;
|
||||
}): Array<Account> {
|
||||
return state.accounts.available;
|
||||
}
|
||||
@ -129,10 +130,7 @@ export default function accounts(
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
(action: empty);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import Helmet from 'react-helmet';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { FormattedMessage as Message, MessageDescriptor } from 'react-intl';
|
||||
|
||||
export default function AuthTitle({ title }: { title: { id: string } }) {
|
||||
export default function AuthTitle({ title }: { title: MessageDescriptor }) {
|
||||
return (
|
||||
<Message {...title}>
|
||||
{msg => (
|
@ -2,13 +2,16 @@
|
||||
* Helps with form fields binding, form serialization and errors rendering
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import AuthError from 'components/auth/authError/AuthError';
|
||||
import { userShape } from 'components/user/User';
|
||||
import { FormModel } from 'components/ui/form';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
export default class BaseAuthBody extends Component {
|
||||
export default class BaseAuthBody extends React.Component<
|
||||
// TODO: this may be converted to generic type RouteComponentProps<T>
|
||||
RouteComponentProps<{ [key: string]: any }>
|
||||
> {
|
||||
static contextTypes = {
|
||||
clearErrors: PropTypes.func.isRequired,
|
||||
resolve: PropTypes.func.isRequired,
|
||||
@ -26,6 +29,8 @@ export default class BaseAuthBody extends Component {
|
||||
user: userShape,
|
||||
};
|
||||
|
||||
autoFocusField: string | null = '';
|
||||
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextContext.auth.error !== this.context.auth.error) {
|
||||
this.form.setErrors(nextContext.auth.error || {});
|
||||
@ -33,12 +38,9 @@ export default class BaseAuthBody extends Component {
|
||||
}
|
||||
|
||||
renderErrors() {
|
||||
return this.form.hasErrors() ? (
|
||||
<AuthError
|
||||
error={this.form.getFirstError()}
|
||||
onClose={this.onClearErrors}
|
||||
/>
|
||||
) : null;
|
||||
const error = this.form.getFirstError();
|
||||
|
||||
return error && <AuthError error={error} onClose={this.onClearErrors} />;
|
||||
}
|
||||
|
||||
onFormSubmit() {
|
@ -1,13 +1,10 @@
|
||||
// @flow
|
||||
import type { User } from 'components/user';
|
||||
import type { AccountsState } from 'components/accounts';
|
||||
import type { Element } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { AccountsState } from 'components/accounts';
|
||||
import { AuthState } from 'components/auth';
|
||||
import { User } from 'components/user';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
|
||||
import {
|
||||
Panel,
|
||||
PanelBody,
|
||||
@ -17,13 +14,14 @@ import {
|
||||
import { getLogin } from 'components/auth/reducer';
|
||||
import { Form } from 'components/ui/form';
|
||||
import MeasureHeight from 'components/MeasureHeight';
|
||||
import { helpLinks as helpLinksStyles } from 'components/auth/helpLinks.scss';
|
||||
import defaultHelpLinksStyles from 'components/auth/helpLinks.scss';
|
||||
import panelStyles from 'components/ui/panel.scss';
|
||||
import icons from 'components/ui/icons.scss';
|
||||
import authFlow from 'services/authFlow';
|
||||
import { userShape } from 'components/user/User';
|
||||
|
||||
import * as actions from './actions';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
const opacitySpringConfig = { stiffness: 300, damping: 20 };
|
||||
const transformSpringConfig = { stiffness: 500, damping: 50, precision: 0.5 };
|
||||
@ -33,6 +31,8 @@ const changeContextSpringConfig = {
|
||||
precision: 0.5,
|
||||
};
|
||||
|
||||
const { helpLinksStyles } = defaultHelpLinksStyles;
|
||||
|
||||
type PanelId = string;
|
||||
|
||||
/**
|
||||
@ -78,98 +78,56 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
type ValidationError =
|
||||
| string
|
||||
| {
|
||||
type: string,
|
||||
payload: { [key: string]: any },
|
||||
type: string;
|
||||
payload: { [key: string]: any };
|
||||
};
|
||||
|
||||
type AnimationProps = {|
|
||||
opacitySpring: number,
|
||||
transformSpring: number,
|
||||
|};
|
||||
type AnimationProps = {
|
||||
opacitySpring: number;
|
||||
transformSpring: number;
|
||||
};
|
||||
|
||||
type AnimationContext = {
|
||||
key: PanelId,
|
||||
style: AnimationProps,
|
||||
key: PanelId;
|
||||
style: AnimationProps;
|
||||
data: {
|
||||
Title: Element<any>,
|
||||
Body: Element<any>,
|
||||
Footer: Element<any>,
|
||||
Links: Element<any>,
|
||||
hasBackButton: boolean,
|
||||
},
|
||||
Title: React.ReactElement<any>;
|
||||
Body: React.ReactElement<any>;
|
||||
Footer: React.ReactElement<any>;
|
||||
Links: React.ReactElement<any>;
|
||||
hasBackButton: boolean | ((props: Props) => boolean);
|
||||
};
|
||||
};
|
||||
|
||||
type OwnProps = {|
|
||||
Title: Element<any>,
|
||||
Body: Element<any>,
|
||||
Footer: Element<any>,
|
||||
Links: Element<any>,
|
||||
children?: Element<any>,
|
||||
|};
|
||||
type OwnProps = {
|
||||
Title: React.ReactElement<any>;
|
||||
Body: React.ReactElement<any>;
|
||||
Footer: React.ReactElement<any>;
|
||||
Links: React.ReactElement<any>;
|
||||
children?: React.ReactElement<any>;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
interface Props extends OwnProps {
|
||||
// context props
|
||||
auth: {
|
||||
error:
|
||||
| string
|
||||
| {
|
||||
type: string,
|
||||
payload: { [key: string]: any },
|
||||
},
|
||||
isLoading: boolean,
|
||||
login: string,
|
||||
},
|
||||
user: User,
|
||||
accounts: AccountsState,
|
||||
setErrors: ({ [key: string]: ValidationError }) => void,
|
||||
clearErrors: () => void,
|
||||
resolve: () => void,
|
||||
reject: () => void,
|
||||
};
|
||||
auth: AuthState;
|
||||
user: User;
|
||||
accounts: AccountsState;
|
||||
setErrors: (errors: { [key: string]: ValidationError }) => void;
|
||||
clearErrors: () => void;
|
||||
resolve: () => void;
|
||||
reject: () => void;
|
||||
}
|
||||
|
||||
type State = {
|
||||
contextHeight: number,
|
||||
panelId: PanelId | void,
|
||||
prevPanelId: PanelId | void,
|
||||
isHeightDirty: boolean,
|
||||
forceHeight: 1 | 0,
|
||||
direction: 'X' | 'Y',
|
||||
contextHeight: number;
|
||||
panelId: PanelId | void;
|
||||
prevPanelId: PanelId | void;
|
||||
isHeightDirty: boolean;
|
||||
forceHeight: 1 | 0;
|
||||
direction: 'X' | 'Y';
|
||||
};
|
||||
|
||||
class PanelTransition extends Component<Props, State> {
|
||||
static displayName = 'PanelTransition';
|
||||
|
||||
static propTypes = {
|
||||
// context props
|
||||
auth: PropTypes.shape({
|
||||
error: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
payload: PropTypes.object,
|
||||
}),
|
||||
]),
|
||||
isLoading: PropTypes.bool,
|
||||
login: PropTypes.string,
|
||||
}).isRequired,
|
||||
user: userShape.isRequired,
|
||||
accounts: PropTypes.shape({
|
||||
available: PropTypes.array,
|
||||
}),
|
||||
setErrors: PropTypes.func.isRequired,
|
||||
clearErrors: PropTypes.func.isRequired,
|
||||
resolve: PropTypes.func.isRequired,
|
||||
reject: PropTypes.func.isRequired,
|
||||
|
||||
// local props
|
||||
Title: PropTypes.element,
|
||||
Body: PropTypes.element,
|
||||
Footer: PropTypes.element,
|
||||
Links: PropTypes.element,
|
||||
children: PropTypes.element,
|
||||
};
|
||||
|
||||
class PanelTransition extends React.Component<Props, State> {
|
||||
static childContextTypes = {
|
||||
auth: PropTypes.shape({
|
||||
error: PropTypes.oneOfType([
|
||||
@ -191,23 +149,23 @@ class PanelTransition extends Component<Props, State> {
|
||||
reject: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
state: State = {
|
||||
contextHeight: 0,
|
||||
panelId: this.props.Body && (this.props.Body: any).type.panelId,
|
||||
panelId: this.props.Body && (this.props.Body.type as any).panelId,
|
||||
isHeightDirty: false,
|
||||
forceHeight: 0,
|
||||
direction: 'X',
|
||||
forceHeight: 0 as const,
|
||||
direction: 'X' as const,
|
||||
prevPanelId: undefined,
|
||||
};
|
||||
|
||||
isHeightMeasured: boolean = false;
|
||||
wasAutoFocused: boolean = false;
|
||||
body: null | {
|
||||
autoFocus: () => void,
|
||||
onFormSubmit: () => void,
|
||||
autoFocus: () => void;
|
||||
onFormSubmit: () => void;
|
||||
} = null;
|
||||
|
||||
timerIds = []; // this is a list of a probably running timeouts to clean on unmount
|
||||
timerIds: NodeJS.Timeout[] = []; // this is a list of a probably running timeouts to clean on unmount
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
@ -230,9 +188,9 @@ class PanelTransition extends Component<Props, State> {
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const nextPanel: PanelId =
|
||||
nextProps.Body && (nextProps.Body: any).type.panelId;
|
||||
nextProps.Body && (nextProps.Body.type as any).panelId;
|
||||
const prevPanel: PanelId =
|
||||
this.props.Body && (this.props.Body: any).type.panelId;
|
||||
this.props.Body && (this.props.Body.type as any).panelId;
|
||||
|
||||
if (nextPanel !== prevPanel) {
|
||||
const direction = this.getDirection(nextPanel, prevPanel);
|
||||
@ -276,9 +234,9 @@ class PanelTransition extends Component<Props, State> {
|
||||
panelId,
|
||||
hasGoBack,
|
||||
}: {
|
||||
panelId: PanelId,
|
||||
hasGoBack: boolean,
|
||||
} = (Body: any).type;
|
||||
panelId: PanelId;
|
||||
hasGoBack: boolean;
|
||||
} = Body.type as any;
|
||||
|
||||
const formHeight = this.state[`formHeight${panelId}`] || 0;
|
||||
|
||||
@ -315,7 +273,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
>
|
||||
{items => {
|
||||
const panels = items.filter(({ key }) => key !== 'common');
|
||||
const common = items.filter(({ key }) => key === 'common')[0];
|
||||
const [common] = items.filter(({ key }) => key === 'common');
|
||||
|
||||
const contentHeight = {
|
||||
overflow: 'hidden',
|
||||
@ -327,7 +285,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
this.tryToAutoFocus(panels.length);
|
||||
|
||||
const bodyHeight = {
|
||||
position: 'relative',
|
||||
position: 'relative' as const,
|
||||
height: `${common.style.heightSpring}px`,
|
||||
};
|
||||
|
||||
@ -394,10 +352,10 @@ class PanelTransition extends Component<Props, State> {
|
||||
getTransitionStyles(
|
||||
{ key }: AnimationContext,
|
||||
options: { isLeave?: boolean } = {},
|
||||
): {|
|
||||
transformSpring: number,
|
||||
opacitySpring: number,
|
||||
|} {
|
||||
): {
|
||||
transformSpring: number;
|
||||
opacitySpring: number;
|
||||
} {
|
||||
const { isLeave = false } = options;
|
||||
const { panelId, prevPanelId } = this.state;
|
||||
|
||||
@ -411,6 +369,8 @@ class PanelTransition extends Component<Props, State> {
|
||||
}
|
||||
|
||||
let sign =
|
||||
prevPanelId &&
|
||||
panelId &&
|
||||
currentContext.indexOf(panelId) > currentContext.indexOf(prevPanelId)
|
||||
? fromRight
|
||||
: fromLeft;
|
||||
@ -430,7 +390,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
}
|
||||
|
||||
getDirection(next: PanelId, prev: PanelId): 'X' | 'Y' {
|
||||
const context = contexts.find(context => context.includes(prev));
|
||||
const context = contexts.find(item => item.includes(prev));
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`Can not find context for transition ${prev} -> ${next}`);
|
||||
@ -442,6 +402,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
onUpdateHeight = (height: number, key: PanelId) => {
|
||||
const heightKey = `formHeight${key}`;
|
||||
|
||||
// @ts-ignore
|
||||
this.setState({
|
||||
[heightKey]: height,
|
||||
});
|
||||
@ -453,7 +414,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
onGoBack = (event: SyntheticEvent<HTMLElement>) => {
|
||||
onGoBack = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
authFlow.goBack();
|
||||
@ -482,7 +443,6 @@ class PanelTransition extends Component<Props, State> {
|
||||
|
||||
shouldMeasureHeight() {
|
||||
const errorString = Object.values(this.props.auth.error || {}).reduce(
|
||||
// $FlowFixMe
|
||||
(acc, item: ValidationError) => {
|
||||
if (typeof item === 'string') {
|
||||
return acc + item;
|
||||
@ -519,7 +479,7 @@ class PanelTransition extends Component<Props, State> {
|
||||
const scrollStyle = this.translate(transformSpring, 'Y');
|
||||
|
||||
const sideScrollStyle = {
|
||||
position: 'relative',
|
||||
position: 'relative' as const,
|
||||
zIndex: 2,
|
||||
...this.translate(-Math.abs(transformSpring)),
|
||||
};
|
||||
@ -549,7 +509,10 @@ class PanelTransition extends Component<Props, State> {
|
||||
const { transformSpring } = style;
|
||||
const { direction } = this.state;
|
||||
|
||||
let transform = this.translate(transformSpring, direction);
|
||||
let transform: { [key: string]: string } = this.translate(
|
||||
transformSpring,
|
||||
direction,
|
||||
);
|
||||
let verticalOrigin = 'top';
|
||||
|
||||
if (direction === 'Y') {
|
||||
@ -613,15 +576,15 @@ class PanelTransition extends Component<Props, State> {
|
||||
*/
|
||||
getDefaultTransitionStyles(
|
||||
key: string,
|
||||
{ opacitySpring }: $ReadOnly<AnimationProps>,
|
||||
): {|
|
||||
position: string,
|
||||
top: number,
|
||||
left: number,
|
||||
width: string,
|
||||
opacity: number,
|
||||
pointerEvents: string,
|
||||
|} {
|
||||
{ opacitySpring }: Readonly<AnimationProps>,
|
||||
): {
|
||||
position: 'absolute';
|
||||
top: number;
|
||||
left: number;
|
||||
width: string;
|
||||
opacity: number;
|
||||
pointerEvents: 'none' | 'auto';
|
||||
} {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@ -632,14 +595,6 @@ class PanelTransition extends Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @param {string} direction='X' - X|Y
|
||||
* @param direction
|
||||
* @param unit
|
||||
* @param {string} unit='%' - %|px etc
|
||||
* @returns {object}
|
||||
*/
|
||||
translate(value: number, direction: 'X' | 'Y' = 'X', unit: '%' | 'px' = '%') {
|
||||
return {
|
||||
WebkitTransform: `translate${direction}(${value}${unit})`,
|
||||
@ -648,8 +603,8 @@ class PanelTransition extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<Props, OwnProps, _, _, _, _>(
|
||||
state => {
|
||||
export default connect(
|
||||
(state: RootState) => {
|
||||
const login = getLogin(state);
|
||||
let user = {
|
||||
...state.user,
|
@ -1,11 +1,23 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { FormattedMessage as Message, MessageDescriptor } from 'react-intl';
|
||||
import { User } from 'components/user';
|
||||
import { userShape } from 'components/user/User';
|
||||
|
||||
export default function RejectionLink(props, context) {
|
||||
interface Props {
|
||||
isAvailable?: (context: Context) => boolean;
|
||||
payload?: { [key: string]: any };
|
||||
label: MessageDescriptor;
|
||||
}
|
||||
|
||||
export type RejectionLinkProps = Props;
|
||||
|
||||
interface Context {
|
||||
reject: (payload: { [key: string]: any } | undefined) => void;
|
||||
user: User;
|
||||
}
|
||||
|
||||
function RejectionLink(props: Props, context: Context) {
|
||||
if (props.isAvailable && !props.isAvailable(context)) {
|
||||
// TODO: if want to properly support multiple links, we should control
|
||||
// the dividers ' | ' rendered from factory too
|
||||
@ -26,16 +38,9 @@ export default function RejectionLink(props, context) {
|
||||
);
|
||||
}
|
||||
|
||||
RejectionLink.displayName = 'RejectionLink';
|
||||
RejectionLink.propTypes = {
|
||||
isAvailable: PropTypes.func, // a function from context to allow link visibility control
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
payload: PropTypes.object, // Custom payload for active state
|
||||
label: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
RejectionLink.contextTypes = {
|
||||
reject: PropTypes.func.isRequired,
|
||||
user: userShape,
|
||||
};
|
||||
|
||||
export default RejectionLink;
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import Body from './AcceptRulesBody';
|
||||
import messages from './AcceptRules.intl.json';
|
||||
|
@ -53,8 +53,8 @@ describe('components/auth/actions', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
request.get.restore();
|
||||
request.post.restore();
|
||||
(request.get as any).restore();
|
||||
(request.post as any).restore();
|
||||
});
|
||||
|
||||
describe('#oAuthValidate()', () => {
|
||||
@ -69,7 +69,7 @@ describe('components/auth/actions', () => {
|
||||
},
|
||||
};
|
||||
|
||||
request.get.returns(Promise.resolve(resp));
|
||||
(request.get as any).returns(Promise.resolve(resp));
|
||||
});
|
||||
|
||||
it('should send get request to an api', () =>
|
||||
@ -106,7 +106,7 @@ describe('components/auth/actions', () => {
|
||||
});
|
||||
|
||||
it('should post to api/oauth2/complete', () => {
|
||||
request.post.returns(
|
||||
(request.post as any).returns(
|
||||
Promise.resolve({
|
||||
redirectUri: '',
|
||||
}),
|
||||
@ -126,7 +126,7 @@ describe('components/auth/actions', () => {
|
||||
redirectUri: 'static_page?code=123&state=',
|
||||
};
|
||||
|
||||
request.post.returns(Promise.resolve(resp));
|
||||
(request.post as any).returns(Promise.resolve(resp));
|
||||
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expectDispatchCalls([
|
||||
@ -141,20 +141,20 @@ describe('components/auth/actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve to with success false and redirectUri for access_denied', () => {
|
||||
it('should resolve to with success false and redirectUri for access_denied', async () => {
|
||||
const resp = {
|
||||
statusCode: 401,
|
||||
error: 'access_denied',
|
||||
redirectUri: 'redirectUri',
|
||||
};
|
||||
|
||||
request.post.returns(Promise.reject(resp));
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
return callThunk(oAuthComplete).then(resp => {
|
||||
expect(resp, 'to equal', {
|
||||
success: false,
|
||||
redirectUri: 'redirectUri',
|
||||
});
|
||||
const data = await callThunk(oAuthComplete);
|
||||
|
||||
expect(data, 'to equal', {
|
||||
success: false,
|
||||
redirectUri: 'redirectUri',
|
||||
});
|
||||
});
|
||||
|
||||
@ -164,10 +164,10 @@ describe('components/auth/actions', () => {
|
||||
error: 'accept_required',
|
||||
};
|
||||
|
||||
request.post.returns(Promise.reject(resp));
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
return callThunk(oAuthComplete).catch(resp => {
|
||||
expect(resp.acceptRequired, 'to be true');
|
||||
return callThunk(oAuthComplete).catch(error => {
|
||||
expect(error.acceptRequired, 'to be true');
|
||||
expectDispatchCalls([[requirePermissionsAccept()]]);
|
||||
});
|
||||
});
|
||||
@ -176,7 +176,7 @@ describe('components/auth/actions', () => {
|
||||
describe('#login()', () => {
|
||||
describe('when correct login was entered', () => {
|
||||
beforeEach(() => {
|
||||
request.post.returns(
|
||||
(request.post as any).returns(
|
||||
Promise.reject({
|
||||
errors: {
|
||||
password: 'error.password_required',
|
@ -1,6 +1,3 @@
|
||||
// @flow
|
||||
import type { OauthData, Client, Scope } from 'services/api/oauth';
|
||||
import type { OAuthResponse } from 'services/api/authentication';
|
||||
import { browserHistory } from 'services/history';
|
||||
import logger from 'services/logger';
|
||||
import localStorage from 'services/localStorage';
|
||||
@ -16,20 +13,22 @@ import {
|
||||
login as loginEndpoint,
|
||||
forgotPassword as forgotPasswordEndpoint,
|
||||
recoverPassword as recoverPasswordEndpoint,
|
||||
OAuthResponse,
|
||||
} from 'services/api/authentication';
|
||||
import oauth from 'services/api/oauth';
|
||||
import oauth, { OauthData, Client, Scope } from 'services/api/oauth';
|
||||
import signup from 'services/api/signup';
|
||||
import dispatchBsod from 'components/ui/bsod/dispatchBsod';
|
||||
import { create as createPopup } from 'components/ui/popup/actions';
|
||||
import ContactForm from 'components/contact/ContactForm';
|
||||
import { ThunkAction, Dispatch } from 'reducers';
|
||||
|
||||
import { getCredentials } from './reducer';
|
||||
|
||||
type ValidationError =
|
||||
| string
|
||||
| {
|
||||
type: string,
|
||||
payload: { [key: string]: any },
|
||||
type: string;
|
||||
payload: { [key: string]: any };
|
||||
};
|
||||
|
||||
export { updateUser } from 'components/user/actions';
|
||||
@ -39,6 +38,7 @@ export {
|
||||
remove as removeAccount,
|
||||
activate as activateAccount,
|
||||
} from 'components/accounts/actions';
|
||||
import { Account } from 'components/accounts/reducer';
|
||||
|
||||
/**
|
||||
* Reoutes user to the previous page if it is possible
|
||||
@ -62,7 +62,7 @@ export function goBack(options: { fallbackUrl?: string }) {
|
||||
};
|
||||
}
|
||||
|
||||
export function redirect(url: string): () => Promise<*> {
|
||||
export function redirect(url: string): () => Promise<void> {
|
||||
loader.show();
|
||||
|
||||
return () =>
|
||||
@ -84,10 +84,10 @@ export function login({
|
||||
totp,
|
||||
rememberMe = false,
|
||||
}: {
|
||||
login: string,
|
||||
password?: string,
|
||||
totp?: string,
|
||||
rememberMe?: boolean,
|
||||
login: string;
|
||||
password?: string;
|
||||
totp?: string;
|
||||
rememberMe?: boolean;
|
||||
}) {
|
||||
return wrapInLoader(dispatch =>
|
||||
loginEndpoint({ login, password, totp, rememberMe })
|
||||
@ -129,8 +129,8 @@ export function forgotPassword({
|
||||
login = '',
|
||||
captcha = '',
|
||||
}: {
|
||||
login: string,
|
||||
captcha: string,
|
||||
login: string;
|
||||
captcha: string;
|
||||
}) {
|
||||
return wrapInLoader((dispatch, getState) =>
|
||||
forgotPasswordEndpoint(login, captcha)
|
||||
@ -150,9 +150,9 @@ export function recoverPassword({
|
||||
newPassword = '',
|
||||
newRePassword = '',
|
||||
}: {
|
||||
key: string,
|
||||
newPassword: string,
|
||||
newRePassword: string,
|
||||
key: string;
|
||||
newPassword: string;
|
||||
newRePassword: string;
|
||||
}) {
|
||||
return wrapInLoader(dispatch =>
|
||||
recoverPasswordEndpoint(key, newPassword, newRePassword)
|
||||
@ -169,12 +169,12 @@ export function register({
|
||||
captcha = '',
|
||||
rulesAgreement = false,
|
||||
}: {
|
||||
email: string,
|
||||
username: string,
|
||||
password: string,
|
||||
rePassword: string,
|
||||
captcha: string,
|
||||
rulesAgreement: boolean,
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
rePassword: string;
|
||||
captcha: string;
|
||||
rulesAgreement: boolean;
|
||||
}) {
|
||||
return wrapInLoader((dispatch, getState) =>
|
||||
signup
|
||||
@ -203,7 +203,11 @@ export function register({
|
||||
);
|
||||
}
|
||||
|
||||
export function activate({ key = '' }: { key: string }) {
|
||||
export function activate({
|
||||
key = '',
|
||||
}: {
|
||||
key: string;
|
||||
}): ThunkAction<Promise<Account>> {
|
||||
return wrapInLoader(dispatch =>
|
||||
signup
|
||||
.activate({ key })
|
||||
@ -216,8 +220,8 @@ export function resendActivation({
|
||||
email = '',
|
||||
captcha,
|
||||
}: {
|
||||
email: string,
|
||||
captcha: string,
|
||||
email: string;
|
||||
captcha: string;
|
||||
}) {
|
||||
return wrapInLoader(dispatch =>
|
||||
signup
|
||||
@ -249,7 +253,7 @@ export const SET_CREDENTIALS = 'auth:setCredentials';
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
export function setLogin(login: ?string) {
|
||||
export function setLogin(login: string | null) {
|
||||
return {
|
||||
type: SET_CREDENTIALS,
|
||||
payload: login
|
||||
@ -260,8 +264,8 @@ export function setLogin(login: ?string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function relogin(login: ?string) {
|
||||
return (dispatch: (Function | Object) => void, getState: () => Object) => {
|
||||
export function relogin(login: string | null): ThunkAction {
|
||||
return (dispatch, getState) => {
|
||||
const credentials = getCredentials(getState());
|
||||
const returnUrl =
|
||||
credentials.returnUrl || location.pathname + location.search;
|
||||
@ -284,11 +288,11 @@ function requestTotp({
|
||||
password,
|
||||
rememberMe,
|
||||
}: {
|
||||
login: string,
|
||||
password: string,
|
||||
rememberMe: boolean,
|
||||
}) {
|
||||
return (dispatch: (Function | Object) => void, getState: () => Object) => {
|
||||
login: string;
|
||||
password: string;
|
||||
rememberMe: boolean;
|
||||
}): ThunkAction {
|
||||
return (dispatch, getState) => {
|
||||
// merging with current credentials to propogate returnUrl
|
||||
const credentials = getCredentials(getState());
|
||||
|
||||
@ -314,7 +318,7 @@ export function setAccountSwitcher(isOn: boolean) {
|
||||
}
|
||||
|
||||
export const ERROR = 'auth:error';
|
||||
export function setErrors(errors: ?{ [key: string]: ValidationError }) {
|
||||
export function setErrors(errors: { [key: string]: ValidationError } | null) {
|
||||
return {
|
||||
type: ERROR,
|
||||
payload: errors,
|
||||
@ -363,7 +367,7 @@ export function oAuthValidate(oauthData: OauthData) {
|
||||
);
|
||||
let prompt = (oauthData.prompt || 'none')
|
||||
.split(',')
|
||||
.map(item => item.trim);
|
||||
.map(item => item.trim());
|
||||
|
||||
if (prompt.includes('none')) {
|
||||
prompt = ['none'];
|
||||
@ -404,15 +408,29 @@ export function oAuthValidate(oauthData: OauthData) {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function oAuthComplete(params: { accept?: boolean } = {}) {
|
||||
return wrapInLoader((dispatch, getState) =>
|
||||
oauth.complete(getState().auth.oauth, params).then(
|
||||
resp => {
|
||||
return wrapInLoader(
|
||||
async (
|
||||
dispatch,
|
||||
getState,
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
redirectUri: string;
|
||||
}> => {
|
||||
const oauthData = getState().auth.oauth;
|
||||
|
||||
if (!oauthData) {
|
||||
throw new Error('Can not complete oAuth. Oauth data does not exist');
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await oauth.complete(oauthData, params);
|
||||
localStorage.removeItem('oauthData');
|
||||
|
||||
if (resp.redirectUri.startsWith('static_page')) {
|
||||
const code = (resp.redirectUri.match(/code=(.+)&/) || [])[1];
|
||||
const displayCode = resp.redirectUri === 'static_page_with_code';
|
||||
resp.redirectUri = (resp.redirectUri.match(/^(.+)\?/) || [])[1];
|
||||
|
||||
const [, code] = resp.redirectUri.match(/code=(.+)&/) || [];
|
||||
[, resp.redirectUri] = resp.redirectUri.match(/^(.+)\?/) || [];
|
||||
|
||||
dispatch(
|
||||
setOAuthCode({
|
||||
@ -424,34 +442,32 @@ export function oAuthComplete(params: { accept?: boolean } = {}) {
|
||||
}
|
||||
|
||||
return resp;
|
||||
},
|
||||
(
|
||||
resp:
|
||||
} catch (error) {
|
||||
const resp:
|
||||
| {
|
||||
acceptRequired: boolean,
|
||||
acceptRequired: boolean;
|
||||
}
|
||||
| {
|
||||
unauthorized: boolean,
|
||||
},
|
||||
) => {
|
||||
if (resp.acceptRequired) {
|
||||
unauthorized: boolean;
|
||||
} = error;
|
||||
|
||||
if ('acceptRequired' in resp) {
|
||||
dispatch(requirePermissionsAccept());
|
||||
|
||||
return Promise.reject(resp);
|
||||
}
|
||||
|
||||
return handleOauthParamsValidation(resp);
|
||||
},
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleOauthParamsValidation(
|
||||
resp:
|
||||
| {
|
||||
userMessage?: string,
|
||||
}
|
||||
| Object = {},
|
||||
resp: {
|
||||
[key: string]: any;
|
||||
userMessage?: string;
|
||||
} = {},
|
||||
) {
|
||||
dispatchBsod();
|
||||
localStorage.removeItem('oauthData');
|
||||
@ -470,8 +486,8 @@ export function setClient({ id, name, description }: Client) {
|
||||
};
|
||||
}
|
||||
|
||||
export function resetOAuth() {
|
||||
return (dispatch: (Function | Object) => void) => {
|
||||
export function resetOAuth(): ThunkAction {
|
||||
return (dispatch): void => {
|
||||
localStorage.removeItem('oauthData');
|
||||
dispatch(setOAuthRequest({}));
|
||||
};
|
||||
@ -479,25 +495,20 @@ export function resetOAuth() {
|
||||
|
||||
/**
|
||||
* Resets all temporary state related to auth
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function resetAuth() {
|
||||
return (
|
||||
dispatch: (Function | Object) => void,
|
||||
getSate: () => Object,
|
||||
): Promise<void> => {
|
||||
export function resetAuth(): ThunkAction {
|
||||
return (dispatch, getSate): Promise<void> => {
|
||||
dispatch(setLogin(null));
|
||||
dispatch(resetOAuth());
|
||||
// ensure current account is valid
|
||||
const activeAccount = getActiveAccount(getSate());
|
||||
|
||||
if (activeAccount) {
|
||||
return Promise.resolve(dispatch(authenticate(activeAccount))).catch(
|
||||
() => {
|
||||
return Promise.resolve(dispatch(authenticate(activeAccount)))
|
||||
.then(() => {})
|
||||
.catch(() => {
|
||||
// its okay. user will be redirected to an appropriate place
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
@ -506,13 +517,13 @@ export function resetAuth() {
|
||||
|
||||
export const SET_OAUTH = 'set_oauth';
|
||||
export function setOAuthRequest(oauth: {
|
||||
client_id?: string,
|
||||
redirect_uri?: string,
|
||||
response_type?: string,
|
||||
scope?: string,
|
||||
prompt?: string,
|
||||
loginHint?: string,
|
||||
state?: string,
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
response_type?: string;
|
||||
scope?: string;
|
||||
prompt?: string;
|
||||
loginHint?: string;
|
||||
state?: string;
|
||||
}) {
|
||||
return {
|
||||
type: SET_OAUTH,
|
||||
@ -530,9 +541,9 @@ export function setOAuthRequest(oauth: {
|
||||
|
||||
export const SET_OAUTH_RESULT = 'set_oauth_result';
|
||||
export function setOAuthCode(oauth: {
|
||||
success: boolean,
|
||||
code: string,
|
||||
displayCode: boolean,
|
||||
success: boolean;
|
||||
code: string;
|
||||
displayCode: boolean;
|
||||
}) {
|
||||
return {
|
||||
type: SET_OAUTH_RESULT,
|
||||
@ -571,12 +582,12 @@ export function setLoadingState(isLoading: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
function wrapInLoader(fn) {
|
||||
return (dispatch: (Function | Object) => void, getState: Object) => {
|
||||
function wrapInLoader<T>(fn: ThunkAction<Promise<T>>): ThunkAction<Promise<T>> {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(setLoadingState(true));
|
||||
const endLoading = () => dispatch(setLoadingState(false));
|
||||
|
||||
return Reflect.apply(fn, null, [dispatch, getState]).then(
|
||||
return fn(dispatch, getState, undefined).then(
|
||||
resp => {
|
||||
endLoading();
|
||||
|
||||
@ -598,12 +609,12 @@ function needActivation() {
|
||||
});
|
||||
}
|
||||
|
||||
function authHandler(dispatch) {
|
||||
return (resp: OAuthResponse) =>
|
||||
function authHandler(dispatch: Dispatch) {
|
||||
return (resp: OAuthResponse): Promise<Account> =>
|
||||
dispatch(
|
||||
authenticate({
|
||||
token: resp.access_token,
|
||||
refreshToken: resp.refresh_token,
|
||||
refreshToken: resp.refresh_token || null,
|
||||
}),
|
||||
).then(resp => {
|
||||
dispatch(setLogin(null));
|
||||
@ -612,10 +623,7 @@ function authHandler(dispatch) {
|
||||
});
|
||||
}
|
||||
|
||||
function validationErrorsHandler(
|
||||
dispatch: (Function | Object) => void,
|
||||
repeatUrl?: string,
|
||||
) {
|
||||
function validationErrorsHandler(dispatch: Dispatch, repeatUrl?: string) {
|
||||
return resp => {
|
||||
if (resp.errors) {
|
||||
const firstError = Object.keys(resp.errors)[0];
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import messages from './Activation.intl.json';
|
||||
import Body from './ActivationBody';
|
||||
|
@ -1,18 +1,15 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { Button } from 'components/ui/form';
|
||||
import { FooterMenu } from 'components/footerMenu';
|
||||
|
||||
import styles from './appInfo.scss';
|
||||
import messages from './AppInfo.intl.json';
|
||||
|
||||
export default class AppInfo extends Component<{
|
||||
name?: string,
|
||||
description?: string,
|
||||
onGoToAuth: () => void,
|
||||
export default class AppInfo extends React.Component<{
|
||||
name?: string;
|
||||
description?: string;
|
||||
onGoToAuth: () => void;
|
||||
}> {
|
||||
render() {
|
||||
const { name, description, onGoToAuth } = this.props;
|
@ -1,4 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
import factory from '../factory';
|
||||
import messages from './ChooseAccount.intl.json';
|
||||
import Body from './ChooseAccountBody';
|
||||
|
@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from 'components/ui/form';
|
||||
import RejectionLink from 'components/auth/RejectionLink';
|
||||
import AuthTitle from 'components/auth/AuthTitle';
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string|object} options.title - panel title
|
||||
* @param {ReactElement} options.body
|
||||
* @param {object} options.footer - config for footer Button
|
||||
* @param {Array|object|null} options.links - link config or an array of link configs
|
||||
*
|
||||
* @returns {object} - structure, required for auth panel to work
|
||||
*/
|
||||
export default function(options) {
|
||||
return () => ({
|
||||
Title: () => <AuthTitle title={options.title} />,
|
||||
Body: options.body,
|
||||
Footer: () => <Button type="submit" {...options.footer} />,
|
||||
Links: () =>
|
||||
options.links ? (
|
||||
<span>
|
||||
{[]
|
||||
.concat(options.links)
|
||||
.map((link, index) => [
|
||||
index ? ' | ' : '',
|
||||
<RejectionLink {...link} key={index} />,
|
||||
])}
|
||||
</span>
|
||||
) : null,
|
||||
});
|
||||
}
|
50
src/components/auth/factory.tsx
Normal file
50
src/components/auth/factory.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'components/ui/form';
|
||||
import RejectionLink, {
|
||||
RejectionLinkProps,
|
||||
} from 'components/auth/RejectionLink';
|
||||
import AuthTitle from 'components/auth/AuthTitle';
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
import { Color } from 'components/ui';
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string|object} options.title - panel title
|
||||
* @param {React.ReactElement} options.body
|
||||
* @param {object} options.footer - config for footer Button
|
||||
* @param {Array|object|null} options.links - link config or an array of link configs
|
||||
*
|
||||
* @returns {object} - structure, required for auth panel to work
|
||||
*/
|
||||
export default function({
|
||||
title,
|
||||
body,
|
||||
footer,
|
||||
links,
|
||||
}: {
|
||||
title: MessageDescriptor;
|
||||
body: React.ElementType;
|
||||
footer: {
|
||||
color?: Color;
|
||||
label: string | MessageDescriptor;
|
||||
autoFocus?: boolean;
|
||||
};
|
||||
links?: RejectionLinkProps | RejectionLinkProps[];
|
||||
}) {
|
||||
return () => ({
|
||||
Title: () => <AuthTitle title={title} />,
|
||||
Body: body,
|
||||
Footer: () => <Button type="submit" {...footer} />,
|
||||
Links: () =>
|
||||
links ? (
|
||||
<span>
|
||||
{([] as RejectionLinkProps[])
|
||||
.concat(links)
|
||||
.map((link, index) => [
|
||||
index ? ' | ' : '',
|
||||
<RejectionLink {...link} key={index} />,
|
||||
])}
|
||||
</span>
|
||||
) : null,
|
||||
});
|
||||
}
|
@ -1,35 +1,32 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { Button } from 'components/ui/form';
|
||||
import copy from 'services/copy';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
import messages from './Finish.intl.json';
|
||||
import styles from './finish.scss';
|
||||
|
||||
class Finish extends Component {
|
||||
static displayName = 'Finish';
|
||||
|
||||
static propTypes = {
|
||||
appName: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
state: PropTypes.string.isRequired,
|
||||
displayCode: PropTypes.bool,
|
||||
success: PropTypes.bool,
|
||||
};
|
||||
interface Props {
|
||||
appName: string;
|
||||
code?: string;
|
||||
state: string;
|
||||
displayCode?: string;
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
class Finish extends React.Component<Props> {
|
||||
render() {
|
||||
const { appName, code, state, displayCode, success } = this.props;
|
||||
const authData = JSON.stringify({
|
||||
auth_code: code, // eslint-disable-line
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
auth_code: code,
|
||||
state,
|
||||
});
|
||||
|
||||
history.pushState(null, null, `#${authData}`);
|
||||
history.pushState(null, document.title, `#${authData}`);
|
||||
|
||||
return (
|
||||
<div className={styles.finishPage}>
|
||||
@ -89,14 +86,25 @@ class Finish extends Component {
|
||||
|
||||
onCopyClick = event => {
|
||||
event.preventDefault();
|
||||
copy(this.props.code);
|
||||
|
||||
const { code } = this.props;
|
||||
|
||||
if (code) {
|
||||
copy(code);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(({ auth }) => ({
|
||||
appName: auth.client.name,
|
||||
code: auth.oauth.code,
|
||||
displayCode: auth.oauth.displayCode,
|
||||
state: auth.oauth.state,
|
||||
success: auth.oauth.success,
|
||||
}))(Finish);
|
||||
export default connect(({ auth }: RootState) => {
|
||||
if (!auth || !auth.client || !auth.oauth) {
|
||||
throw new Error('Can not connect Finish component. No auth data in state');
|
||||
}
|
||||
|
||||
return {
|
||||
appName: auth.client.name,
|
||||
code: auth.oauth.code,
|
||||
displayCode: auth.oauth.displayCode,
|
||||
state: auth.oauth.state,
|
||||
success: auth.oauth.success,
|
||||
};
|
||||
})(Finish);
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import messages from './ForgotPassword.intl.json';
|
||||
import Body from './ForgotPasswordBody';
|
||||
|
1
src/components/auth/index.ts
Normal file
1
src/components/auth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { State as AuthState } from './reducer';
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import Body from './LoginBody';
|
||||
import messages from './Login.intl.json';
|
||||
|
@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import Body from './MfaBody';
|
||||
import messages from './Mfa.intl.json';
|
||||
import passwordMessages from '../password/Password.intl.json';
|
@ -1,8 +1,5 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { PanelIcon } from 'components/ui/Panel';
|
||||
import { Input } from 'components/ui/form';
|
||||
import BaseAuthBody from 'components/auth/BaseAuthBody';
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import Body from './PasswordBody';
|
||||
import messages from './Password.intl.json';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
import factory from '../factory';
|
||||
import messages from './Permissions.intl.json';
|
||||
import Body from './PermissionsBody';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import factory from 'components/auth/factory';
|
||||
|
||||
import factory from '../factory';
|
||||
import messages from './RecoverPassword.intl.json';
|
||||
import Body from './RecoverPasswordBody';
|
||||
|
@ -1,12 +1,11 @@
|
||||
import expect from 'test/unexpected';
|
||||
|
||||
import auth from 'components/auth/reducer';
|
||||
import auth from './reducer';
|
||||
import {
|
||||
setLogin,
|
||||
SET_CREDENTIALS,
|
||||
setAccountSwitcher,
|
||||
SET_SWITCHER,
|
||||
} from 'components/auth/actions';
|
||||
} from './actions';
|
||||
|
||||
describe('components/auth/reducer', () => {
|
||||
describe(SET_CREDENTIALS, () => {
|
||||
@ -25,7 +24,7 @@ describe('components/auth/reducer', () => {
|
||||
|
||||
describe(SET_SWITCHER, () => {
|
||||
it('should be enabled by default', () =>
|
||||
expect(auth(undefined, {}), 'to satisfy', {
|
||||
expect(auth(undefined, {} as any), 'to satisfy', {
|
||||
isSwitcherEnabled: true,
|
||||
}));
|
||||
|
@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import {
|
||||
@ -14,15 +13,50 @@ import {
|
||||
} from './actions';
|
||||
|
||||
type Credentials = {
|
||||
login?: string,
|
||||
password?: string,
|
||||
rememberMe?: boolean,
|
||||
returnUrl?: string,
|
||||
isRelogin?: boolean,
|
||||
isTotpRequired?: boolean,
|
||||
login?: string;
|
||||
password?: string;
|
||||
rememberMe?: boolean;
|
||||
returnUrl?: string;
|
||||
isRelogin?: boolean;
|
||||
isTotpRequired?: boolean;
|
||||
};
|
||||
|
||||
export default combineReducers<_, { action: string, payload?: mixed }>({
|
||||
export interface Client {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
credentials: Credentials;
|
||||
error:
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
payload: { [key: string]: any };
|
||||
};
|
||||
isLoading: boolean;
|
||||
isSwitcherEnabled: boolean;
|
||||
client: Client | null;
|
||||
login: string;
|
||||
oauth: {
|
||||
clientId: string;
|
||||
redirectUrl: string;
|
||||
responseType: string;
|
||||
description: string;
|
||||
scope: string;
|
||||
prompt: string;
|
||||
loginHint: string;
|
||||
state: string;
|
||||
success?: boolean;
|
||||
code?: string;
|
||||
displayCode?: string;
|
||||
acceptRequired?: boolean;
|
||||
} | null;
|
||||
scopes: string[];
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
credentials,
|
||||
error,
|
||||
isLoading,
|
||||
@ -52,8 +86,8 @@ function credentials(
|
||||
type,
|
||||
payload,
|
||||
}: {
|
||||
type: string,
|
||||
payload: ?Credentials,
|
||||
type: string;
|
||||
payload: Credentials | null;
|
||||
},
|
||||
) {
|
||||
if (type === SET_CREDENTIALS) {
|
||||
@ -93,7 +127,7 @@ function isLoading(state = false, { type, payload = null }) {
|
||||
}
|
||||
}
|
||||
|
||||
function client(state = null, { type, payload = {} }) {
|
||||
function client(state = null, { type, payload }) {
|
||||
switch (type) {
|
||||
case SET_CLIENT:
|
||||
return {
|
||||
@ -107,7 +141,7 @@ function client(state = null, { type, payload = {} }) {
|
||||
}
|
||||
}
|
||||
|
||||
function oauth(state = null, { type, payload = {} }) {
|
||||
function oauth(state: State | null = null, { type, payload }) {
|
||||
switch (type) {
|
||||
case SET_OAUTH:
|
||||
return {
|
||||
@ -149,10 +183,10 @@ function scopes(state = [], { type, payload = [] }) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getLogin(state: Object): ?string {
|
||||
export function getLogin(state: { [key: string]: any }): string | null {
|
||||
return state.auth.credentials.login || null;
|
||||
}
|
||||
|
||||
export function getCredentials(state: Object): Credentials {
|
||||
export function getCredentials(state: { [key: string]: any }): Credentials {
|
||||
return state.auth.credentials;
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import factory from 'components/auth/factory';
|
||||
import activationMessages from 'components/auth/activation/Activation.intl.json';
|
||||
import forgotPasswordMessages from 'components/auth/forgotPassword/ForgotPassword.intl.json';
|
||||
|
||||
import factory from '../factory';
|
||||
import activationMessages from '../activation/Activation.intl.json';
|
||||
import forgotPasswordMessages from '../forgotPassword/ForgotPassword.intl.json';
|
||||
import messages from './Register.intl.json';
|
||||
import Body from './RegisterBody';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import factory from 'components/auth/factory';
|
||||
import forgotPasswordMessages from 'components/auth/forgotPassword/ForgotPassword.intl.json';
|
||||
|
||||
import factory from '../factory';
|
||||
import forgotPasswordMessages from '../forgotPassword/ForgotPassword.intl.json';
|
||||
import messages from './ResendActivation.intl.json';
|
||||
import Body from './ResendActivationBody';
|
||||
|
@ -2,16 +2,15 @@ import React from 'react';
|
||||
import expect from 'test/unexpected';
|
||||
import sinon from 'sinon';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import feedback from 'services/api/feedback';
|
||||
import { User } from 'components/user';
|
||||
|
||||
import { ContactForm } from 'components/contact/ContactForm';
|
||||
import { ContactForm } from './ContactForm';
|
||||
|
||||
describe('ContactForm', () => {
|
||||
describe('when rendered', () => {
|
||||
const user = {};
|
||||
const user = {} as User;
|
||||
let component;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -57,7 +56,7 @@ describe('ContactForm', () => {
|
||||
describe('when rendered with user', () => {
|
||||
const user = {
|
||||
email: 'foo@bar.com',
|
||||
};
|
||||
} as User;
|
||||
let component;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -76,11 +75,11 @@ describe('ContactForm', () => {
|
||||
describe('when email was successfully sent', () => {
|
||||
const user = {
|
||||
email: 'foo@bar.com',
|
||||
};
|
||||
} as User;
|
||||
let component;
|
||||
|
||||
beforeEach(() => {
|
||||
component = shallow(<ContactForm user={{ user }} />);
|
||||
component = shallow(<ContactForm user={user} />);
|
||||
|
||||
component.setState({ isSuccessfullySent: true });
|
||||
});
|
||||
@ -93,7 +92,7 @@ describe('ContactForm', () => {
|
||||
xdescribe('validation', () => {
|
||||
const user = {
|
||||
email: 'foo@bar.com',
|
||||
};
|
||||
} as User;
|
||||
let component;
|
||||
let wrapper;
|
||||
|
||||
@ -102,7 +101,7 @@ describe('ContactForm', () => {
|
||||
|
||||
wrapper = mount(
|
||||
<IntlProvider locale="en" defaultLocale="en">
|
||||
<ContactForm user={{ user }} ref={el => (component = el)} />
|
||||
<ContactForm user={user} ref={el => (component = el)} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
});
|
||||
@ -118,7 +117,7 @@ describe('ContactForm', () => {
|
||||
describe('when user submits form', () => {
|
||||
const user = {
|
||||
email: 'foo@bar.com',
|
||||
};
|
||||
} as User;
|
||||
let component;
|
||||
let wrapper;
|
||||
const requestData = {
|
||||
@ -131,14 +130,14 @@ describe('ContactForm', () => {
|
||||
sinon.stub(feedback, 'send');
|
||||
|
||||
// TODO: add polyfill for from validation for jsdom
|
||||
if (!Element.prototype.checkValidity) {
|
||||
Element.prototype.checkValidity = () => true;
|
||||
if (!(Element.prototype as any).checkValidity) {
|
||||
(Element.prototype as any).checkValidity = () => true;
|
||||
}
|
||||
|
||||
// TODO: try to rewrite with unexpected-react
|
||||
wrapper = mount(
|
||||
<IntlProvider locale="en" defaultLocale="en">
|
||||
<ContactForm user={{ user }} ref={el => (component = el)} />
|
||||
<ContactForm user={user} ref={el => (component = el)} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
@ -151,7 +150,7 @@ describe('ContactForm', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
feedback.send.restore();
|
||||
(feedback.send as any).restore();
|
||||
});
|
||||
|
||||
xit('should call onSubmit', () => {
|
||||
@ -163,7 +162,7 @@ describe('ContactForm', () => {
|
||||
});
|
||||
|
||||
it('should call send with required data', () => {
|
||||
feedback.send.returns(Promise.resolve());
|
||||
(feedback.send as any).returns(Promise.resolve());
|
||||
|
||||
component.onSubmit();
|
||||
|
||||
@ -171,7 +170,7 @@ describe('ContactForm', () => {
|
||||
});
|
||||
|
||||
it('should set isSuccessfullySent', () => {
|
||||
feedback.send.returns(Promise.resolve());
|
||||
(feedback.send as any).returns(Promise.resolve());
|
||||
|
||||
return component
|
||||
.onSubmit()
|
||||
@ -181,7 +180,7 @@ describe('ContactForm', () => {
|
||||
});
|
||||
|
||||
it('should handle isLoading during request', () => {
|
||||
feedback.send.returns(Promise.resolve());
|
||||
(feedback.send as any).returns(Promise.resolve());
|
||||
|
||||
const promise = component.onSubmit();
|
||||
|
||||
@ -193,7 +192,7 @@ describe('ContactForm', () => {
|
||||
});
|
||||
|
||||
it('should render success message with user email', () => {
|
||||
feedback.send.returns(Promise.resolve());
|
||||
(feedback.send as any).returns(Promise.resolve());
|
||||
|
||||
return component
|
||||
.onSubmit()
|
@ -1,5 +1,4 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
@ -14,7 +13,9 @@ import {
|
||||
import feedback from 'services/api/feedback';
|
||||
import icons from 'components/ui/icons.scss';
|
||||
import popupStyles from 'components/ui/popup/popup.scss';
|
||||
import { RootState } from 'reducers';
|
||||
import logger from 'services/logger';
|
||||
import { User } from 'components/user';
|
||||
|
||||
import styles from './contactForm.scss';
|
||||
import messages from './contactForm.intl.json';
|
||||
@ -28,16 +29,17 @@ const CONTACT_CATEGORIES = [
|
||||
<Message key="m5" {...messages.other} />,
|
||||
];
|
||||
|
||||
export class ContactForm extends Component {
|
||||
static displayName = 'ContactForm';
|
||||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
user: PropTypes.shape({
|
||||
email: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export class ContactForm extends React.Component<
|
||||
{
|
||||
onClose: () => void;
|
||||
user: User;
|
||||
},
|
||||
{
|
||||
isLoading: boolean;
|
||||
isSuccessfullySent: boolean;
|
||||
lastEmail: string | null;
|
||||
}
|
||||
> {
|
||||
static defaultProps = {
|
||||
onClose() {},
|
||||
};
|
||||
@ -45,6 +47,7 @@ export class ContactForm extends Component {
|
||||
state = {
|
||||
isLoading: false,
|
||||
isSuccessfullySent: false,
|
||||
lastEmail: null,
|
||||
};
|
||||
|
||||
form = new FormModel();
|
||||
@ -191,6 +194,6 @@ export class ContactForm extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
export default connect((state: RootState) => ({
|
||||
user: state.user,
|
||||
}))(ContactForm);
|
@ -1,15 +1,10 @@
|
||||
// @flow
|
||||
import type { ElementConfig } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { create as createPopup } from 'components/ui/popup/actions';
|
||||
import ContactForm from './ContactForm';
|
||||
|
||||
type OwnProps = $Exact<ElementConfig<'a'>>;
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
createContactPopup: () => void,
|
||||
type Props = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||
createContactPopup: () => void;
|
||||
};
|
||||
|
||||
function ContactLink({ createContactPopup, ...props }: Props) {
|
||||
@ -27,6 +22,6 @@ function ContactLink({ createContactPopup, ...props }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default connect<Props, OwnProps, _, _, _, _>(null, {
|
||||
export default connect(null, {
|
||||
createContactPopup: () => createPopup({ Popup: ContactForm }),
|
||||
})(ContactLink);
|
@ -1,3 +1 @@
|
||||
// @flow
|
||||
|
||||
export { default as ContactLink } from './ContactLink';
|
@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { LinkButton } from 'components/ui/form';
|
||||
import { COLOR_GREEN, COLOR_BLUE } from 'components/ui';
|
||||
import { ContactLink } from 'components/contact';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
|
||||
import styles from './applicationsIndex.scss';
|
||||
import messages from './ApplicationsIndex.intl.json';
|
||||
@ -16,16 +15,16 @@ import toolsIcon from './icons/tools.svg';
|
||||
import ApplicationsList from './list';
|
||||
|
||||
type Props = {
|
||||
clientId?: ?string,
|
||||
resetClientId: () => void, // notify parent to remove clientId from current location.href
|
||||
displayForGuest: boolean,
|
||||
applications: Array<OauthAppResponse>,
|
||||
isLoading: boolean,
|
||||
deleteApp: string => Promise<any>,
|
||||
resetApp: (string, boolean) => Promise<any>,
|
||||
clientId: string | null;
|
||||
resetClientId: () => void; // notify parent to remove clientId from current location.href
|
||||
displayForGuest: boolean;
|
||||
applications: Array<OauthAppResponse>;
|
||||
isLoading: boolean;
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
};
|
||||
|
||||
export default class ApplicationsIndex extends Component<Props> {
|
||||
export default class ApplicationsIndex extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.container}>
|
@ -1,17 +1,16 @@
|
||||
// @flow
|
||||
import type { Dispatch } from 'redux';
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import type { User } from 'components/user';
|
||||
import oauth from 'services/api/oauth';
|
||||
import { Dispatch } from 'redux';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
import { User } from 'components/user';
|
||||
|
||||
import type { Apps } from './reducer';
|
||||
import { Apps } from './reducer';
|
||||
|
||||
type SetAvailableAction = {
|
||||
type: 'apps:setAvailable',
|
||||
payload: Array<OauthAppResponse>,
|
||||
type: 'apps:setAvailable';
|
||||
payload: Array<OauthAppResponse>;
|
||||
};
|
||||
type DeleteAppAction = { type: 'apps:deleteApp', payload: string };
|
||||
type AddAppAction = { type: 'apps:addApp', payload: OauthAppResponse };
|
||||
type DeleteAppAction = { type: 'apps:deleteApp'; payload: string };
|
||||
type AddAppAction = { type: 'apps:addApp'; payload: OauthAppResponse };
|
||||
export type Action = SetAvailableAction | DeleteAppAction | AddAppAction;
|
||||
|
||||
export function setAppsList(apps: Array<OauthAppResponse>): SetAvailableAction {
|
||||
@ -24,7 +23,7 @@ export function setAppsList(apps: Array<OauthAppResponse>): SetAvailableAction {
|
||||
export function getApp(
|
||||
state: { apps: Apps },
|
||||
clientId: string,
|
||||
): ?OauthAppResponse {
|
||||
): OauthAppResponse | null {
|
||||
return state.apps.available.find(app => app.clientId === clientId) || null;
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
import type { ComponentType } from 'react';
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import type { ApplicationType } from 'components/dev/apps';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
import { ApplicationType } from 'components/dev/apps';
|
||||
import { Form, FormModel, Button } from 'components/ui/form';
|
||||
import { BackButton } from 'components/profile/ProfileForm';
|
||||
import { COLOR_GREEN } from 'components/ui';
|
||||
@ -19,10 +17,10 @@ import WebsiteType from './WebsiteType';
|
||||
import MinecraftServerType from './MinecraftServerType';
|
||||
|
||||
const typeToForm: {
|
||||
[key: ApplicationType]: {
|
||||
label: MessageDescriptor,
|
||||
component: ComponentType<any>,
|
||||
},
|
||||
[K in ApplicationType]: {
|
||||
label: MessageDescriptor;
|
||||
component: React.ComponentType<any>;
|
||||
};
|
||||
} = {
|
||||
[TYPE_APPLICATION]: {
|
||||
label: messages.website,
|
||||
@ -34,21 +32,24 @@ const typeToForm: {
|
||||
},
|
||||
};
|
||||
|
||||
const typeToLabel: {
|
||||
[key: ApplicationType]: MessageDescriptor,
|
||||
} = Object.keys(typeToForm).reduce((result, key: ApplicationType) => {
|
||||
result[key] = typeToForm[key].label;
|
||||
const typeToLabel = Object.keys(typeToForm).reduce(
|
||||
(result, key: ApplicationType) => {
|
||||
result[key] = typeToForm[key].label;
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
return result;
|
||||
},
|
||||
{} as {
|
||||
[K in ApplicationType]: MessageDescriptor;
|
||||
},
|
||||
);
|
||||
|
||||
export default class ApplicationForm extends Component<{
|
||||
app: OauthAppResponse,
|
||||
form: FormModel,
|
||||
displayTypeSwitcher?: boolean,
|
||||
type: ?ApplicationType,
|
||||
setType: ApplicationType => void,
|
||||
onSubmit: FormModel => Promise<void>,
|
||||
export default class ApplicationForm extends React.Component<{
|
||||
app: OauthAppResponse;
|
||||
form: FormModel;
|
||||
displayTypeSwitcher?: boolean;
|
||||
type: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
onSubmit: (form: FormModel) => Promise<void>;
|
||||
}> {
|
||||
static defaultProps = {
|
||||
setType: () => {},
|
@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import type { ApplicationType } from 'components/dev/apps';
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
import React from 'react';
|
||||
import { ApplicationType } from 'components/dev/apps';
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
import { SKIN_LIGHT } from 'components/ui';
|
||||
import { Radio } from 'components/ui/form';
|
||||
|
||||
@ -13,10 +12,10 @@ export default function ApplicationTypeSwitcher({
|
||||
selectedType,
|
||||
}: {
|
||||
appTypes: {
|
||||
[key: ApplicationType]: MessageDescriptor,
|
||||
},
|
||||
selectedType: ?ApplicationType,
|
||||
setType: (type: ApplicationType) => void,
|
||||
[K in ApplicationType]: MessageDescriptor;
|
||||
};
|
||||
selectedType: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
import { Input, FormModel } from 'components/ui/form';
|
||||
import { SKIN_LIGHT } from 'components/ui';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
|
||||
import messages from './ApplicationForm.intl.json';
|
||||
|
||||
@ -12,8 +11,8 @@ export default function MinecraftServerType({
|
||||
form,
|
||||
app,
|
||||
}: {
|
||||
form: FormModel,
|
||||
app: OauthAppResponse,
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Input, TextArea, FormModel } from 'components/ui/form';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
import { SKIN_LIGHT } from 'components/ui';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
|
||||
@ -12,8 +11,8 @@ export default function WebsiteType({
|
||||
form,
|
||||
app,
|
||||
}: {
|
||||
form: FormModel,
|
||||
app: OauthAppResponse,
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
export type ApplicationType = 'application' | 'minecraft-server';
|
||||
export const TYPE_APPLICATION: 'application' = 'application';
|
||||
export const TYPE_MINECRAFT_SERVER: 'minecraft-server' = 'minecraft-server';
|
@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { SKIN_LIGHT, COLOR_BLACK, COLOR_RED } from 'components/ui';
|
||||
import { Input, Button } from 'components/ui/form';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
import Collapse from 'components/ui/collapse';
|
||||
|
||||
import styles from '../applicationsIndex.scss';
|
||||
@ -29,29 +28,34 @@ const actionButtons = [
|
||||
},
|
||||
];
|
||||
|
||||
export default class ApplicationItem extends Component<
|
||||
interface State {
|
||||
selectedAction: string | null;
|
||||
isActionPerforming: boolean;
|
||||
detailsHeight: number;
|
||||
translateY: number;
|
||||
}
|
||||
|
||||
export default class ApplicationItem extends React.Component<
|
||||
{
|
||||
application: OauthAppResponse,
|
||||
expand: boolean,
|
||||
onTileClick: string => void,
|
||||
onResetSubmit: (string, boolean) => Promise<*>,
|
||||
onDeleteSubmit: string => Promise<*>,
|
||||
},
|
||||
{
|
||||
selectedAction: ?string,
|
||||
isActionPerforming: boolean,
|
||||
detailsHeight: number,
|
||||
translateY: number,
|
||||
application: OauthAppResponse;
|
||||
expand: boolean;
|
||||
onTileClick: (clientId: string) => void;
|
||||
onResetSubmit: (
|
||||
clientId: string,
|
||||
resetClientSecret: boolean,
|
||||
) => Promise<void>;
|
||||
onDeleteSubmit: (clientId: string) => Promise<void>;
|
||||
},
|
||||
State
|
||||
> {
|
||||
state = {
|
||||
state: State = {
|
||||
selectedAction: null,
|
||||
isActionPerforming: false,
|
||||
translateY: 0,
|
||||
detailsHeight: 0,
|
||||
};
|
||||
|
||||
actionContainer: ?HTMLDivElement;
|
||||
actionContainer: HTMLDivElement | null;
|
||||
|
||||
render() {
|
||||
const { application: app, expand } = this.props;
|
||||
@ -137,7 +141,7 @@ export default class ApplicationItem extends Component<
|
||||
label={label}
|
||||
color={COLOR_BLACK}
|
||||
className={styles.appActionButton}
|
||||
disabled={selectedAction && selectedAction !== type}
|
||||
disabled={!!selectedAction && selectedAction !== type}
|
||||
onClick={this.onActionButtonClick(type)}
|
||||
small
|
||||
/>
|
||||
@ -237,7 +241,7 @@ export default class ApplicationItem extends Component<
|
||||
}
|
||||
}
|
||||
|
||||
setActiveAction = (type: ?string) => {
|
||||
setActiveAction = (type: string | null) => {
|
||||
const { actionContainer } = this;
|
||||
|
||||
if (!actionContainer) {
|
||||
@ -268,7 +272,7 @@ export default class ApplicationItem extends Component<
|
||||
}
|
||||
};
|
||||
|
||||
onActionButtonClick = (type: ?string) => () => {
|
||||
onActionButtonClick = (type: string | null) => () => {
|
||||
this.setActiveAction(type === this.state.selectedAction ? null : type);
|
||||
};
|
||||
|
@ -1,25 +1,24 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import React from 'react';
|
||||
import { restoreScroll } from 'components/ui/scroll/scroll';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { LinkButton } from 'components/ui/form';
|
||||
import { COLOR_GREEN } from 'components/ui';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
|
||||
import messages from '../ApplicationsIndex.intl.json';
|
||||
import styles from '../applicationsIndex.scss';
|
||||
import ApplicationItem from './ApplicationItem';
|
||||
|
||||
type Props = {
|
||||
applications: Array<OauthAppResponse>,
|
||||
deleteApp: string => Promise<any>,
|
||||
resetApp: (string, boolean) => Promise<any>,
|
||||
resetClientId: () => void,
|
||||
clientId: ?string,
|
||||
applications: OauthAppResponse[];
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
resetClientId: () => void;
|
||||
clientId: string | null;
|
||||
};
|
||||
|
||||
type State = {
|
||||
expandedApp: ?string,
|
||||
expandedApp: string | null;
|
||||
};
|
||||
|
||||
export default class ApplicationsList extends React.Component<Props, State> {
|
||||
@ -27,7 +26,7 @@ export default class ApplicationsList extends React.Component<Props, State> {
|
||||
expandedApp: null,
|
||||
};
|
||||
|
||||
appsRefs: { [key: string]: ?HTMLDivElement } = {};
|
||||
appsRefs: { [key: string]: HTMLDivElement | null } = {};
|
||||
|
||||
componentDidMount() {
|
||||
this.checkForActiveApp();
|
@ -1,3 +1 @@
|
||||
// @flow
|
||||
|
||||
export { default } from './ApplicationsList';
|
@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import type { OauthAppResponse } from 'services/api/oauth';
|
||||
import { OauthAppResponse } from 'services/api/oauth';
|
||||
|
||||
import type { Action } from './actions';
|
||||
import { Action } from './actions';
|
||||
|
||||
export type Apps = {|
|
||||
+available: OauthAppResponse[],
|
||||
|};
|
||||
export interface Apps {
|
||||
available: OauthAppResponse[];
|
||||
}
|
||||
|
||||
const defaults: Apps = {
|
||||
available: [],
|
||||
@ -45,8 +44,7 @@ export default function apps(state: Apps = defaults, action: Action): Apps {
|
||||
};
|
||||
|
||||
default:
|
||||
(action.type: empty);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
@ -10,18 +9,11 @@ import { ContactLink } from 'components/contact';
|
||||
import styles from './footerMenu.scss';
|
||||
import messages from './footerMenu.intl.json';
|
||||
|
||||
type OwnProps = {|
|
||||
createContactPopup: () => void,
|
||||
|};
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
createLanguageSwitcherPopup: () => void,
|
||||
createLanguageSwitcherPopup: () => void;
|
||||
};
|
||||
|
||||
class FooterMenu extends Component<Props> {
|
||||
static displayName = 'FooterMenu';
|
||||
|
||||
class FooterMenu extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.footerMenu}>
|
||||
@ -51,7 +43,7 @@ class FooterMenu extends Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
onLanguageSwitcher = (event: SyntheticMouseEvent<HTMLElement>) => {
|
||||
onLanguageSwitcher = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.createLanguageSwitcherPopup();
|
||||
@ -60,7 +52,7 @@ class FooterMenu extends Component<Props> {
|
||||
|
||||
// mark this component, as not pure, because it is stateless,
|
||||
// but should be re-rendered, if current lang was changed
|
||||
export default connect<Props, OwnProps, _, _, _, _>(
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
createLanguageSwitcherPopup: () => createPopup({ Popup: LanguageSwitcher }),
|
@ -1,18 +1,12 @@
|
||||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import type { IntlShape } from 'react-intl';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RawIntlProvider } from 'react-intl';
|
||||
import { RawIntlProvider, IntlShape } from 'react-intl';
|
||||
import i18n from 'services/i18n';
|
||||
|
||||
type OwnProps = {|
|
||||
children: Node,
|
||||
|};
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
locale: string,
|
||||
children: React.ReactNode;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
function IntlProvider({ children, locale }: Props) {
|
||||
@ -27,6 +21,6 @@ function IntlProvider({ children, locale }: Props) {
|
||||
return <RawIntlProvider value={intl}>{children}</RawIntlProvider>;
|
||||
}
|
||||
|
||||
export default connect<Props, OwnProps, _, _, _, _>(({ i18n }) => i18n)(
|
||||
export default connect(({ i18n: i18nState }: RootState) => i18nState)(
|
||||
IntlProvider,
|
||||
);
|
@ -1,9 +1,10 @@
|
||||
// @flow
|
||||
import i18n from 'services/i18n';
|
||||
|
||||
export const SET_LOCALE = 'i18n:setLocale';
|
||||
export function setLocale(desiredLocale: string) {
|
||||
return async (dispatch: (action: Object) => any): Promise<string> => {
|
||||
return async (
|
||||
dispatch: (action: { [key: string]: any }) => any,
|
||||
): Promise<string> => {
|
||||
const locale = i18n.detectLanguage(desiredLocale);
|
||||
|
||||
dispatch(_setLocale(locale));
|
@ -1,3 +1,2 @@
|
||||
// @flow
|
||||
export { default as IntlProvider } from './IntlProvider';
|
||||
export { default as localeFlags } from './localeFlags';
|
@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import supportedLocales from 'i18n/index.json';
|
||||
import supportedLocales from 'i18n';
|
||||
|
||||
const localeToCountryCode = {
|
||||
en: 'gb',
|
||||
@ -11,10 +10,10 @@ const localeToCountryCode = {
|
||||
sr: 'rs',
|
||||
zh: 'cn',
|
||||
};
|
||||
const SUPPORTED_LANGUAGES: Array<string> = Object.keys(supportedLocales);
|
||||
const SUPPORTED_LANGUAGES: string[] = Object.keys(supportedLocales);
|
||||
|
||||
export default {
|
||||
getCountryList(): Array<string> {
|
||||
getCountryList(): string[] {
|
||||
return SUPPORTED_LANGUAGES.map(
|
||||
locale => localeToCountryCode[locale] || locale,
|
||||
);
|
@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import i18n from 'services/i18n';
|
||||
|
||||
import { SET_LOCALE } from './actions';
|
||||
|
||||
type State = { locale: string };
|
||||
export type State = { locale: string };
|
||||
|
||||
const defaultState = {
|
||||
locale: i18n.detectLanguage(),
|
||||
@ -11,7 +10,7 @@ const defaultState = {
|
||||
|
||||
export default function(
|
||||
state: State = defaultState,
|
||||
{ type, payload }: { type: string, payload: State },
|
||||
{ type, payload }: { type: string; payload: State },
|
||||
): State {
|
||||
if (type === SET_LOCALE) {
|
||||
return payload;
|
@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { TransitionMotion, spring, presets } from 'react-motion';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import LocaleItem from './LocaleItem';
|
||||
import messages from './languageSwitcher.intl.json';
|
||||
import { LocalesMap } from './LanguageSwitcher';
|
||||
import styles from './languageSwitcher.scss';
|
||||
|
||||
import thatFuckingPumpkin from './images/that_fucking_pumpkin.svg';
|
||||
@ -13,16 +13,14 @@ import mayTheForceBeWithYou from './images/may_the_force_be_with_you.svg';
|
||||
import biteMyShinyMetalAss from './images/bite_my_shiny_metal_ass.svg';
|
||||
import iTookAnArrowInMyKnee from './images/i_took_an_arrow_in_my_knee.svg';
|
||||
|
||||
import type { LocalesMap } from './LanguageSwitcher';
|
||||
|
||||
const itemHeight = 51;
|
||||
|
||||
export default class LanguageList extends React.Component<{
|
||||
selectedLocale: string,
|
||||
langs: LocalesMap,
|
||||
onChangeLang: (lang: string) => void,
|
||||
selectedLocale: string;
|
||||
langs: LocalesMap;
|
||||
onChangeLang: (lang: string) => void;
|
||||
}> {
|
||||
emptyListStateInner: ?HTMLDivElement;
|
||||
emptyListStateInner: HTMLDivElement | null;
|
||||
|
||||
render() {
|
||||
const { selectedLocale, langs } = this.props;
|
||||
@ -51,7 +49,7 @@ export default class LanguageList extends React.Component<{
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={(elem: ?HTMLDivElement) =>
|
||||
ref={(elem: HTMLDivElement | null) =>
|
||||
(this.emptyListStateInner = elem)
|
||||
}
|
||||
className={styles.emptyLanguagesList}
|
||||
@ -114,7 +112,7 @@ export default class LanguageList extends React.Component<{
|
||||
}
|
||||
|
||||
onChangeLang(lang: string) {
|
||||
return (event: SyntheticMouseEvent<HTMLElement>) => {
|
||||
return (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.onChangeLang(lang);
|
@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
import type { IntlShape } from 'react-intl';
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage as Message, injectIntl } from 'react-intl';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message, injectIntl, IntlShape } from 'react-intl';
|
||||
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';
|
||||
import formStyles from 'components/ui/form/form.scss';
|
||||
import popupStyles from 'components/ui/popup/popup.scss';
|
||||
import icons from 'components/ui/icons.scss';
|
||||
@ -13,41 +11,41 @@ import icons from 'components/ui/icons.scss';
|
||||
import styles from './languageSwitcher.scss';
|
||||
import messages from './languageSwitcher.intl.json';
|
||||
import LanguageList from './LanguageList';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
const translateUrl = 'http://ely.by/translate';
|
||||
|
||||
export type LocaleData = {
|
||||
code: string,
|
||||
name: string,
|
||||
englishName: string,
|
||||
progress: number,
|
||||
isReleased: boolean,
|
||||
code: string;
|
||||
name: string;
|
||||
englishName: string;
|
||||
progress: number;
|
||||
isReleased: boolean;
|
||||
};
|
||||
|
||||
export type LocalesMap = { [code: string]: LocaleData };
|
||||
|
||||
type OwnProps = {|
|
||||
onClose: () => void,
|
||||
langs: LocalesMap,
|
||||
type OwnProps = {
|
||||
onClose: () => void;
|
||||
langs: LocalesMap;
|
||||
emptyCaptions: Array<{
|
||||
src: string,
|
||||
caption: string,
|
||||
}>,
|
||||
|};
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
intl: IntlShape,
|
||||
selectedLocale: string,
|
||||
changeLang: (lang: string) => void,
|
||||
src: string;
|
||||
caption: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
class LanguageSwitcher extends Component<
|
||||
interface Props extends OwnProps {
|
||||
intl: IntlShape;
|
||||
selectedLocale: string;
|
||||
changeLang: (lang: string) => void;
|
||||
}
|
||||
|
||||
class LanguageSwitcher extends React.Component<
|
||||
Props,
|
||||
{
|
||||
filter: string,
|
||||
filteredLangs: LocalesMap,
|
||||
},
|
||||
filter: string;
|
||||
filteredLangs: LocalesMap;
|
||||
}
|
||||
> {
|
||||
state = {
|
||||
filter: '',
|
||||
@ -125,7 +123,7 @@ class LanguageSwitcher extends Component<
|
||||
setTimeout(this.props.onClose, 300);
|
||||
}
|
||||
|
||||
onFilterUpdate = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
onFilterUpdate = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const filter = event.currentTarget.value.trim().toLowerCase();
|
||||
const { langs } = this.props;
|
||||
|
||||
@ -149,7 +147,7 @@ class LanguageSwitcher extends Component<
|
||||
};
|
||||
|
||||
onFilterKeyPress() {
|
||||
return (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
|
||||
return (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== 'Enter' || this.state.filter === '') {
|
||||
return;
|
||||
}
|
||||
@ -166,8 +164,8 @@ class LanguageSwitcher extends Component<
|
||||
}
|
||||
|
||||
export default injectIntl(
|
||||
connect<Props, OwnProps, _, _, _, _>(
|
||||
state => ({
|
||||
connect(
|
||||
(state: RootState) => ({
|
||||
selectedLocale: state.i18n.locale,
|
||||
}),
|
||||
{
|
@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { localeFlags } from 'components/i18n';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import messages from './languageSwitcher.intl.json';
|
||||
import styles from './languageSwitcher.scss';
|
||||
import type { LocaleData } from './LanguageSwitcher';
|
||||
import { LocaleData } from './LanguageSwitcher';
|
||||
|
||||
export default function LocaleItem({ locale }: { locale: LocaleData }) {
|
||||
const { code, name, englishName, progress, isReleased } = locale;
|
@ -1,21 +1,18 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { localeFlags } from 'components/i18n';
|
||||
import LANGS from 'i18n/index.json';
|
||||
import LANGS from 'i18n';
|
||||
import { connect } from 'react-redux';
|
||||
import { create as createPopup } from 'components/ui/popup/actions';
|
||||
import LanguageSwitcher from 'components/languageSwitcher';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
import styles from './link.scss';
|
||||
|
||||
type OwnProps = {||};
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
userLang: string,
|
||||
interfaceLocale: string,
|
||||
showLanguageSwitcherPopup: Function,
|
||||
userLang: string;
|
||||
interfaceLocale: string;
|
||||
showLanguageSwitcherPopup: (event: React.MouseEvent<HTMLSpanElement>) => void;
|
||||
};
|
||||
|
||||
function LanguageLink({
|
||||
@ -45,8 +42,8 @@ function LanguageLink({
|
||||
);
|
||||
}
|
||||
|
||||
export default connect<Props, OwnProps, _, _, _, _>(
|
||||
state => ({
|
||||
export default connect(
|
||||
(state: RootState) => ({
|
||||
userLang: state.user.lang,
|
||||
interfaceLocale: state.i18n.locale,
|
||||
}),
|
@ -1,3 +1,2 @@
|
||||
// @flow
|
||||
export { default } from './LanguageSwitcher';
|
||||
export { default as ChangeLanguageLink } from './changeLanguageLink/ChangeLanguageLink';
|
@ -1,30 +1,26 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
import { ChangeLanguageLink } from 'components/languageSwitcher';
|
||||
import { RelativeTime } from 'components/ui';
|
||||
import { User } from 'components/user';
|
||||
import RulesPage from 'pages/rules/RulesPage';
|
||||
import { RootState } from 'reducers';
|
||||
|
||||
import ProfileField from './ProfileField';
|
||||
import styles from './profile.scss';
|
||||
import profileForm from './profileForm.scss';
|
||||
import messages from './Profile.intl.json';
|
||||
import RulesPage from 'pages/rules/RulesPage';
|
||||
|
||||
import type { User } from 'components/user';
|
||||
|
||||
type OwnProps = {||};
|
||||
|
||||
type Props = {
|
||||
...OwnProps,
|
||||
user: User,
|
||||
interfaceLocale: string,
|
||||
user: User;
|
||||
interfaceLocale: string;
|
||||
};
|
||||
|
||||
class Profile extends Component<Props> {
|
||||
UUID: ?HTMLElement;
|
||||
class Profile extends React.Component<Props> {
|
||||
UUID: HTMLElement | null;
|
||||
|
||||
render() {
|
||||
const { user, interfaceLocale } = this.props;
|
||||
@ -172,6 +168,11 @@ class Profile extends Component<Props> {
|
||||
|
||||
try {
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
selection.removeAllRanges();
|
||||
@ -181,12 +182,12 @@ class Profile extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
setUUID(el: ?HTMLElement) {
|
||||
setUUID(el: HTMLElement | null) {
|
||||
this.UUID = el;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect<Props, OwnProps, _, _, _, _>(({ user, i18n }) => ({
|
||||
export default connect(({ user, i18n }: RootState) => ({
|
||||
user,
|
||||
interfaceLocale: i18n.locale,
|
||||
}))(Profile);
|
@ -1,12 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import styles from './profile.scss';
|
||||
|
||||
export default class ProfileField extends Component {
|
||||
static displayName = 'ProfileField';
|
||||
export default class ProfileField extends React.Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
|
||||
.isRequired,
|
||||
|
@ -1,20 +1,14 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import FormComponent from 'components/ui/form/FormComponent';
|
||||
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
|
||||
import messages from './ProfileForm.intl.json';
|
||||
|
||||
export class BackButton extends FormComponent<{
|
||||
to: string,
|
||||
to: string;
|
||||
}> {
|
||||
static displayName = 'BackButton';
|
||||
|
||||
static defaultProps = {
|
||||
to: '/',
|
||||
};
|
@ -36,6 +36,8 @@ export default class ChangeEmail extends Component {
|
||||
\`${componentName}\`. Validation failed.`,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
@ -120,7 +122,6 @@ export default class ChangeEmail extends Component {
|
||||
? messages.changeEmailButton
|
||||
: messages.sendEmailButton
|
||||
}
|
||||
onClick={this.onSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -1,29 +1,28 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import logger from 'services/logger';
|
||||
import { disable as disableMFA } from 'services/api/mfa';
|
||||
import { FormModel } from 'components/ui/form';
|
||||
|
||||
import MfaDisableForm from './disableForm/MfaDisableForm';
|
||||
import MfaStatus from './status/MfaStatus';
|
||||
|
||||
import type { FormModel } from 'components/ui/form';
|
||||
|
||||
export default class MfaDisable extends Component<
|
||||
export default class MfaDisable extends React.Component<
|
||||
{
|
||||
onSubmit: (form: FormModel, sendData: () => Promise<*>) => Promise<*>,
|
||||
onComplete: Function,
|
||||
onSubmit: (form: FormModel, sendData: () => Promise<void>) => Promise<void>;
|
||||
onComplete: () => void;
|
||||
},
|
||||
{
|
||||
showForm?: boolean,
|
||||
},
|
||||
showForm: boolean;
|
||||
}
|
||||
> {
|
||||
static contextTypes = {
|
||||
userId: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
state = {};
|
||||
state = {
|
||||
showForm: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showForm } = this.state;
|
||||
@ -40,9 +39,9 @@ export default class MfaDisable extends Component<
|
||||
onSubmit = (form: FormModel) => {
|
||||
return this.props
|
||||
.onSubmit(form, () => {
|
||||
const data = form.serialize();
|
||||
const { totp } = form.serialize() as { totp: string };
|
||||
|
||||
return disableMFA(this.context.userId, data);
|
||||
return disableMFA(this.context.userId, totp);
|
||||
})
|
||||
.then(() => this.props.onComplete())
|
||||
.catch(resp => {
|
@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, FormModel } from 'components/ui/form';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
import Stepper from 'components/ui/stepper';
|
||||
@ -9,23 +7,22 @@ import { SlideMotion } from 'components/ui/motion';
|
||||
import { ScrollIntoView } from 'components/ui/scroll';
|
||||
import logger from 'services/logger';
|
||||
import { getSecret, enable as enableMFA } from 'services/api/mfa';
|
||||
import { Form } from 'components/ui/form';
|
||||
|
||||
import Instructions from './instructions';
|
||||
import KeyForm from './keyForm';
|
||||
import Confirmation from './confirmation';
|
||||
import messages from './MultiFactorAuth.intl.json';
|
||||
|
||||
import type { Form } from 'components/ui/form';
|
||||
|
||||
const STEPS_TOTAL = 3;
|
||||
|
||||
export type MfaStep = 0 | 1 | 2;
|
||||
type Props = {
|
||||
onChangeStep: Function,
|
||||
confirmationForm: FormModel,
|
||||
onSubmit: (form: FormModel, sendData: () => Promise<*>) => Promise<*>,
|
||||
onComplete: Function,
|
||||
step: MfaStep,
|
||||
onChangeStep: (nextStep: number) => void;
|
||||
confirmationForm: FormModel;
|
||||
onSubmit: (form: FormModel, sendData: () => Promise<void>) => Promise<void>;
|
||||
onComplete: () => void;
|
||||
step: MfaStep;
|
||||
};
|
||||
|
||||
interface State {
|
||||
@ -35,7 +32,7 @@ interface State {
|
||||
qrCodeSrc: string;
|
||||
}
|
||||
|
||||
export default class MfaEnable extends Component<Props, State> {
|
||||
export default class MfaEnable extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
confirmationForm: new FormModel(),
|
||||
step: 0,
|
||||
@ -52,7 +49,7 @@ export default class MfaEnable extends Component<Props, State> {
|
||||
secret: '',
|
||||
};
|
||||
|
||||
confirmationFormEl: ?Form;
|
||||
confirmationFormEl: Form | null;
|
||||
|
||||
componentWillMount() {
|
||||
this.syncState(this.props);
|
||||
@ -153,7 +150,7 @@ export default class MfaEnable extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
onTotpSubmit = (form: FormModel): Promise<*> => {
|
||||
onTotpSubmit = (form: FormModel): Promise<void> => {
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
return this.props
|
@ -1,23 +1,20 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
import { BackButton } from 'components/profile/ProfileForm';
|
||||
import { FormModel } from 'components/ui/form';
|
||||
|
||||
import MfaEnable from './MfaEnable';
|
||||
import MfaEnable, { MfaStep } from './MfaEnable';
|
||||
import MfaDisable from './MfaDisable';
|
||||
import messages from './MultiFactorAuth.intl.json';
|
||||
|
||||
import type { MfaStep } from './MfaEnable';
|
||||
|
||||
class MultiFactorAuth extends Component<{
|
||||
step: MfaStep,
|
||||
isMfaEnabled: boolean,
|
||||
onSubmit: Function,
|
||||
onComplete: Function,
|
||||
onChangeStep: Function,
|
||||
class MultiFactorAuth extends React.Component<{
|
||||
step: MfaStep;
|
||||
isMfaEnabled: boolean;
|
||||
onSubmit: (form: FormModel, sendData: () => Promise<void>) => Promise<void>;
|
||||
onComplete: () => void;
|
||||
onChangeStep: (nextStep: number) => void;
|
||||
}> {
|
||||
render() {
|
||||
const {
|
@ -1,8 +1,5 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { Input, Form, FormModel } from 'components/ui/form';
|
||||
|
||||
import profileForm from 'components/profile/profileForm.scss';
|
||||
@ -14,10 +11,10 @@ export default function Confirmation({
|
||||
onSubmit,
|
||||
onInvalid,
|
||||
}: {
|
||||
form: FormModel,
|
||||
formRef?: (el: ?Form) => void,
|
||||
onSubmit: () => Promise<*>,
|
||||
onInvalid: Function,
|
||||
form: FormModel;
|
||||
formRef?: (el: Form | null) => void;
|
||||
onSubmit: (form: FormModel) => Promise<void>;
|
||||
onInvalid: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Form form={form} onSubmit={onSubmit} onInvalid={onInvalid} ref={formRef}>
|
@ -1,2 +1 @@
|
||||
// @flow
|
||||
export { default } from './Confirmation';
|
@ -1,15 +1,13 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { Button, Input, Form, FormModel } from 'components/ui/form';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
import mfaStyles from '../mfa.scss';
|
||||
|
||||
export default class MfaDisableForm extends Component<{
|
||||
onSubmit: (form: FormModel) => Promise<*>,
|
||||
export default class MfaDisableForm extends React.Component<{
|
||||
onSubmit: (form: FormModel) => Promise<void>;
|
||||
}> {
|
||||
form: FormModel = new FormModel();
|
||||
|
@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
export { default } from './MultiFactorAuth';
|
||||
export type { MfaStep } from './MfaEnable';
|
2
src/components/profile/multiFactorAuth/index.ts
Normal file
2
src/components/profile/multiFactorAuth/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default } from './MultiFactorAuth';
|
||||
export { MfaStep } from './MfaEnable';
|
@ -1,12 +1,9 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import profileForm from 'components/profile/profileForm.scss';
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
import OsInstruction from './OsInstruction';
|
||||
import OsTile from './OsTile';
|
||||
import styles from './instructions.scss';
|
||||
@ -14,15 +11,12 @@ import androidLogo from './images/android.svg';
|
||||
import appleLogo from './images/apple.svg';
|
||||
import windowsLogo from './images/windows.svg';
|
||||
|
||||
export default class Instructions extends Component<
|
||||
{},
|
||||
{
|
||||
activeOs: null | 'android' | 'ios' | 'windows',
|
||||
},
|
||||
> {
|
||||
state: {
|
||||
activeOs: null | 'android' | 'ios' | 'windows',
|
||||
} = {
|
||||
interface State {
|
||||
activeOs: null | 'android' | 'ios' | 'windows';
|
||||
}
|
||||
|
||||
export default class Instructions extends React.Component<{}, State> {
|
||||
state: State = {
|
||||
activeOs: null,
|
||||
};
|
||||
|
||||
@ -79,7 +73,7 @@ export default class Instructions extends Component<
|
||||
);
|
||||
}
|
||||
|
||||
onChangeOs(event: MouseEvent, osName: 'android' | 'ios' | 'windows') {
|
||||
onChangeOs(event: React.MouseEvent, osName: 'android' | 'ios' | 'windows') {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
@ -9,10 +7,10 @@ import styles from './instructions.scss';
|
||||
type OS = 'android' | 'ios' | 'windows';
|
||||
|
||||
const linksByOs: {
|
||||
[key: OS]: {
|
||||
searchLink: string,
|
||||
featured: Array<{ link: string, label: string }>,
|
||||
},
|
||||
[K in OS]: {
|
||||
searchLink: string;
|
||||
featured: Array<{ link: string; label: string }>;
|
||||
};
|
||||
} = {
|
||||
android: {
|
||||
searchLink: 'https://play.google.com/store/search?q=totp%20authenticator',
|
@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './instructions.scss';
|
||||
@ -11,10 +9,10 @@ export default function OsInstruction({
|
||||
label,
|
||||
onClick,
|
||||
}: {
|
||||
className: string,
|
||||
logo: string,
|
||||
label: string,
|
||||
onClick: (event: MouseEvent) => void,
|
||||
className: string;
|
||||
logo: string;
|
||||
label: string;
|
||||
onClick: (event: React.MouseEvent<any>) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className={classNames(styles.osTile, className)} onClick={onClick}>
|
@ -1,2 +1 @@
|
||||
// @flow
|
||||
export { default } from './Instructions';
|
@ -1,22 +1,18 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { ImageLoader } from 'components/ui/loader';
|
||||
import profileForm from 'components/profile/profileForm.scss';
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
import styles from './key-form.scss';
|
||||
|
||||
export default function KeyForm({
|
||||
secret,
|
||||
qrCodeSrc,
|
||||
}: {
|
||||
secret: string,
|
||||
qrCodeSrc: string,
|
||||
secret: string;
|
||||
qrCodeSrc: string;
|
||||
}) {
|
||||
const formattedSecret = formatSecret(secret || new Array(24).join('X'));
|
||||
|
@ -1,2 +1 @@
|
||||
// @flow
|
||||
export { default } from './KeyForm';
|
@ -1,15 +1,13 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
import { ScrollIntoView } from 'components/ui/scroll';
|
||||
import styles from 'components/profile/profileForm.scss';
|
||||
import icons from 'components/ui/icons.scss';
|
||||
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
import mfaStyles from '../mfa.scss';
|
||||
|
||||
export default function MfaStatus({ onProceed }: { onProceed: Function }) {
|
||||
export default function MfaStatus({ onProceed }: { onProceed: () => void }) {
|
||||
return (
|
||||
<div className={styles.formBody}>
|
||||
<ScrollIntoView />
|
@ -1,34 +1,32 @@
|
||||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { omit } from 'functions';
|
||||
|
||||
import styles from './panel.scss';
|
||||
import icons from './icons.scss';
|
||||
|
||||
export function Panel(props: {
|
||||
title?: string,
|
||||
icon?: string,
|
||||
children: Node,
|
||||
title?: string;
|
||||
icon?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
let { title, icon } = props;
|
||||
const { title: titleText, icon: iconType } = props;
|
||||
let icon: React.ReactElement | undefined;
|
||||
let title: React.ReactElement | undefined;
|
||||
|
||||
if (icon) {
|
||||
if (iconType) {
|
||||
icon = (
|
||||
<button className={styles.headerControl}>
|
||||
<span className={icons[icon]} />
|
||||
<span className={icons[iconType]} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (title) {
|
||||
if (titleText) {
|
||||
title = (
|
||||
<PanelHeader>
|
||||
{icon}
|
||||
{title}
|
||||
{titleText}
|
||||
</PanelHeader>
|
||||
);
|
||||
}
|
||||
@ -42,7 +40,7 @@ export function Panel(props: {
|
||||
);
|
||||
}
|
||||
|
||||
export function PanelHeader(props: { children: * }) {
|
||||
export function PanelHeader(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className={styles.header} {...props}>
|
||||
{props.children}
|
||||
@ -50,7 +48,7 @@ export function PanelHeader(props: { children: * }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function PanelBody(props: { children: * }) {
|
||||
export function PanelBody(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className={styles.body} {...props}>
|
||||
{props.children}
|
||||
@ -58,7 +56,7 @@ export function PanelBody(props: { children: * }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function PanelFooter(props: { children: * }) {
|
||||
export function PanelFooter(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className={styles.footer} {...props}>
|
||||
{props.children}
|
||||
@ -66,18 +64,18 @@ export function PanelFooter(props: { children: * }) {
|
||||
);
|
||||
}
|
||||
|
||||
export class PanelBodyHeader extends Component<
|
||||
export class PanelBodyHeader extends React.Component<
|
||||
{
|
||||
type: 'default' | 'error',
|
||||
onClose: Function,
|
||||
children: *,
|
||||
type?: 'default' | 'error';
|
||||
onClose?: () => void;
|
||||
children: React.ReactNode;
|
||||
},
|
||||
{
|
||||
isClosed: boolean,
|
||||
},
|
||||
isClosed: boolean;
|
||||
}
|
||||
> {
|
||||
state: {
|
||||
isClosed: boolean,
|
||||
isClosed: boolean;
|
||||
} = {
|
||||
isClosed: false,
|
||||
};
|
||||
@ -105,12 +103,16 @@ export class PanelBodyHeader extends Component<
|
||||
);
|
||||
}
|
||||
|
||||
onClose = (event: MouseEvent) => {
|
||||
onClose = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { onClose } = this.props;
|
||||
|
||||
this.setState({ isClosed: true });
|
||||
|
||||
this.props.onClose();
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { FormattedRelativeTime } from 'react-intl';
|
||||
import { selectUnit } from '@formatjs/intl-utils';
|
||||
|
||||
function RelativeTime({ timestamp }: { timestamp: number }) {
|
||||
const { unit, value }: { unit: any, value: number } = selectUnit(timestamp);
|
||||
const { unit, value }: { unit: any; value: number } = selectUnit(timestamp);
|
||||
|
||||
return (
|
||||
<FormattedRelativeTime
|
@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { IntlProvider } from 'components/i18n';
|
||||
@ -9,17 +8,14 @@ import styles from './styles.scss';
|
||||
import BoxesField from './BoxesField';
|
||||
import messages from './BSoD.intl.json';
|
||||
|
||||
interface State {
|
||||
lastEventId?: string | void;
|
||||
}
|
||||
|
||||
// TODO: probably it is better to render this view from the App view
|
||||
// to remove dependencies from store and IntlProvider
|
||||
export default class BSoD extends React.Component<
|
||||
{
|
||||
store: Object,
|
||||
},
|
||||
{
|
||||
lastEventId?: string,
|
||||
},
|
||||
> {
|
||||
state = {};
|
||||
class BSoD extends React.Component<{}, State> {
|
||||
state: State = {};
|
||||
|
||||
componentDidMount() {
|
||||
// poll for event id
|
||||
@ -37,7 +33,6 @@ export default class BSoD extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const { store } = this.props;
|
||||
const { lastEventId } = this.state;
|
||||
|
||||
let emailUrl = 'mailto:support@ely.by';
|
||||
@ -47,11 +42,11 @@ export default class BSoD extends React.Component<
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider store={store}>
|
||||
<IntlProvider>
|
||||
<div className={styles.body}>
|
||||
<canvas
|
||||
className={styles.canvas}
|
||||
ref={(el: ?HTMLCanvasElement) => el && new BoxesField(el)}
|
||||
ref={(el: HTMLCanvasElement | null) => el && new BoxesField(el)}
|
||||
/>
|
||||
|
||||
<div className={styles.wrapper}>
|
||||
@ -76,3 +71,5 @@ export default class BSoD extends React.Component<
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default BSoD;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user