diff options
Diffstat (limited to 'src')
20 files changed, 450 insertions, 561 deletions
diff --git a/src/@types/mobx-form.types.ts b/src/@types/mobx-form.types.ts index c9932c053..2a984d3a6 100644 --- a/src/@types/mobx-form.types.ts +++ b/src/@types/mobx-form.types.ts | |||
@@ -9,29 +9,22 @@ export interface FormFieldOptions { | |||
9 | 9 | ||
10 | export interface FormFields { | 10 | export interface FormFields { |
11 | fields: { | 11 | fields: { |
12 | [key: string]: { | 12 | [key: string]: Field; |
13 | label?: string; | ||
14 | placeholder?: string; | ||
15 | options?: FormFieldOptions[]; | ||
16 | value?: string | boolean | number | null; | ||
17 | default?: string | boolean | number | null; | ||
18 | type?: string; // todo specifiy probably | ||
19 | disabled?: boolean; | ||
20 | validators?: any; // Not sure yet. | ||
21 | }; | ||
22 | }; | 13 | }; |
23 | } | 14 | } |
24 | 15 | ||
25 | export interface Field extends Partial<Listeners> { | 16 | export interface Field extends Listeners { |
26 | id?: string; | 17 | id?: string; |
27 | type?: string; | 18 | type?: string; // todo specifiy probably |
28 | name?: string; | 19 | name?: string; |
29 | value: string | string[]; | 20 | value?: any; |
30 | label?: string; | 21 | label?: string; |
31 | placeholder?: string; | 22 | placeholder?: string; |
32 | disabled?: boolean; | 23 | disabled?: boolean; |
33 | error?: GlobalError | string; | 24 | error?: GlobalError | string; |
34 | options?: SelectOptions[]; | 25 | options?: SelectOptions[]; |
26 | default?: string | boolean | number | null; | ||
27 | validators?: any; // Not sure yet. | ||
35 | } | 28 | } |
36 | 29 | ||
37 | export interface SelectOptions { | 30 | export interface SelectOptions { |
diff --git a/src/components/auth/ChangeServer.tsx b/src/components/auth/ChangeServer.tsx index d8509f599..c49e52673 100644 --- a/src/components/auth/ChangeServer.tsx +++ b/src/components/auth/ChangeServer.tsx | |||
@@ -4,7 +4,7 @@ import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | |||
4 | import { mdiArrowLeftCircle } from '@mdi/js'; | 4 | import { mdiArrowLeftCircle } from '@mdi/js'; |
5 | import { noop } from 'lodash'; | 5 | import { noop } from 'lodash'; |
6 | import Form from '../../lib/Form'; | 6 | import Form from '../../lib/Form'; |
7 | import Input from '../ui/Input'; | 7 | import Input from '../ui/input/index'; |
8 | import Select from '../ui/Select'; | 8 | import Select from '../ui/Select'; |
9 | import Button from '../ui/button'; | 9 | import Button from '../ui/button'; |
10 | import Link from '../ui/Link'; | 10 | import Link from '../ui/Link'; |
@@ -128,7 +128,10 @@ class ChangeServer extends Component<IProps> { | |||
128 | )} | 128 | )} |
129 | <Select field={form.$('server')} /> | 129 | <Select field={form.$('server')} /> |
130 | {!this.defaultServers.includes(form.$('server').value) && ( | 130 | {!this.defaultServers.includes(form.$('server').value) && ( |
131 | <Input placeholder="Custom Server" field={form.$('customServer')} /> | 131 | <Input |
132 | placeholder="Custom Server" | ||
133 | {...form.$('customServer').bind()} | ||
134 | /> | ||
132 | )} | 135 | )} |
133 | <Button | 136 | <Button |
134 | type="submit" | 137 | type="submit" |
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.tsx index 6b0e0e40b..7723ea1ac 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.tsx | |||
@@ -1,15 +1,14 @@ | |||
1 | import { Component, Fragment } from 'react'; | 1 | import { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
5 | import { Link } from 'react-router-dom'; | 4 | import { Link } from 'react-router-dom'; |
6 | import classnames from 'classnames'; | 5 | import classnames from 'classnames'; |
7 | 6 | import { noop } from 'lodash'; | |
8 | import Infobox from '../ui/Infobox'; | 7 | import Infobox from '../ui/Infobox'; |
9 | import Appear from '../ui/effects/Appear'; | 8 | import Appear from '../ui/effects/Appear'; |
10 | import Form from '../../lib/Form'; | 9 | import Form from '../../lib/Form'; |
11 | import { email } from '../../helpers/validation-helpers'; | 10 | import { email, required } from '../../helpers/validation-helpers'; |
12 | import Input from '../ui/Input'; | 11 | import Input from '../ui/input/index'; |
13 | import Button from '../ui/button'; | 12 | import Button from '../ui/button'; |
14 | import { H1 } from '../ui/headline'; | 13 | import { H1 } from '../ui/headline'; |
15 | 14 | ||
@@ -44,55 +43,61 @@ const messages = defineMessages({ | |||
44 | }, | 43 | }, |
45 | }); | 44 | }); |
46 | 45 | ||
47 | class Invite extends Component { | 46 | interface IProps extends WrappedComponentProps { |
48 | static propTypes = { | 47 | onSubmit: (...args: any[]) => void; |
49 | onSubmit: PropTypes.func.isRequired, | 48 | embed?: boolean; |
50 | embed: PropTypes.bool, | 49 | isInviteSuccessful?: boolean; |
51 | isInviteSuccessful: PropTypes.bool, | 50 | isLoadingInvite?: boolean; |
52 | isLoadingInvite: PropTypes.bool, | 51 | } |
53 | }; | ||
54 | |||
55 | static defaultProps = { | ||
56 | embed: false, | ||
57 | isInviteSuccessful: false, | ||
58 | isLoadingInvite: false, | ||
59 | }; | ||
60 | 52 | ||
61 | state = { showSuccessInfo: false }; | 53 | interface IState { |
54 | showSuccessInfo: boolean; | ||
55 | } | ||
62 | 56 | ||
63 | componentDidMount() { | 57 | @observer |
64 | const { intl } = this.props; | 58 | class Invite extends Component<IProps, IState> { |
65 | this.form = new Form( | 59 | form: Form; |
66 | { | 60 | |
67 | fields: { | 61 | constructor(props: IProps) { |
68 | invite: [ | 62 | super(props); |
69 | ...Array.from({ length: 3 }).fill({ | 63 | |
70 | fields: { | 64 | this.state = { showSuccessInfo: false }; |
71 | name: { | 65 | this.form = new Form({ |
72 | label: intl.formatMessage(messages.nameLabel), | 66 | fields: { |
73 | placeholder: intl.formatMessage(messages.nameLabel), | 67 | invite: [ |
74 | onChange: () => { | 68 | ...Array.from({ length: 3 }).fill({ |
75 | this.setState({ showSuccessInfo: false }); | 69 | fields: { |
76 | }, | 70 | name: { |
77 | // related: ['invite.0.email'], // path accepted but does not work | 71 | label: this.props.intl.formatMessage(messages.nameLabel), |
72 | placeholder: this.props.intl.formatMessage(messages.nameLabel), | ||
73 | onChange: () => { | ||
74 | this.setState({ showSuccessInfo: false }); | ||
78 | }, | 75 | }, |
79 | email: { | 76 | validators: [required], |
80 | label: intl.formatMessage(messages.emailLabel), | 77 | // related: ['invite.0.email'], // path accepted but does not work |
81 | placeholder: intl.formatMessage(messages.emailLabel), | 78 | }, |
82 | onChange: () => { | 79 | email: { |
83 | this.setState({ showSuccessInfo: false }); | 80 | label: this.props.intl.formatMessage(messages.emailLabel), |
84 | }, | 81 | placeholder: this.props.intl.formatMessage(messages.emailLabel), |
85 | validators: [email], | 82 | onChange: () => { |
83 | this.setState({ showSuccessInfo: false }); | ||
86 | }, | 84 | }, |
85 | validators: [email], | ||
87 | }, | 86 | }, |
88 | }), | 87 | }, |
89 | ], | 88 | }), |
90 | }, | 89 | // TODO - [TS DEBT] need to fix this type once mobx-react-form is updated to next version |
90 | ] as any, | ||
91 | }, | 91 | }, |
92 | intl, | 92 | }); |
93 | ); | 93 | } |
94 | 94 | ||
95 | document.querySelector('input:first-child')?.focus(); | 95 | componentDidMount() { |
96 | const selector: HTMLElement | null = | ||
97 | document.querySelector('input:first-child'); | ||
98 | if (selector) { | ||
99 | selector.focus(); | ||
100 | } | ||
96 | } | 101 | } |
97 | 102 | ||
98 | submit(e) { | 103 | submit(e) { |
@@ -101,10 +106,15 @@ class Invite extends Component { | |||
101 | this.form?.submit({ | 106 | this.form?.submit({ |
102 | onSuccess: form => { | 107 | onSuccess: form => { |
103 | this.props.onSubmit({ invites: form.values().invite }); | 108 | this.props.onSubmit({ invites: form.values().invite }); |
104 | |||
105 | this.form?.clear(); | 109 | this.form?.clear(); |
106 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( | 110 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( |
107 | document.querySelector('input:first-child')?.focus(); | 111 | |
112 | const selector: HTMLElement | null = | ||
113 | document.querySelector('input:first-child'); | ||
114 | if (selector) { | ||
115 | selector.focus(); | ||
116 | } | ||
117 | |||
108 | this.setState({ showSuccessInfo: true }); | 118 | this.setState({ showSuccessInfo: true }); |
109 | }, | 119 | }, |
110 | onError: () => {}, | 120 | onError: () => {}, |
@@ -114,7 +124,11 @@ class Invite extends Component { | |||
114 | render() { | 124 | render() { |
115 | const { form } = this; | 125 | const { form } = this; |
116 | const { intl } = this.props; | 126 | const { intl } = this.props; |
117 | const { embed, isInviteSuccessful, isLoadingInvite } = this.props; | 127 | const { |
128 | embed = false, | ||
129 | isInviteSuccessful = false, | ||
130 | isLoadingInvite = false, | ||
131 | } = this.props; | ||
118 | 132 | ||
119 | const atLeastOneEmailAddress = form | 133 | const atLeastOneEmailAddress = form |
120 | .$('invite') | 134 | .$('invite') |
@@ -133,7 +147,7 @@ class Invite extends Component { | |||
133 | <Infobox | 147 | <Infobox |
134 | type="success" | 148 | type="success" |
135 | icon="checkbox-marked-circle-outline" | 149 | icon="checkbox-marked-circle-outline" |
136 | dismissable | 150 | dismissible |
137 | > | 151 | > |
138 | {intl.formatMessage(messages.inviteSuccessInfo)} | 152 | {intl.formatMessage(messages.inviteSuccessInfo)} |
139 | </Infobox> | 153 | </Infobox> |
@@ -144,14 +158,14 @@ class Invite extends Component { | |||
144 | {!embed && ( | 158 | {!embed && ( |
145 | <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> | 159 | <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> |
146 | )} | 160 | )} |
147 | <H1 className={embed && 'invite__embed'}> | 161 | <H1 className={embed ? 'invite__embed' : ''}> |
148 | {intl.formatMessage(messages.headline)} | 162 | {intl.formatMessage(messages.headline)} |
149 | </H1> | 163 | </H1> |
150 | {form.$('invite').map(invite => ( | 164 | {form.$('invite').map(invite => ( |
151 | <div className="grid" key={invite.key}> | 165 | <div className="grid" key={invite.key}> |
152 | <div className="grid__row"> | 166 | <div className="grid__row"> |
153 | <Input field={invite.$('name')} showLabel={false} /> | 167 | <Input {...invite.$('name').bind()} showLabel={false} /> |
154 | <Input field={invite.$('email')} showLabel={false} /> | 168 | <Input {...invite.$('email').bind()} showLabel={false} /> |
155 | </div> | 169 | </div> |
156 | </div> | 170 | </div> |
157 | ))} | 171 | ))} |
@@ -161,6 +175,7 @@ class Invite extends Component { | |||
161 | disabled={!atLeastOneEmailAddress} | 175 | disabled={!atLeastOneEmailAddress} |
162 | label={intl.formatMessage(messages.submitButtonLabel)} | 176 | label={intl.formatMessage(messages.submitButtonLabel)} |
163 | loaded={!isLoadingInvite} | 177 | loaded={!isLoadingInvite} |
178 | onClick={noop} | ||
164 | /> | 179 | /> |
165 | {!embed && ( | 180 | {!embed && ( |
166 | <Link | 181 | <Link |
@@ -195,4 +210,4 @@ class Invite extends Component { | |||
195 | } | 210 | } |
196 | } | 211 | } |
197 | 212 | ||
198 | export default injectIntl(observer(Invite)); | 213 | export default injectIntl(Invite); |
diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.tsx index 6e32dd980..8b4e26878 100644 --- a/src/components/auth/Locked.js +++ b/src/components/auth/Locked.tsx | |||
@@ -1,17 +1,14 @@ | |||
1 | import { systemPreferences } from '@electron/remote'; | 1 | import { systemPreferences } from '@electron/remote'; |
2 | import { Component } from 'react'; | 2 | import { Component } from 'react'; |
3 | import PropTypes from 'prop-types'; | ||
4 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
5 | import { defineMessages, injectIntl } from 'react-intl'; | 4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
6 | 5 | import { noop } from 'lodash'; | |
7 | import Form from '../../lib/Form'; | 6 | import Form from '../../lib/Form'; |
8 | import Input from '../ui/Input'; | 7 | import Input from '../ui/input/index'; |
9 | import Button from '../ui/button'; | 8 | import Button from '../ui/button'; |
10 | import { H1 } from '../ui/headline'; | 9 | import { H1 } from '../ui/headline'; |
11 | import { isMac } from '../../environment'; | 10 | import { isMac } from '../../environment'; |
12 | 11 | ||
13 | import { globalError as globalErrorPropType } from '../../prop-types'; | ||
14 | |||
15 | const messages = defineMessages({ | 12 | const messages = defineMessages({ |
16 | headline: { | 13 | headline: { |
17 | id: 'locked.headline', | 14 | id: 'locked.headline', |
@@ -23,7 +20,7 @@ const messages = defineMessages({ | |||
23 | }, | 20 | }, |
24 | touchIdPrompt: { | 21 | touchIdPrompt: { |
25 | id: 'locked.touchIdPrompt', | 22 | id: 'locked.touchIdPrompt', |
26 | defaultMessage: 'unlock via Touch ID', | 23 | defaultMessage: 'Unlock with Touch ID', |
27 | }, | 24 | }, |
28 | passwordLabel: { | 25 | passwordLabel: { |
29 | id: 'locked.password.label', | 26 | id: 'locked.password.label', |
@@ -43,30 +40,31 @@ const messages = defineMessages({ | |||
43 | }, | 40 | }, |
44 | }); | 41 | }); |
45 | 42 | ||
46 | class Locked extends Component { | 43 | interface IProps extends WrappedComponentProps { |
47 | static propTypes = { | 44 | onSubmit: (...args: any[]) => void; |
48 | onSubmit: PropTypes.func.isRequired, | 45 | unlock: () => void; |
49 | unlock: PropTypes.func.isRequired, | 46 | isSubmitting: boolean; |
50 | isSubmitting: PropTypes.bool.isRequired, | 47 | useTouchIdToUnlock: boolean; |
51 | useTouchIdToUnlock: PropTypes.bool.isRequired, | 48 | error: boolean; |
52 | error: globalErrorPropType.isRequired, | 49 | } |
53 | }; | ||
54 | 50 | ||
55 | form = (() => { | 51 | @observer |
56 | const { intl } = this.props; | 52 | class Locked extends Component<IProps> { |
57 | return new Form( | 53 | form: Form; |
58 | { | 54 | |
59 | fields: { | 55 | constructor(props: IProps) { |
60 | password: { | 56 | super(props); |
61 | label: intl.formatMessage(messages.passwordLabel), | 57 | |
62 | value: '', | 58 | this.form = new Form({ |
63 | type: 'password', | 59 | fields: { |
64 | }, | 60 | password: { |
61 | label: this.props.intl.formatMessage(messages.passwordLabel), | ||
62 | value: '', | ||
63 | type: 'password', | ||
65 | }, | 64 | }, |
66 | }, | 65 | }, |
67 | intl, | 66 | }); |
68 | ); | 67 | } |
69 | })(); | ||
70 | 68 | ||
71 | submit(e) { | 69 | submit(e) { |
72 | e.preventDefault(); | 70 | e.preventDefault(); |
@@ -90,8 +88,7 @@ class Locked extends Component { | |||
90 | 88 | ||
91 | render() { | 89 | render() { |
92 | const { form } = this; | 90 | const { form } = this; |
93 | const { intl } = this.props; | 91 | const { isSubmitting, error, useTouchIdToUnlock, intl } = this.props; |
94 | const { isSubmitting, error, useTouchIdToUnlock } = this.props; | ||
95 | 92 | ||
96 | const touchIdEnabled = isMac | 93 | const touchIdEnabled = isMac |
97 | ? useTouchIdToUnlock && systemPreferences.canPromptTouchID() | 94 | ? useTouchIdToUnlock && systemPreferences.canPromptTouchID() |
@@ -118,8 +115,8 @@ class Locked extends Component { | |||
118 | </> | 115 | </> |
119 | )} | 116 | )} |
120 | 117 | ||
121 | <Input field={form.$('password')} showPasswordToggle focus /> | 118 | <Input {...form.$('password').bind()} showPasswordToggle focus /> |
122 | {error.code === 'invalid-credentials' && ( | 119 | {error && ( |
123 | <p className="error-message center"> | 120 | <p className="error-message center"> |
124 | {intl.formatMessage(messages.invalidCredentials)} | 121 | {intl.formatMessage(messages.invalidCredentials)} |
125 | </p> | 122 | </p> |
@@ -130,6 +127,7 @@ class Locked extends Component { | |||
130 | buttonType="secondary" | 127 | buttonType="secondary" |
131 | label={`${submitButtonLabel} ...`} | 128 | label={`${submitButtonLabel} ...`} |
132 | loaded={false} | 129 | loaded={false} |
130 | onClick={noop} | ||
133 | disabled | 131 | disabled |
134 | /> | 132 | /> |
135 | ) : ( | 133 | ) : ( |
@@ -137,6 +135,7 @@ class Locked extends Component { | |||
137 | type="submit" | 135 | type="submit" |
138 | className="auth__button" | 136 | className="auth__button" |
139 | label={submitButtonLabel} | 137 | label={submitButtonLabel} |
138 | onClick={noop} | ||
140 | /> | 139 | /> |
141 | )} | 140 | )} |
142 | </form> | 141 | </form> |
@@ -145,4 +144,4 @@ class Locked extends Component { | |||
145 | } | 144 | } |
146 | } | 145 | } |
147 | 146 | ||
148 | export default injectIntl(observer(Locked)); | 147 | export default injectIntl(Locked); |
diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx index 66a567fe4..185a6ad48 100644 --- a/src/components/auth/Login.tsx +++ b/src/components/auth/Login.tsx | |||
@@ -9,7 +9,7 @@ import { API_VERSION } from '../../environment-remote'; | |||
9 | import { serverBase } from '../../api/apiBase'; // TODO: Remove this line after fixing password recovery in-app | 9 | import { serverBase } from '../../api/apiBase'; // TODO: Remove this line after fixing password recovery in-app |
10 | import Form from '../../lib/Form'; | 10 | import Form from '../../lib/Form'; |
11 | import { required, email } from '../../helpers/validation-helpers'; | 11 | import { required, email } from '../../helpers/validation-helpers'; |
12 | import Input from '../ui/Input'; | 12 | import Input from '../ui/input/index'; |
13 | import Button from '../ui/button'; | 13 | import Button from '../ui/button'; |
14 | import Link from '../ui/Link'; | 14 | import Link from '../ui/Link'; |
15 | import { H1 } from '../ui/headline'; | 15 | import { H1 } from '../ui/headline'; |
@@ -136,8 +136,8 @@ class Login extends Component<IProps> { | |||
136 | {intl.formatMessage(messages.serverLogout)} | 136 | {intl.formatMessage(messages.serverLogout)} |
137 | </p> | 137 | </p> |
138 | )} | 138 | )} |
139 | <Input field={form.$('email')} focus /> | 139 | <Input {...form.$('email').bind()} focus /> |
140 | <Input field={form.$('password')} showPasswordToggle /> | 140 | <Input {...form.$('password').bind()} showPasswordToggle /> |
141 | {error.code === 'invalid-credentials' && ( | 141 | {error.code === 'invalid-credentials' && ( |
142 | <> | 142 | <> |
143 | <p className="error-message center"> | 143 | <p className="error-message center"> |
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.tsx index 5086b0bbd..53fdbf842 100644 --- a/src/components/auth/Password.js +++ b/src/components/auth/Password.tsx | |||
@@ -1,11 +1,11 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, FormEvent } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import { observer } from 'mobx-react'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | ||
5 | 4 | ||
5 | import { noop } from 'lodash'; | ||
6 | import Form from '../../lib/Form'; | 6 | import Form from '../../lib/Form'; |
7 | import { required, email } from '../../helpers/validation-helpers'; | 7 | import { required, email } from '../../helpers/validation-helpers'; |
8 | import Input from '../ui/Input'; | 8 | import Input from '../ui/input/index'; |
9 | import Button from '../ui/button'; | 9 | import Button from '../ui/button'; |
10 | import Link from '../ui/Link'; | 10 | import Link from '../ui/Link'; |
11 | import Infobox from '../ui/Infobox'; | 11 | import Infobox from '../ui/Infobox'; |
@@ -39,32 +39,33 @@ const messages = defineMessages({ | |||
39 | }, | 39 | }, |
40 | }); | 40 | }); |
41 | 41 | ||
42 | class Password extends Component { | 42 | interface IProps extends WrappedComponentProps { |
43 | static propTypes = { | 43 | onSubmit: (...args: any[]) => void; |
44 | onSubmit: PropTypes.func.isRequired, | 44 | isSubmitting: boolean; |
45 | isSubmitting: PropTypes.bool.isRequired, | 45 | signupRoute: string; |
46 | signupRoute: PropTypes.string.isRequired, | 46 | loginRoute: string; |
47 | loginRoute: PropTypes.string.isRequired, | 47 | status: string[]; |
48 | status: MobxPropTypes.arrayOrObservableArray.isRequired, | 48 | } |
49 | }; | 49 | |
50 | @observer | ||
51 | class Password extends Component<IProps> { | ||
52 | form: Form; | ||
53 | |||
54 | constructor(props: IProps) { | ||
55 | super(props); | ||
50 | 56 | ||
51 | form = (() => { | 57 | this.form = new Form({ |
52 | const { intl } = this.props; | 58 | fields: { |
53 | return new Form( | 59 | email: { |
54 | { | 60 | label: this.props.intl.formatMessage(messages.emailLabel), |
55 | fields: { | 61 | value: '', |
56 | email: { | 62 | validators: [required, email], |
57 | label: intl.formatMessage(messages.emailLabel), | ||
58 | value: '', | ||
59 | validators: [required, email], | ||
60 | }, | ||
61 | }, | 63 | }, |
62 | }, | 64 | }, |
63 | intl, | 65 | }); |
64 | ); | 66 | } |
65 | })(); | ||
66 | 67 | ||
67 | submit(e) { | 68 | submit(e: FormEvent<HTMLFormElement>): void { |
68 | e.preventDefault(); | 69 | e.preventDefault(); |
69 | this.form.submit({ | 70 | this.form.submit({ |
70 | onSuccess: form => { | 71 | onSuccess: form => { |
@@ -76,8 +77,7 @@ class Password extends Component { | |||
76 | 77 | ||
77 | render() { | 78 | render() { |
78 | const { form } = this; | 79 | const { form } = this; |
79 | const { intl } = this.props; | 80 | const { isSubmitting, signupRoute, loginRoute, status, intl } = this.props; |
80 | const { isSubmitting, signupRoute, loginRoute, status } = this.props; | ||
81 | 81 | ||
82 | return ( | 82 | return ( |
83 | <div className="auth__container"> | 83 | <div className="auth__container"> |
@@ -91,7 +91,7 @@ class Password extends Component { | |||
91 | {intl.formatMessage(messages.successInfo)} | 91 | {intl.formatMessage(messages.successInfo)} |
92 | </Infobox> | 92 | </Infobox> |
93 | )} | 93 | )} |
94 | <Input field={form.$('email')} focus /> | 94 | <Input {...form.$('email').bind()} focus /> |
95 | {status.length > 0 && status.includes('no-user') && ( | 95 | {status.length > 0 && status.includes('no-user') && ( |
96 | <p className="error-message center"> | 96 | <p className="error-message center"> |
97 | {intl.formatMessage(messages.noUser)} | 97 | {intl.formatMessage(messages.noUser)} |
@@ -103,6 +103,7 @@ class Password extends Component { | |||
103 | buttonType="secondary" | 103 | buttonType="secondary" |
104 | label={`${intl.formatMessage(globalMessages.submit)} ...`} | 104 | label={`${intl.formatMessage(globalMessages.submit)} ...`} |
105 | loaded={false} | 105 | loaded={false} |
106 | onClick={noop} | ||
106 | disabled | 107 | disabled |
107 | /> | 108 | /> |
108 | ) : ( | 109 | ) : ( |
@@ -110,6 +111,7 @@ class Password extends Component { | |||
110 | type="submit" | 111 | type="submit" |
111 | className="auth__button" | 112 | className="auth__button" |
112 | label={intl.formatMessage(globalMessages.submit)} | 113 | label={intl.formatMessage(globalMessages.submit)} |
114 | onClick={noop} | ||
113 | /> | 115 | /> |
114 | )} | 116 | )} |
115 | </form> | 117 | </form> |
@@ -124,4 +126,4 @@ class Password extends Component { | |||
124 | } | 126 | } |
125 | } | 127 | } |
126 | 128 | ||
127 | export default injectIntl(observer(Password)); | 129 | export default injectIntl(Password); |
diff --git a/src/components/auth/Signup.jsx b/src/components/auth/Signup.tsx index e0337656c..af14430aa 100644 --- a/src/components/auth/Signup.jsx +++ b/src/components/auth/Signup.tsx | |||
@@ -1,20 +1,18 @@ | |||
1 | /* eslint jsx-a11y/anchor-is-valid: 0 */ | ||
2 | import { Component } from 'react'; | 1 | import { Component } from 'react'; |
3 | import PropTypes from 'prop-types'; | ||
4 | import { observer, inject } from 'mobx-react'; | 2 | import { observer, inject } from 'mobx-react'; |
5 | import { defineMessages, injectIntl } from 'react-intl'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
6 | |||
7 | import { mdiArrowLeftCircle } from '@mdi/js'; | 4 | import { mdiArrowLeftCircle } from '@mdi/js'; |
5 | import { noop } from 'lodash'; | ||
8 | import Form from '../../lib/Form'; | 6 | import Form from '../../lib/Form'; |
9 | import { required, email, minLength } from '../../helpers/validation-helpers'; | 7 | import { required, email, minLength } from '../../helpers/validation-helpers'; |
10 | import Input from '../ui/Input'; | 8 | import Input from '../ui/input/index'; |
11 | import Button from '../ui/button'; | 9 | import Button from '../ui/button'; |
12 | import Link from '../ui/Link'; | 10 | import Link from '../ui/Link'; |
13 | import Icon from '../ui/icon'; | 11 | import Icon from '../ui/icon'; |
14 | |||
15 | import { globalError as globalErrorPropType } from '../../prop-types'; | ||
16 | import { serverBase } from '../../api/apiBase'; | 12 | import { serverBase } from '../../api/apiBase'; |
17 | import { H1 } from '../ui/headline'; | 13 | import { H1 } from '../ui/headline'; |
14 | import { GlobalError } from '../../@types/ferdium-components.types'; | ||
15 | import { Actions } from '../../actions/lib/actions'; | ||
18 | 16 | ||
19 | const messages = defineMessages({ | 17 | const messages = defineMessages({ |
20 | headline: { | 18 | headline: { |
@@ -33,10 +31,10 @@ const messages = defineMessages({ | |||
33 | id: 'signup.email.label', | 31 | id: 'signup.email.label', |
34 | defaultMessage: 'Email address', | 32 | defaultMessage: 'Email address', |
35 | }, | 33 | }, |
36 | // companyLabel: { | 34 | companyLabel: { |
37 | // id: 'signup.company.label', | 35 | id: 'signup.company.label', |
38 | // defaultMessage: 'Company', | 36 | defaultMessage: 'Company', |
39 | // }, | 37 | }, |
40 | passwordLabel: { | 38 | passwordLabel: { |
41 | id: 'signup.password.label', | 39 | id: 'signup.password.label', |
42 | defaultMessage: 'Password', | 40 | defaultMessage: 'Password', |
@@ -67,45 +65,48 @@ const messages = defineMessages({ | |||
67 | }, | 65 | }, |
68 | }); | 66 | }); |
69 | 67 | ||
70 | class Signup extends Component { | 68 | interface IProps extends WrappedComponentProps { |
71 | static propTypes = { | 69 | onSubmit: (...args: any[]) => void; |
72 | onSubmit: PropTypes.func.isRequired, | 70 | isSubmitting: boolean; |
73 | isSubmitting: PropTypes.bool.isRequired, | 71 | loginRoute: string; |
74 | loginRoute: PropTypes.string.isRequired, | 72 | error: GlobalError; |
75 | error: globalErrorPropType.isRequired, | 73 | actions?: Actions; |
76 | }; | 74 | } |
77 | 75 | ||
78 | form = (() => { | 76 | @inject('actions') |
79 | const { intl } = this.props; | 77 | @observer |
80 | return new Form( | 78 | class Signup extends Component<IProps> { |
81 | { | 79 | form: Form; |
82 | fields: { | 80 | |
83 | firstname: { | 81 | constructor(props: IProps) { |
84 | label: intl.formatMessage(messages.firstnameLabel), | 82 | super(props); |
85 | value: '', | 83 | |
86 | validators: [required], | 84 | this.form = new Form({ |
87 | }, | 85 | fields: { |
88 | lastname: { | 86 | firstname: { |
89 | label: intl.formatMessage(messages.lastnameLabel), | 87 | label: this.props.intl.formatMessage(messages.firstnameLabel), |
90 | value: '', | 88 | value: '', |
91 | validators: [required], | 89 | validators: [required], |
92 | }, | 90 | }, |
93 | email: { | 91 | lastname: { |
94 | label: intl.formatMessage(messages.emailLabel), | 92 | label: this.props.intl.formatMessage(messages.lastnameLabel), |
95 | value: '', | 93 | value: '', |
96 | validators: [required, email], | 94 | validators: [required], |
97 | }, | 95 | }, |
98 | password: { | 96 | email: { |
99 | label: intl.formatMessage(messages.passwordLabel), | 97 | label: this.props.intl.formatMessage(messages.emailLabel), |
100 | value: '', | 98 | value: '', |
101 | validators: [required, minLength(6)], | 99 | validators: [required, email], |
102 | type: 'password', | 100 | }, |
103 | }, | 101 | password: { |
102 | label: this.props.intl.formatMessage(messages.passwordLabel), | ||
103 | value: '', | ||
104 | validators: [required, minLength(6)], | ||
105 | type: 'password', | ||
104 | }, | 106 | }, |
105 | }, | 107 | }, |
106 | intl, | 108 | }); |
107 | ); | 109 | } |
108 | })(); | ||
109 | 110 | ||
110 | submit(e) { | 111 | submit(e) { |
111 | e.preventDefault(); | 112 | e.preventDefault(); |
@@ -138,12 +139,12 @@ class Signup extends Component { | |||
138 | </Link> | 139 | </Link> |
139 | <H1>{intl.formatMessage(messages.headline)}</H1> | 140 | <H1>{intl.formatMessage(messages.headline)}</H1> |
140 | <div className="grid__row"> | 141 | <div className="grid__row"> |
141 | <Input field={form.$('firstname')} focus /> | 142 | <Input {...form.$('firstname').bind()} focus /> |
142 | <Input field={form.$('lastname')} /> | 143 | <Input {...form.$('lastname').bind()} /> |
143 | </div> | 144 | </div> |
144 | <Input field={form.$('email')} /> | 145 | <Input {...form.$('email').bind()} /> |
145 | <Input | 146 | <Input |
146 | field={form.$('password')} | 147 | {...form.$('password').bind()} |
147 | showPasswordToggle | 148 | showPasswordToggle |
148 | scorePassword | 149 | scorePassword |
149 | /> | 150 | /> |
@@ -158,12 +159,14 @@ class Signup extends Component { | |||
158 | label={`${intl.formatMessage(messages.submitButtonLabel)} ...`} | 159 | label={`${intl.formatMessage(messages.submitButtonLabel)} ...`} |
159 | loaded={false} | 160 | loaded={false} |
160 | disabled | 161 | disabled |
162 | onClick={noop} | ||
161 | /> | 163 | /> |
162 | ) : ( | 164 | ) : ( |
163 | <Button | 165 | <Button |
164 | type="submit" | 166 | type="submit" |
165 | className="auth__button" | 167 | className="auth__button" |
166 | label={intl.formatMessage(messages.submitButtonLabel)} | 168 | label={intl.formatMessage(messages.submitButtonLabel)} |
169 | onClick={noop} | ||
167 | /> | 170 | /> |
168 | )} | 171 | )} |
169 | <p className="legal"> | 172 | <p className="legal"> |
@@ -203,4 +206,4 @@ class Signup extends Component { | |||
203 | } | 206 | } |
204 | } | 207 | } |
205 | 208 | ||
206 | export default injectIntl(inject('actions')(observer(Signup))); | 209 | export default injectIntl(Signup); |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 069b6643c..eeb9ef54d 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -11,7 +11,7 @@ import Recipe from '../../../models/Recipe'; | |||
11 | import Service from '../../../models/Service'; | 11 | import Service from '../../../models/Service'; |
12 | import Tabs from '../../ui/Tabs/Tabs'; | 12 | import Tabs from '../../ui/Tabs/Tabs'; |
13 | import TabItem from '../../ui/Tabs/TabItem'; | 13 | import TabItem from '../../ui/Tabs/TabItem'; |
14 | import Input from '../../ui/Input'; | 14 | import Input from '../../ui/input/index'; |
15 | import Toggle from '../../ui/Toggle'; | 15 | import Toggle from '../../ui/Toggle'; |
16 | import Slider from '../../ui/Slider'; | 16 | import Slider from '../../ui/Slider'; |
17 | import Button from '../../ui/button'; | 17 | import Button from '../../ui/button'; |
@@ -292,7 +292,7 @@ class EditServiceForm extends Component { | |||
292 | <div className="settings__body"> | 292 | <div className="settings__body"> |
293 | <form onSubmit={e => this.submit(e)} id="form"> | 293 | <form onSubmit={e => this.submit(e)} id="form"> |
294 | <div className="service-name"> | 294 | <div className="service-name"> |
295 | <Input field={form.$('name')} focus /> | 295 | <Input {...form.$('name').bind()} focus /> |
296 | </div> | 296 | </div> |
297 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | 297 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( |
298 | <Tabs active={activeTabIndex}> | 298 | <Tabs active={activeTabIndex}> |
@@ -314,7 +314,7 @@ class EditServiceForm extends Component { | |||
314 | )} | 314 | )} |
315 | {recipe.hasCustomUrl && ( | 315 | {recipe.hasCustomUrl && ( |
316 | <TabItem title={intl.formatMessage(messages.tabOnPremise)}> | 316 | <TabItem title={intl.formatMessage(messages.tabOnPremise)}> |
317 | <Input field={form.$('customUrl')} /> | 317 | <Input {...form.$('customUrl').bind()} /> |
318 | {form.error === 'url-validation-error' && ( | 318 | {form.error === 'url-validation-error' && ( |
319 | <p className="franz-form__error"> | 319 | <p className="franz-form__error"> |
320 | {intl.formatMessage(messages.customUrlValidationError, { | 320 | {intl.formatMessage(messages.customUrlValidationError, { |
@@ -435,17 +435,17 @@ class EditServiceForm extends Component { | |||
435 | <div className="grid"> | 435 | <div className="grid"> |
436 | <div className="grid__row"> | 436 | <div className="grid__row"> |
437 | <Input | 437 | <Input |
438 | field={form.$('proxy.host')} | 438 | {...form.$('proxy.host').bind()} |
439 | className="proxyHost" | 439 | className="proxyHost" |
440 | /> | 440 | /> |
441 | <Input field={form.$('proxy.port')} /> | 441 | <Input {...form.$('proxy.port').bind()} /> |
442 | </div> | 442 | </div> |
443 | </div> | 443 | </div> |
444 | <div className="grid"> | 444 | <div className="grid"> |
445 | <div className="grid__row"> | 445 | <div className="grid__row"> |
446 | <Input field={form.$('proxy.user')} /> | 446 | <Input {...form.$('proxy.user').bind()} /> |
447 | <Input | 447 | <Input |
448 | field={form.$('proxy.password')} | 448 | {...form.$('proxy.password').bind()} |
449 | showPasswordToggle | 449 | showPasswordToggle |
450 | /> | 450 | /> |
451 | </div> | 451 | </div> |
@@ -464,7 +464,7 @@ class EditServiceForm extends Component { | |||
464 | )} | 464 | )} |
465 | 465 | ||
466 | <div className="user-agent"> | 466 | <div className="user-agent"> |
467 | <Input field={form.$('userAgentPref')} /> | 467 | <Input {...form.$('userAgentPref').bind()} /> |
468 | <p className="settings__help"> | 468 | <p className="settings__help"> |
469 | {intl.formatMessage(globalMessages.userAgentHelp)} | 469 | {intl.formatMessage(globalMessages.userAgentHelp)} |
470 | </p> | 470 | </p> |
diff --git a/src/components/settings/settings/EditSettingsForm.jsx b/src/components/settings/settings/EditSettingsForm.tsx index a327dccac..5c422b977 100644 --- a/src/components/settings/settings/EditSettingsForm.jsx +++ b/src/components/settings/settings/EditSettingsForm.tsx | |||
@@ -1,17 +1,14 @@ | |||
1 | import { systemPreferences } from '@electron/remote'; | 1 | import { systemPreferences } from '@electron/remote'; |
2 | import { Component } from 'react'; | 2 | import { Component } from 'react'; |
3 | import PropTypes from 'prop-types'; | ||
4 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
5 | import prettyBytes from 'pretty-bytes'; | 4 | import prettyBytes from 'pretty-bytes'; |
6 | import { defineMessages, injectIntl } from 'react-intl'; | 5 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
7 | |||
8 | import { mdiGithub, mdiOpenInNew, mdiPowerPlug } from '@mdi/js'; | 6 | import { mdiGithub, mdiOpenInNew, mdiPowerPlug } from '@mdi/js'; |
9 | |||
10 | import Form from '../../../lib/Form'; | 7 | import Form from '../../../lib/Form'; |
11 | import Button from '../../ui/button'; | 8 | import Button from '../../ui/button'; |
12 | import Toggle from '../../ui/Toggle'; | 9 | import Toggle from '../../ui/Toggle.js'; |
13 | import Select from '../../ui/Select'; | 10 | import Select from '../../ui/Select'; |
14 | import Input from '../../ui/Input'; | 11 | import Input from '../../ui/input/index'; |
15 | import ColorPickerInput from '../../ui/ColorPickerInput'; | 12 | import ColorPickerInput from '../../ui/ColorPickerInput'; |
16 | import Infobox from '../../ui/Infobox'; | 13 | import Infobox from '../../ui/Infobox'; |
17 | import { H1, H2, H3, H5 } from '../../ui/headline'; | 14 | import { H1, H2, H3, H5 } from '../../ui/headline'; |
@@ -20,9 +17,7 @@ import { | |||
20 | userDataPath, | 17 | userDataPath, |
21 | userDataRecipesPath, | 18 | userDataRecipesPath, |
22 | } from '../../../environment-remote'; | 19 | } from '../../../environment-remote'; |
23 | |||
24 | import { updateVersionParse } from '../../../helpers/update-helpers'; | 20 | import { updateVersionParse } from '../../../helpers/update-helpers'; |
25 | |||
26 | import { | 21 | import { |
27 | DEFAULT_ACCENT_COLOR, | 22 | DEFAULT_ACCENT_COLOR, |
28 | DEFAULT_APP_SETTINGS, | 23 | DEFAULT_APP_SETTINGS, |
@@ -264,6 +259,7 @@ const Hr = () => ( | |||
264 | style={{ marginBottom: 20, borderStyle: 'dashed' }} | 259 | style={{ marginBottom: 20, borderStyle: 'dashed' }} |
265 | /> | 260 | /> |
266 | ); | 261 | ); |
262 | |||
267 | const HrSections = () => ( | 263 | const HrSections = () => ( |
268 | <hr | 264 | <hr |
269 | className="settings__hr-sections" | 265 | className="settings__hr-sections" |
@@ -271,32 +267,41 @@ const HrSections = () => ( | |||
271 | /> | 267 | /> |
272 | ); | 268 | ); |
273 | 269 | ||
274 | class EditSettingsForm extends Component { | 270 | interface IProps extends WrappedComponentProps { |
275 | static propTypes = { | 271 | form: Form; |
276 | checkForUpdates: PropTypes.func.isRequired, | 272 | isCheckingForUpdates: boolean; |
277 | installUpdate: PropTypes.func.isRequired, | 273 | isUpdateAvailable: boolean; |
278 | form: PropTypes.instanceOf(Form).isRequired, | 274 | noUpdateAvailable: boolean; |
279 | onSubmit: PropTypes.func.isRequired, | 275 | updateIsReadyToInstall: boolean; |
280 | isCheckingForUpdates: PropTypes.bool.isRequired, | 276 | updateFailed: boolean; |
281 | isUpdateAvailable: PropTypes.bool.isRequired, | 277 | isClearingAllCache: boolean; |
282 | noUpdateAvailable: PropTypes.bool.isRequired, | 278 | isTodosActivated: boolean; |
283 | updateIsReadyToInstall: PropTypes.bool.isRequired, | 279 | automaticUpdates: boolean; |
284 | updateFailed: PropTypes.bool.isRequired, | 280 | isDarkmodeEnabled: boolean; |
285 | isClearingAllCache: PropTypes.bool.isRequired, | 281 | isAdaptableDarkModeEnabled: boolean; |
286 | onClearAllCache: PropTypes.func.isRequired, | 282 | isUseGrayscaleServicesEnabled: boolean; |
287 | getCacheSize: PropTypes.func.isRequired, | 283 | lockingFeatureEnabled: boolean; |
288 | isTodosActivated: PropTypes.bool.isRequired, | 284 | isSplitModeEnabled: boolean; |
289 | automaticUpdates: PropTypes.bool.isRequired, | 285 | isOnline: boolean; |
290 | isDarkmodeEnabled: PropTypes.bool.isRequired, | 286 | showServicesUpdatedInfoBar: boolean; |
291 | isAdaptableDarkModeEnabled: PropTypes.bool.isRequired, | 287 | updateVersion: string; |
292 | isUseGrayscaleServicesEnabled: PropTypes.bool.isRequired, | 288 | serverURL: string; |
293 | openProcessManager: PropTypes.func.isRequired, | 289 | onClearAllCache: () => void; |
294 | isSplitModeEnabled: PropTypes.bool.isRequired, | 290 | getCacheSize: () => void; |
295 | isOnline: PropTypes.bool.isRequired, | 291 | checkForUpdates: () => void; |
296 | serverURL: PropTypes.string.isRequired, | 292 | installUpdate: () => void; |
297 | }; | 293 | openProcessManager: () => void; |
294 | onSubmit: (...args: any[]) => void; | ||
295 | } | ||
296 | |||
297 | interface IState { | ||
298 | activeSetttingsTab: string; | ||
299 | clearCacheButtonClicked: boolean; | ||
300 | } | ||
298 | 301 | ||
299 | constructor(props) { | 302 | @observer |
303 | class EditSettingsForm extends Component<IProps, IState> { | ||
304 | constructor(props: IProps) { | ||
300 | super(props); | 305 | super(props); |
301 | 306 | ||
302 | this.state = { | 307 | this.state = { |
@@ -358,8 +363,8 @@ class EditSettingsForm extends Component { | |||
358 | isTodosActivated, | 363 | isTodosActivated, |
359 | isOnline, | 364 | isOnline, |
360 | serverURL, | 365 | serverURL, |
366 | intl, | ||
361 | } = this.props; | 367 | } = this.props; |
362 | const { intl } = this.props; | ||
363 | 368 | ||
364 | let updateButtonLabelMessage = messages.buttonSearchForUpdate; | 369 | let updateButtonLabelMessage = messages.buttonSearchForUpdate; |
365 | if (isCheckingForUpdates) { | 370 | if (isCheckingForUpdates) { |
@@ -520,7 +525,7 @@ class EditSettingsForm extends Component { | |||
520 | <Toggle field={form.$('reloadAfterResume')} /> | 525 | <Toggle field={form.$('reloadAfterResume')} /> |
521 | {reloadAfterResume && ( | 526 | {reloadAfterResume && ( |
522 | <div> | 527 | <div> |
523 | <Input field={form.$('reloadAfterResumeTime')} /> | 528 | <Input {...form.$('reloadAfterResumeTime').bind()} /> |
524 | <Hr /> | 529 | <Hr /> |
525 | </div> | 530 | </div> |
526 | )} | 531 | )} |
@@ -541,7 +546,7 @@ class EditSettingsForm extends Component { | |||
541 | <Input | 546 | <Input |
542 | placeholder="Todo Server" | 547 | placeholder="Todo Server" |
543 | onChange={e => this.submit(e)} | 548 | onChange={e => this.submit(e)} |
544 | field={form.$('customTodoServer')} | 549 | {...form.$('customTodoServer').bind()} |
545 | /> | 550 | /> |
546 | <p | 551 | <p |
547 | className="settings__message" | 552 | className="settings__message" |
@@ -580,7 +585,7 @@ class EditSettingsForm extends Component { | |||
580 | <Input | 585 | <Input |
581 | placeholder="17:00" | 586 | placeholder="17:00" |
582 | onChange={e => this.submit(e)} | 587 | onChange={e => this.submit(e)} |
583 | field={form.$('scheduledDNDStart')} | 588 | {...form.$('scheduledDNDStart').bind()} |
584 | type="time" | 589 | type="time" |
585 | /> | 590 | /> |
586 | </div> | 591 | </div> |
@@ -593,7 +598,7 @@ class EditSettingsForm extends Component { | |||
593 | <Input | 598 | <Input |
594 | placeholder="09:00" | 599 | placeholder="09:00" |
595 | onChange={e => this.submit(e)} | 600 | onChange={e => this.submit(e)} |
596 | field={form.$('scheduledDNDEnd')} | 601 | {...form.$('scheduledDNDEnd').bind()} |
597 | type="time" | 602 | type="time" |
598 | /> | 603 | /> |
599 | </div> | 604 | </div> |
@@ -711,7 +716,7 @@ class EditSettingsForm extends Component { | |||
711 | max={SPLIT_COLUMNS_MAX} | 716 | max={SPLIT_COLUMNS_MAX} |
712 | placeholder={`${SPLIT_COLUMNS_MIN}-${SPLIT_COLUMNS_MAX}`} | 717 | placeholder={`${SPLIT_COLUMNS_MIN}-${SPLIT_COLUMNS_MAX}`} |
713 | onChange={e => this.submit(e)} | 718 | onChange={e => this.submit(e)} |
714 | field={form.$('splitColumns')} | 719 | {...form.$('splitColumns').bind()} |
715 | /> | 720 | /> |
716 | )} | 721 | )} |
717 | 722 | ||
@@ -825,7 +830,7 @@ class EditSettingsForm extends Component { | |||
825 | <Input | 830 | <Input |
826 | placeholder={intl.formatMessage(messages.lockedPassword)} | 831 | placeholder={intl.formatMessage(messages.lockedPassword)} |
827 | onChange={e => this.submit(e)} | 832 | onChange={e => this.submit(e)} |
828 | field={form.$('lockedPassword')} | 833 | {...form.$('lockedPassword')} |
829 | type="password" | 834 | type="password" |
830 | scorePassword | 835 | scorePassword |
831 | showPasswordToggle | 836 | showPasswordToggle |
@@ -835,7 +840,7 @@ class EditSettingsForm extends Component { | |||
835 | <Input | 840 | <Input |
836 | placeholder="Lock after inactivity" | 841 | placeholder="Lock after inactivity" |
837 | onChange={e => this.submit(e)} | 842 | onChange={e => this.submit(e)} |
838 | field={form.$('inactivityLock')} | 843 | {...form.$('inactivityLock')} |
839 | autoFocus | 844 | autoFocus |
840 | /> | 845 | /> |
841 | <p>{intl.formatMessage(messages.inactivityLockInfo)}</p> | 846 | <p>{intl.formatMessage(messages.inactivityLockInfo)}</p> |
@@ -927,7 +932,7 @@ class EditSettingsForm extends Component { | |||
927 | <Input | 932 | <Input |
928 | placeholder="User Agent" | 933 | placeholder="User Agent" |
929 | onChange={e => this.submit(e)} | 934 | onChange={e => this.submit(e)} |
930 | field={form.$('userAgentPref')} | 935 | {...form.$('userAgentPref').bind()} |
931 | /> | 936 | /> |
932 | <p className="settings__help"> | 937 | <p className="settings__help"> |
933 | {intl.formatMessage(globalMessages.userAgentHelp)} | 938 | {intl.formatMessage(globalMessages.userAgentHelp)} |
@@ -1128,4 +1133,4 @@ class EditSettingsForm extends Component { | |||
1128 | } | 1133 | } |
1129 | } | 1134 | } |
1130 | 1135 | ||
1131 | export default injectIntl(observer(EditSettingsForm)); | 1136 | export default injectIntl(EditSettingsForm); |
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.tsx index c2773a47d..3b604a79f 100644 --- a/src/components/settings/user/EditUserForm.js +++ b/src/components/settings/user/EditUserForm.tsx | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, FormEvent, FormEventHandler, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import { observer } from 'mobx-react'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | ||
5 | import { Link } from 'react-router-dom'; | 4 | import { Link } from 'react-router-dom'; |
6 | 5 | import { noop } from 'lodash'; | |
7 | import Input from '../../ui/input/index'; | 6 | import Input from '../../ui/input/index'; |
8 | import Form from '../../../lib/Form'; | 7 | import Form from '../../../lib/Form'; |
9 | import Button from '../../ui/button'; | 8 | import Button from '../../ui/button'; |
@@ -38,15 +37,16 @@ const messages = defineMessages({ | |||
38 | }, | 37 | }, |
39 | }); | 38 | }); |
40 | 39 | ||
41 | class EditUserForm extends Component { | 40 | interface IProps extends WrappedComponentProps { |
42 | static propTypes = { | 41 | status: string[]; |
43 | status: MobxPropTypes.observableArray.isRequired, | 42 | form: Form; |
44 | form: PropTypes.instanceOf(Form).isRequired, | 43 | onSubmit: FormEventHandler<HTMLFormElement>; |
45 | onSubmit: PropTypes.func.isRequired, | 44 | isSaving: boolean; |
46 | isSaving: PropTypes.bool.isRequired, | 45 | } |
47 | }; | ||
48 | 46 | ||
49 | submit(e) { | 47 | @observer |
48 | class EditUserForm extends Component<IProps> { | ||
49 | submit(e: FormEvent<HTMLFormElement>): void { | ||
50 | e.preventDefault(); | 50 | e.preventDefault(); |
51 | this.props.form.submit({ | 51 | this.props.form.submit({ |
52 | onSuccess: form => { | 52 | onSuccess: form => { |
@@ -57,14 +57,14 @@ class EditUserForm extends Component { | |||
57 | }); | 57 | }); |
58 | } | 58 | } |
59 | 59 | ||
60 | render() { | 60 | render(): ReactElement { |
61 | const { | 61 | const { |
62 | // user, | 62 | // user, |
63 | status, | 63 | status, |
64 | form, | 64 | form, |
65 | isSaving, | 65 | isSaving, |
66 | intl, | ||
66 | } = this.props; | 67 | } = this.props; |
67 | const { intl } = this.props; | ||
68 | 68 | ||
69 | return ( | 69 | return ( |
70 | <div className="settings__main"> | 70 | <div className="settings__main"> |
@@ -92,9 +92,9 @@ class EditUserForm extends Component { | |||
92 | <Input {...form.$('lastname').bind()} /> | 92 | <Input {...form.$('lastname').bind()} /> |
93 | </div> | 93 | </div> |
94 | <Input {...form.$('email').bind()} /> | 94 | <Input {...form.$('email').bind()} /> |
95 | <Radio field={form.$('accountType')} /> | 95 | <Radio field={form.$('accountType')} className="" /> |
96 | {form.$('accountType').value === 'company' && ( | 96 | {form.$('accountType').value === 'company' && ( |
97 | <Input field={form.$('organization')} /> | 97 | <Input {...form.$('organization').bind()} /> |
98 | )} | 98 | )} |
99 | <H2>{intl.formatMessage(messages.headlinePassword)}</H2> | 99 | <H2>{intl.formatMessage(messages.headlinePassword)}</H2> |
100 | <Input {...form.$('oldPassword').bind()} showPasswordToggle /> | 100 | <Input {...form.$('oldPassword').bind()} showPasswordToggle /> |
@@ -114,12 +114,14 @@ class EditUserForm extends Component { | |||
114 | loaded={!isSaving} | 114 | loaded={!isSaving} |
115 | buttonType="secondary" | 115 | buttonType="secondary" |
116 | disabled | 116 | disabled |
117 | onClick={noop} | ||
117 | /> | 118 | /> |
118 | ) : ( | 119 | ) : ( |
119 | <Button | 120 | <Button |
120 | type="submit" | 121 | type="submit" |
121 | label={intl.formatMessage(messages.buttonSave)} | 122 | label={intl.formatMessage(messages.buttonSave)} |
122 | htmlForm="form" | 123 | htmlForm="form" |
124 | onClick={noop} | ||
123 | /> | 125 | /> |
124 | )} | 126 | )} |
125 | </div> | 127 | </div> |
@@ -128,4 +130,4 @@ class EditUserForm extends Component { | |||
128 | } | 130 | } |
129 | } | 131 | } |
130 | 132 | ||
131 | export default injectIntl(observer(EditUserForm)); | 133 | export default injectIntl(EditUserForm); |
diff --git a/src/components/ui/ColorPickerInput.tsx b/src/components/ui/ColorPickerInput.tsx index 7e3965331..da1fffb71 100644 --- a/src/components/ui/ColorPickerInput.tsx +++ b/src/components/ui/ColorPickerInput.tsx | |||
@@ -1,15 +1,25 @@ | |||
1 | import { ChangeEvent, Component, createRef, RefObject } from 'react'; | 1 | import { |
2 | ChangeEvent, | ||
3 | ChangeEventHandler, | ||
4 | Component, | ||
5 | createRef, | ||
6 | RefObject, | ||
7 | } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | 8 | import { observer } from 'mobx-react'; |
3 | import classnames from 'classnames'; | 9 | import classnames from 'classnames'; |
4 | import { SliderPicker } from 'react-color'; | 10 | import { SliderPicker } from 'react-color'; |
11 | import { noop } from 'lodash'; | ||
5 | import { Field } from '../../@types/mobx-form.types'; | 12 | import { Field } from '../../@types/mobx-form.types'; |
6 | 13 | ||
7 | interface IProps { | 14 | interface IProps { |
8 | field: Field; | 15 | field: Field; |
9 | className?: string; | 16 | className?: string; |
10 | focus?: boolean; | 17 | focus?: boolean; |
18 | onChange: ChangeEventHandler<HTMLInputElement>; | ||
11 | } | 19 | } |
12 | 20 | ||
21 | // TODO - [TS DEBT] check if field can be spread instead of having it single field attribute in interface | ||
22 | @observer | ||
13 | class ColorPickerInput extends Component<IProps> { | 23 | class ColorPickerInput extends Component<IProps> { |
14 | private inputElement: RefObject<HTMLInputElement> = | 24 | private inputElement: RefObject<HTMLInputElement> = |
15 | createRef<HTMLInputElement>(); | 25 | createRef<HTMLInputElement>(); |
@@ -22,7 +32,9 @@ class ColorPickerInput extends Component<IProps> { | |||
22 | } | 32 | } |
23 | 33 | ||
24 | onChange(e: ChangeEvent<HTMLInputElement>) { | 34 | onChange(e: ChangeEvent<HTMLInputElement>) { |
25 | const { field } = this.props; | 35 | const { field, onChange = noop } = this.props; |
36 | |||
37 | onChange(e); | ||
26 | if (field.onChange) { | 38 | if (field.onChange) { |
27 | field.onChange(e); | 39 | field.onChange(e); |
28 | } | 40 | } |
@@ -87,4 +99,4 @@ class ColorPickerInput extends Component<IProps> { | |||
87 | } | 99 | } |
88 | } | 100 | } |
89 | 101 | ||
90 | export default observer(ColorPickerInput); | 102 | export default ColorPickerInput; |
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx deleted file mode 100644 index c22dc5838..000000000 --- a/src/components/ui/Input.tsx +++ /dev/null | |||
@@ -1,170 +0,0 @@ | |||
1 | import { | ||
2 | ChangeEvent, | ||
3 | ChangeEventHandler, | ||
4 | Component, | ||
5 | createRef, | ||
6 | ReactElement, | ||
7 | RefObject, | ||
8 | } from 'react'; | ||
9 | import { observer } from 'mobx-react'; | ||
10 | import classnames from 'classnames'; | ||
11 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
12 | import { mdiEye, mdiEyeOff } from '@mdi/js'; | ||
13 | import { noop } from 'lodash'; | ||
14 | import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers'; | ||
15 | import Icon from './icon'; | ||
16 | import { Field } from '../../@types/mobx-form.types'; | ||
17 | |||
18 | const messages = defineMessages({ | ||
19 | passwordToggle: { | ||
20 | id: 'settings.app.form.passwordToggle', | ||
21 | defaultMessage: 'Password toggle', | ||
22 | }, | ||
23 | }); | ||
24 | |||
25 | interface IProps extends WrappedComponentProps { | ||
26 | field: Field; | ||
27 | className?: string; | ||
28 | focus?: boolean; | ||
29 | showPasswordToggle?: boolean; | ||
30 | showLabel?: boolean; | ||
31 | scorePassword?: boolean; | ||
32 | prefix?: string; | ||
33 | suffix?: string; | ||
34 | placeholder?: string; | ||
35 | onChange?: ChangeEventHandler<HTMLInputElement>; | ||
36 | } | ||
37 | |||
38 | interface IState { | ||
39 | showPassword: boolean; | ||
40 | passwordScore: number; | ||
41 | } | ||
42 | |||
43 | // Can this file be merged into the './input/index.tsx' file? | ||
44 | @observer | ||
45 | class Input extends Component<IProps, IState> { | ||
46 | private inputElement: RefObject<HTMLInputElement> = | ||
47 | createRef<HTMLInputElement>(); | ||
48 | |||
49 | constructor(props: IProps) { | ||
50 | super(props); | ||
51 | |||
52 | this.state = { | ||
53 | showPassword: false, | ||
54 | passwordScore: 0, | ||
55 | }; | ||
56 | } | ||
57 | |||
58 | componentDidMount(): void { | ||
59 | const { focus = false } = this.props; | ||
60 | if (focus) { | ||
61 | this.focus(); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | onChange(e: ChangeEvent<HTMLInputElement>): void { | ||
66 | const { field, scorePassword, onChange = noop } = this.props; | ||
67 | |||
68 | if (field.onChange) { | ||
69 | onChange(e); | ||
70 | field.onChange(e); | ||
71 | } | ||
72 | |||
73 | if (scorePassword) { | ||
74 | this.setState({ | ||
75 | passwordScore: scorePasswordFunc(field.value as string), | ||
76 | }); | ||
77 | } | ||
78 | } | ||
79 | |||
80 | focus() { | ||
81 | if (this.inputElement && this.inputElement.current) { | ||
82 | this.inputElement.current!.focus(); | ||
83 | } | ||
84 | } | ||
85 | |||
86 | render(): ReactElement { | ||
87 | const { | ||
88 | field, | ||
89 | className = null, | ||
90 | showPasswordToggle = false, | ||
91 | showLabel = true, | ||
92 | scorePassword = false, | ||
93 | prefix = '', | ||
94 | suffix = '', | ||
95 | intl, | ||
96 | } = this.props; | ||
97 | |||
98 | const { passwordScore } = this.state; | ||
99 | |||
100 | let { type } = field; | ||
101 | if (type === 'password' && this.state.showPassword) { | ||
102 | type = 'text'; | ||
103 | } | ||
104 | |||
105 | return ( | ||
106 | <div | ||
107 | className={classnames({ | ||
108 | 'franz-form__field': true, | ||
109 | 'has-error': field.error, | ||
110 | [`${className}`]: className, | ||
111 | })} | ||
112 | > | ||
113 | <div className="franz-form__input-wrapper"> | ||
114 | {prefix && <span className="franz-form__input-prefix">{prefix}</span>} | ||
115 | <input | ||
116 | id={field.id} | ||
117 | type={type} | ||
118 | className="franz-form__input" | ||
119 | name={field.name} | ||
120 | value={field.value} | ||
121 | placeholder={field.placeholder} | ||
122 | onChange={e => this.onChange(e)} | ||
123 | onBlur={field.onBlur} | ||
124 | onFocus={field.onFocus} | ||
125 | ref={this.inputElement} | ||
126 | disabled={field.disabled} | ||
127 | /> | ||
128 | {suffix && <span className="franz-form__input-suffix">{suffix}</span>} | ||
129 | {showPasswordToggle && ( | ||
130 | <button | ||
131 | type="button" | ||
132 | className={classnames({ | ||
133 | 'franz-form__input-modifier': true, | ||
134 | })} | ||
135 | onClick={() => | ||
136 | this.setState(prevState => ({ | ||
137 | showPassword: !prevState.showPassword, | ||
138 | })) | ||
139 | } | ||
140 | tabIndex={-1} | ||
141 | aria-label={intl.formatMessage(messages.passwordToggle)} | ||
142 | > | ||
143 | <Icon icon={this.state.showPassword ? mdiEye : mdiEyeOff} /> | ||
144 | </button> | ||
145 | )} | ||
146 | {scorePassword && ( | ||
147 | <div className="franz-form__password-score"> | ||
148 | {/* <progress value={this.state.passwordScore} max="100" /> */} | ||
149 | <meter | ||
150 | value={passwordScore < 5 ? 5 : passwordScore} | ||
151 | low={30} | ||
152 | high={75} | ||
153 | optimum={100} | ||
154 | max={100} | ||
155 | /> | ||
156 | </div> | ||
157 | )} | ||
158 | </div> | ||
159 | {field.label && showLabel && ( | ||
160 | <label className="franz-form__label" htmlFor={field.name}> | ||
161 | {field.label} | ||
162 | </label> | ||
163 | )} | ||
164 | {field.error && <div className="franz-form__error">{field.error}</div>} | ||
165 | </div> | ||
166 | ); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | export default injectIntl(Input); | ||
diff --git a/src/components/ui/input/index.tsx b/src/components/ui/input/index.tsx index 2a36d7aa9..cb26c0ea4 100644 --- a/src/components/ui/input/index.tsx +++ b/src/components/ui/input/index.tsx | |||
@@ -1,5 +1,4 @@ | |||
1 | import { mdiEye, mdiEyeOff } from '@mdi/js'; | 1 | import { mdiEye, mdiEyeOff } from '@mdi/js'; |
2 | import Icon from '@mdi/react'; | ||
3 | import classnames from 'classnames'; | 2 | import classnames from 'classnames'; |
4 | import { | 3 | import { |
5 | Component, | 4 | Component, |
@@ -7,9 +6,13 @@ import { | |||
7 | InputHTMLAttributes, | 6 | InputHTMLAttributes, |
8 | ReactElement, | 7 | ReactElement, |
9 | RefObject, | 8 | RefObject, |
9 | KeyboardEvent, | ||
10 | } from 'react'; | 10 | } from 'react'; |
11 | import injectSheet, { WithStylesProps } from 'react-jss'; | 11 | import withStyles, { WithStylesProps } from 'react-jss'; |
12 | import { noop } from 'lodash'; | 12 | import { noop } from 'lodash'; |
13 | import { observer } from 'mobx-react'; | ||
14 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
15 | import Icon from '../icon'; | ||
13 | import { IFormField } from '../typings/generic'; | 16 | import { IFormField } from '../typings/generic'; |
14 | import Error from '../error'; | 17 | import Error from '../error'; |
15 | import Label from '../label'; | 18 | import Label from '../label'; |
@@ -17,6 +20,12 @@ import Wrapper from '../wrapper'; | |||
17 | import { scorePasswordFunc } from './scorePassword'; | 20 | import { scorePasswordFunc } from './scorePassword'; |
18 | import styles from './styles'; | 21 | import styles from './styles'; |
19 | 22 | ||
23 | const messages = defineMessages({ | ||
24 | passwordToggle: { | ||
25 | id: 'settings.app.form.passwordToggle', | ||
26 | defaultMessage: 'Password toggle', | ||
27 | }, | ||
28 | }); | ||
20 | interface IData { | 29 | interface IData { |
21 | [index: string]: string; | 30 | [index: string]: string; |
22 | } | 31 | } |
@@ -24,7 +33,8 @@ interface IData { | |||
24 | interface IProps | 33 | interface IProps |
25 | extends InputHTMLAttributes<HTMLInputElement>, | 34 | extends InputHTMLAttributes<HTMLInputElement>, |
26 | IFormField, | 35 | IFormField, |
27 | WithStylesProps<typeof styles> { | 36 | WithStylesProps<typeof styles>, |
37 | WrappedComponentProps { | ||
28 | focus?: boolean; | 38 | focus?: boolean; |
29 | prefix?: string; | 39 | prefix?: string; |
30 | suffix?: string; | 40 | suffix?: string; |
@@ -40,8 +50,10 @@ interface IState { | |||
40 | passwordScore: number; | 50 | passwordScore: number; |
41 | } | 51 | } |
42 | 52 | ||
43 | class InputComponent extends Component<IProps, IState> { | 53 | @observer |
44 | private inputRef: RefObject<HTMLInputElement> = createRef<HTMLInputElement>(); | 54 | class Input extends Component<IProps, IState> { |
55 | private inputElement: RefObject<HTMLInputElement> = | ||
56 | createRef<HTMLInputElement>(); | ||
45 | 57 | ||
46 | constructor(props: IProps) { | 58 | constructor(props: IProps) { |
47 | super(props); | 59 | super(props); |
@@ -53,36 +65,40 @@ class InputComponent extends Component<IProps, IState> { | |||
53 | } | 65 | } |
54 | 66 | ||
55 | componentDidMount(): void { | 67 | componentDidMount(): void { |
56 | const { focus, data = {} } = this.props; | 68 | const { focus = false, data = {} } = this.props; |
57 | 69 | ||
58 | if (this.inputRef && this.inputRef.current) { | 70 | if (this.inputElement && this.inputElement.current) { |
59 | if (focus) { | 71 | if (focus) { |
60 | this.inputRef.current.focus(); | 72 | this.inputElement.current.focus(); |
61 | } | 73 | } |
62 | 74 | ||
63 | for (const key of Object.keys(data)) | 75 | for (const key of Object.keys(data)) { |
64 | this.inputRef.current!.dataset[key] = data[key]; | 76 | this.inputElement.current.dataset[key] = data[key]; |
77 | } | ||
65 | } | 78 | } |
66 | } | 79 | } |
67 | 80 | ||
68 | onChange(e: React.ChangeEvent<HTMLInputElement>): void { | 81 | onChange(e: React.ChangeEvent<HTMLInputElement>): void { |
69 | const { scorePassword, onChange } = this.props; | 82 | const { scorePassword, onChange = noop } = this.props; |
70 | 83 | ||
71 | if (onChange) { | 84 | onChange(e); |
72 | onChange(e); | ||
73 | } | ||
74 | 85 | ||
75 | if (this.inputRef && this.inputRef.current && scorePassword) { | 86 | if (scorePassword) { |
87 | console.log( | ||
88 | '--->', | ||
89 | e.target.value, | ||
90 | scorePasswordFunc(e.target.value as string), | ||
91 | ); | ||
76 | this.setState({ | 92 | this.setState({ |
77 | passwordScore: scorePasswordFunc(this.inputRef.current.value), | 93 | passwordScore: scorePasswordFunc(e.target.value), |
78 | }); | 94 | }); |
79 | } | 95 | } |
80 | } | 96 | } |
81 | 97 | ||
82 | onInputKeyPress(e: React.KeyboardEvent): void { | 98 | onInputKeyPress(e: KeyboardEvent<HTMLInputElement>): void { |
83 | if (e.key === 'Enter') { | 99 | if (e.key === 'Enter') { |
84 | const { onEnterKey } = this.props; | 100 | const { onEnterKey = noop } = this.props; |
85 | onEnterKey && onEnterKey(); | 101 | onEnterKey(); |
86 | } | 102 | } |
87 | } | 103 | } |
88 | 104 | ||
@@ -113,10 +129,9 @@ class InputComponent extends Component<IProps, IState> { | |||
113 | type = 'text', | 129 | type = 'text', |
114 | disabled = false, | 130 | disabled = false, |
115 | readOnly, | 131 | readOnly, |
132 | intl, | ||
116 | } = this.props; | 133 | } = this.props; |
117 | |||
118 | const { showPassword, passwordScore } = this.state; | 134 | const { showPassword, passwordScore } = this.state; |
119 | |||
120 | const inputType = type === 'password' && showPassword ? 'text' : type; | 135 | const inputType = type === 'password' && showPassword ? 'text' : type; |
121 | 136 | ||
122 | return ( | 137 | return ( |
@@ -125,79 +140,85 @@ class InputComponent extends Component<IProps, IState> { | |||
125 | identifier="franz-input" | 140 | identifier="franz-input" |
126 | noMargin={noMargin} | 141 | noMargin={noMargin} |
127 | > | 142 | > |
128 | <Label | 143 | {label && showLabel && ( |
129 | title={label} | 144 | <Label |
130 | showLabel={showLabel} | 145 | title={label} |
131 | htmlFor={id} | 146 | showLabel={showLabel} |
132 | className={classes.label} | 147 | htmlFor={id} |
133 | isRequired={required} | 148 | className={classes.label} |
149 | isRequired={required} | ||
150 | /> | ||
151 | )} | ||
152 | <div | ||
153 | className={classnames({ | ||
154 | [`${inputClassName}`]: inputClassName, | ||
155 | // [`${classes.hasPasswordScore}`]: scorePassword, | ||
156 | [`${classes.wrapper}`]: true, | ||
157 | [`${classes.disabled}`]: disabled, | ||
158 | [`${classes.hasError}`]: error, | ||
159 | })} | ||
134 | > | 160 | > |
161 | {prefix && <span className={classes.prefix}>{prefix}</span>} | ||
162 | <input | ||
163 | id={id} | ||
164 | type={inputType} | ||
165 | name={name} | ||
166 | value={value as string} | ||
167 | placeholder={placeholder} | ||
168 | spellCheck={spellCheck} | ||
169 | className={classes.input} | ||
170 | ref={this.inputElement} | ||
171 | onChange={this.onChange.bind(this)} | ||
172 | onFocus={onFocus} | ||
173 | onBlur={onBlur} | ||
174 | disabled={disabled} | ||
175 | onKeyPress={this.onInputKeyPress.bind(this)} | ||
176 | min={min} | ||
177 | max={max} | ||
178 | step={step} | ||
179 | readOnly={readOnly} | ||
180 | /> | ||
181 | |||
182 | {suffix && <span className={classes.suffix}>{suffix}</span>} | ||
183 | |||
184 | {showPasswordToggle && ( | ||
185 | <button | ||
186 | type="button" | ||
187 | className={classnames({ | ||
188 | 'franz-form__input-modifier': true, | ||
189 | })} | ||
190 | onClick={() => | ||
191 | this.setState(prevState => ({ | ||
192 | showPassword: !prevState.showPassword, | ||
193 | })) | ||
194 | } | ||
195 | tabIndex={-1} | ||
196 | aria-label={intl.formatMessage(messages.passwordToggle)} | ||
197 | > | ||
198 | <Icon icon={this.state.showPassword ? mdiEye : mdiEyeOff} /> | ||
199 | </button> | ||
200 | )} | ||
201 | </div> | ||
202 | {scorePassword && ( | ||
135 | <div | 203 | <div |
136 | className={classnames({ | 204 | className={classnames({ |
137 | [`${inputClassName}`]: inputClassName, | 205 | [`${classes.passwordScore}`]: true, |
138 | [`${classes.hasPasswordScore}`]: scorePassword, | ||
139 | [`${classes.wrapper}`]: true, | ||
140 | [`${classes.disabled}`]: disabled, | ||
141 | [`${classes.hasError}`]: error, | 206 | [`${classes.hasError}`]: error, |
142 | })} | 207 | })} |
143 | > | 208 | > |
144 | {prefix && <span className={classes.prefix}>{prefix}</span>} | 209 | <meter |
145 | <input | 210 | value={passwordScore < 5 ? 5 : passwordScore} |
146 | id={id} | 211 | low={30} |
147 | type={inputType} | 212 | high={75} |
148 | name={name} | 213 | optimum={100} |
149 | value={value as string} | 214 | max={100} |
150 | placeholder={placeholder} | ||
151 | spellCheck={spellCheck} | ||
152 | className={classes.input} | ||
153 | ref={this.inputRef} | ||
154 | onChange={this.onChange.bind(this)} | ||
155 | onFocus={onFocus} | ||
156 | onBlur={onBlur} | ||
157 | disabled={disabled} | ||
158 | onKeyPress={this.onInputKeyPress.bind(this)} | ||
159 | min={min} | ||
160 | max={max} | ||
161 | step={step} | ||
162 | readOnly={readOnly} | ||
163 | /> | 215 | /> |
164 | {suffix && <span className={classes.suffix}>{suffix}</span>} | ||
165 | {showPasswordToggle && ( | ||
166 | <button | ||
167 | type="button" | ||
168 | className={classes.formModifier} | ||
169 | onClick={() => | ||
170 | this.setState(prevState => ({ | ||
171 | showPassword: !prevState.showPassword, | ||
172 | })) | ||
173 | } | ||
174 | tabIndex={-1} | ||
175 | > | ||
176 | <Icon path={!showPassword ? mdiEye : mdiEyeOff} /> | ||
177 | </button> | ||
178 | )} | ||
179 | </div> | 216 | </div> |
180 | {scorePassword && ( | 217 | )} |
181 | <div | ||
182 | className={classnames({ | ||
183 | [`${classes.passwordScore}`]: true, | ||
184 | [`${classes.hasError}`]: error, | ||
185 | })} | ||
186 | > | ||
187 | <meter | ||
188 | value={passwordScore < 5 ? 5 : passwordScore} | ||
189 | low={30} | ||
190 | high={75} | ||
191 | optimum={100} | ||
192 | max={100} | ||
193 | /> | ||
194 | </div> | ||
195 | )} | ||
196 | </Label> | ||
197 | {error && <Error message={error} />} | 218 | {error && <Error message={error} />} |
198 | </Wrapper> | 219 | </Wrapper> |
199 | ); | 220 | ); |
200 | } | 221 | } |
201 | } | 222 | } |
202 | 223 | ||
203 | export default injectSheet(styles, { injectTheme: true })(InputComponent); | 224 | export default injectIntl(withStyles(styles, { injectTheme: true })(Input)); |
diff --git a/src/components/ui/input/styles.ts b/src/components/ui/input/styles.ts index 04c1b3991..ebae0e40d 100644 --- a/src/components/ui/input/styles.ts +++ b/src/components/ui/input/styles.ts | |||
@@ -49,7 +49,6 @@ export default (theme: Theme) => ({ | |||
49 | }, | 49 | }, |
50 | passwordScore: { | 50 | passwordScore: { |
51 | background: theme.inputScorePasswordBackground, | 51 | background: theme.inputScorePasswordBackground, |
52 | border: theme.inputBorder, | ||
53 | borderTopWidth: 0, | 52 | borderTopWidth: 0, |
54 | borderBottomLeftRadius: theme.borderRadiusSmall, | 53 | borderBottomLeftRadius: theme.borderRadiusSmall, |
55 | borderBottomRightRadius: theme.borderRadiusSmall, | 54 | borderBottomRightRadius: theme.borderRadiusSmall, |
diff --git a/src/containers/auth/LockedScreen.tsx b/src/containers/auth/LockedScreen.tsx index 611a0757c..a4cb43f73 100644 --- a/src/containers/auth/LockedScreen.tsx +++ b/src/containers/auth/LockedScreen.tsx | |||
@@ -12,16 +12,19 @@ interface IProps { | |||
12 | stores?: RealStores; | 12 | stores?: RealStores; |
13 | } | 13 | } |
14 | 14 | ||
15 | interface IState { | ||
16 | error: boolean; | ||
17 | } | ||
18 | |||
15 | @inject('stores', 'actions') | 19 | @inject('stores', 'actions') |
16 | @observer | 20 | @observer |
17 | class LockedScreen extends Component<IProps> { | 21 | class LockedScreen extends Component<IProps, IState> { |
18 | state = { | ||
19 | error: false, | ||
20 | }; | ||
21 | |||
22 | constructor(props: StoresProps) { | 22 | constructor(props: StoresProps) { |
23 | super(props); | 23 | super(props); |
24 | 24 | ||
25 | this.state = { | ||
26 | error: false, | ||
27 | }; | ||
25 | this.onSubmit = this.onSubmit.bind(this); | 28 | this.onSubmit = this.onSubmit.bind(this); |
26 | this.unlock = this.unlock.bind(this); | 29 | this.unlock = this.unlock.bind(this); |
27 | } | 30 | } |
@@ -43,9 +46,7 @@ class LockedScreen extends Component<IProps> { | |||
43 | }); | 46 | }); |
44 | } else { | 47 | } else { |
45 | this.setState({ | 48 | this.setState({ |
46 | error: { | 49 | error: true, |
47 | code: 'invalid-credentials', | ||
48 | }, | ||
49 | }); | 50 | }); |
50 | } | 51 | } |
51 | } | 52 | } |
@@ -71,7 +72,7 @@ class LockedScreen extends Component<IProps> { | |||
71 | unlock={this.unlock} | 72 | unlock={this.unlock} |
72 | useTouchIdToUnlock={useTouchIdToUnlock} | 73 | useTouchIdToUnlock={useTouchIdToUnlock} |
73 | isSubmitting={stores!.user.loginRequest.isExecuting} | 74 | isSubmitting={stores!.user.loginRequest.isExecuting} |
74 | error={this.state.error || {}} | 75 | error={this.state.error} |
75 | /> | 76 | /> |
76 | </div> | 77 | </div> |
77 | </div> | 78 | </div> |
diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx index 162d422ce..0d8a0a758 100644 --- a/src/containers/settings/EditSettingsScreen.tsx +++ b/src/containers/settings/EditSettingsScreen.tsx | |||
@@ -1,7 +1,7 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { Component, ReactElement } from 'react'; | 2 | import { Component, ReactElement } from 'react'; |
3 | import { inject, observer } from 'mobx-react'; | 3 | import { inject, observer } from 'mobx-react'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | 4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
5 | 5 | ||
6 | import { FormFields } from '../../@types/mobx-form.types'; | 6 | import { FormFields } from '../../@types/mobx-form.types'; |
7 | import { StoresProps } from '../../@types/ferdium-components.types'; | 7 | import { StoresProps } from '../../@types/ferdium-components.types'; |
@@ -314,10 +314,10 @@ const messages = defineMessages({ | |||
314 | }, | 314 | }, |
315 | }); | 315 | }); |
316 | 316 | ||
317 | interface EditSettingsScreenProps extends StoresProps { | 317 | interface EditSettingsScreenProps extends StoresProps, WrappedComponentProps {} |
318 | intl: any; | ||
319 | } | ||
320 | 318 | ||
319 | @inject('stores', 'actions') | ||
320 | @observer | ||
321 | class EditSettingsScreen extends Component<EditSettingsScreenProps> { | 321 | class EditSettingsScreen extends Component<EditSettingsScreenProps> { |
322 | state = { | 322 | state = { |
323 | lockedPassword: '', | 323 | lockedPassword: '', |
@@ -962,9 +962,6 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
962 | } | 962 | } |
963 | isSplitModeEnabled={this.props.stores.settings.app.splitMode} | 963 | isSplitModeEnabled={this.props.stores.settings.app.splitMode} |
964 | isTodosActivated={this.props.stores.todos.isFeatureEnabledByUser} | 964 | isTodosActivated={this.props.stores.todos.isFeatureEnabledByUser} |
965 | isUsingCustomTodoService={ | ||
966 | this.props.stores.todos.isUsingCustomTodoService | ||
967 | } | ||
968 | openProcessManager={() => this.openProcessManager()} | 965 | openProcessManager={() => this.openProcessManager()} |
969 | isOnline={app.isOnline} | 966 | isOnline={app.isOnline} |
970 | serverURL={importExportURL()} | 967 | serverURL={importExportURL()} |
@@ -974,6 +971,4 @@ class EditSettingsScreen extends Component<EditSettingsScreenProps> { | |||
974 | } | 971 | } |
975 | } | 972 | } |
976 | 973 | ||
977 | export default injectIntl( | 974 | export default injectIntl(EditSettingsScreen); |
978 | inject('stores', 'actions')(observer(EditSettingsScreen)), | ||
979 | ); | ||
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.tsx index acba5a90d..e20f7641b 100644 --- a/src/features/basicAuth/Component.js +++ b/src/features/basicAuth/Component.tsx | |||
@@ -1,17 +1,14 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, FormEvent, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import injectSheet, { WithStylesProps } from 'react-jss'; |
3 | import injectSheet from 'react-jss'; | ||
4 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
5 | import { defineMessages, injectIntl } from 'react-intl'; | 4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
6 | import classnames from 'classnames'; | 5 | import classnames from 'classnames'; |
7 | 6 | import { noop } from 'lodash'; | |
8 | import Modal from '../../components/ui/Modal'; | 7 | import Modal from '../../components/ui/Modal'; |
9 | import Input from '../../components/ui/Input'; | 8 | import Input from '../../components/ui/input/index'; |
10 | import Button from '../../components/ui/button'; | 9 | import Button from '../../components/ui/button'; |
11 | |||
12 | import { state, resetState, sendCredentials, cancelLogin } from './store'; | 10 | import { state, resetState, sendCredentials, cancelLogin } from './store'; |
13 | import Form from './Form'; | 11 | import Form from './Form'; |
14 | |||
15 | import styles from './styles'; | 12 | import styles from './styles'; |
16 | import globalMessages from '../../i18n/globalMessages'; | 13 | import globalMessages from '../../i18n/globalMessages'; |
17 | import { H1 } from '../../components/ui/headline'; | 14 | import { H1 } from '../../components/ui/headline'; |
@@ -23,33 +20,31 @@ const messages = defineMessages({ | |||
23 | }, | 20 | }, |
24 | }); | 21 | }); |
25 | 22 | ||
26 | class BasicAuthModal extends Component { | 23 | interface IProps |
27 | static propTypes = { | 24 | extends WithStylesProps<typeof styles>, |
28 | classes: PropTypes.object.isRequired, | 25 | WrappedComponentProps {} |
29 | }; | ||
30 | 26 | ||
31 | submit(e) { | 27 | @observer |
28 | class BasicAuthModal extends Component<IProps> { | ||
29 | submit(e: FormEvent<HTMLFormElement>): void { | ||
32 | e.preventDefault(); | 30 | e.preventDefault(); |
33 | |||
34 | const values = Form.values(); | 31 | const values = Form.values(); |
35 | |||
36 | sendCredentials(values.user, values.password); | 32 | sendCredentials(values.user, values.password); |
37 | resetState(); | 33 | resetState(); |
38 | } | 34 | } |
39 | 35 | ||
40 | cancel() { | 36 | cancel(): void { |
41 | cancelLogin(); | 37 | cancelLogin(); |
42 | this.close(); | 38 | this.close(); |
43 | } | 39 | } |
44 | 40 | ||
45 | close() { | 41 | close(): void { |
46 | resetState(); | 42 | resetState(); |
47 | state.isModalVisible = false; | 43 | state.isModalVisible = false; |
48 | } | 44 | } |
49 | 45 | ||
50 | render() { | 46 | render(): ReactElement | null { |
51 | const { classes } = this.props; | 47 | const { classes } = this.props; |
52 | |||
53 | const { isModalVisible, authInfo } = state; | 48 | const { isModalVisible, authInfo } = state; |
54 | 49 | ||
55 | if (!authInfo) { | 50 | if (!authInfo) { |
@@ -76,9 +71,9 @@ class BasicAuthModal extends Component { | |||
76 | onSubmit={this.submit.bind(this)} | 71 | onSubmit={this.submit.bind(this)} |
77 | className={classnames('franz-form', classes.form)} | 72 | className={classnames('franz-form', classes.form)} |
78 | > | 73 | > |
79 | <Input field={Form.$('user')} showLabel={false} /> | 74 | <Input {...Form.$('user').bind()} showLabel={false} /> |
80 | <Input | 75 | <Input |
81 | field={Form.$('password')} | 76 | {...Form.$('password').bind()} |
82 | showLabel={false} | 77 | showLabel={false} |
83 | showPasswordToggle | 78 | showPasswordToggle |
84 | /> | 79 | /> |
@@ -89,7 +84,11 @@ class BasicAuthModal extends Component { | |||
89 | buttonType="secondary" | 84 | buttonType="secondary" |
90 | onClick={this.cancel.bind(this)} | 85 | onClick={this.cancel.bind(this)} |
91 | /> | 86 | /> |
92 | <Button type="submit" label={intl.formatMessage(messages.signIn)} /> | 87 | <Button |
88 | type="submit" | ||
89 | label={intl.formatMessage(messages.signIn)} | ||
90 | onClick={noop} | ||
91 | /> | ||
93 | </div> | 92 | </div> |
94 | </form> | 93 | </form> |
95 | </Modal> | 94 | </Modal> |
@@ -97,5 +96,5 @@ class BasicAuthModal extends Component { | |||
97 | } | 96 | } |
98 | } | 97 | } |
99 | export default injectIntl( | 98 | export default injectIntl( |
100 | injectSheet(styles, { injectTheme: true })(observer(BasicAuthModal)), | 99 | injectSheet(styles, { injectTheme: true })(BasicAuthModal), |
101 | ); | 100 | ); |
diff --git a/src/features/basicAuth/store.ts b/src/features/basicAuth/store.ts index e0ae8ba17..0fc289916 100644 --- a/src/features/basicAuth/store.ts +++ b/src/features/basicAuth/store.ts | |||
@@ -3,7 +3,17 @@ import { ipcRenderer } from 'electron'; | |||
3 | 3 | ||
4 | const debug = require('../../preload-safe-debug')('Ferdium:feature:basicAuth'); | 4 | const debug = require('../../preload-safe-debug')('Ferdium:feature:basicAuth'); |
5 | 5 | ||
6 | const defaultState = { | 6 | interface IAuthInfo { |
7 | host: string; | ||
8 | port: number; | ||
9 | } | ||
10 | interface IDefaultState { | ||
11 | isModalVisible: boolean; | ||
12 | service: null; | ||
13 | authInfo: IAuthInfo | null; | ||
14 | } | ||
15 | |||
16 | const defaultState: IDefaultState = { | ||
7 | isModalVisible: true, | 17 | isModalVisible: true, |
8 | service: null, | 18 | service: null, |
9 | authInfo: null, | 19 | authInfo: null, |
diff --git a/src/features/publishDebugInfo/Component.tsx b/src/features/publishDebugInfo/Component.tsx index e265902dd..3c6729ba0 100644 --- a/src/features/publishDebugInfo/Component.tsx +++ b/src/features/publishDebugInfo/Component.tsx | |||
@@ -163,7 +163,6 @@ class PublishDebugLogModal extends Component<IProps, IState> { | |||
163 | <p className={classes.info}> | 163 | <p className={classes.info}> |
164 | {intl.formatMessage(messages.published)} | 164 | {intl.formatMessage(messages.published)} |
165 | </p> | 165 | </p> |
166 | {/* TODO - [TS DEBT] need to check if <Input/> take readOnly and use it */} | ||
167 | <Input showLabel={false} value={`${DEBUG_API}/${log}`} readOnly /> | 166 | <Input showLabel={false} value={`${DEBUG_API}/${log}`} readOnly /> |
168 | </> | 167 | </> |
169 | )} | 168 | )} |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 63face219..c34106044 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -62,7 +62,7 @@ | |||
62 | "locked.password.label": "Password", | 62 | "locked.password.label": "Password", |
63 | "locked.submit.label": "Unlock", | 63 | "locked.submit.label": "Unlock", |
64 | "locked.touchId": "Unlock with Touch ID", | 64 | "locked.touchId": "Unlock with Touch ID", |
65 | "locked.touchIdPrompt": "unlock via Touch ID", | 65 | "locked.touchIdPrompt": "Unlock with Touch ID", |
66 | "locked.unlockWithPassword": "Unlock with Password", | 66 | "locked.unlockWithPassword": "Unlock with Password", |
67 | "login.changeServer": "Change here!", | 67 | "login.changeServer": "Change here!", |
68 | "login.changeServerMessage": "You are using {serverNameParse} Server, do you want to switch?", | 68 | "login.changeServerMessage": "You are using {serverNameParse} Server, do you want to switch?", |
@@ -448,6 +448,7 @@ | |||
448 | "sidebar.openWorkspaceDrawer": "Open workspace drawer", | 448 | "sidebar.openWorkspaceDrawer": "Open workspace drawer", |
449 | "sidebar.splitModeToggle": "Split Mode Toggle", | 449 | "sidebar.splitModeToggle": "Split Mode Toggle", |
450 | "sidebar.unmuteApp": "Enable notifications & audio", | 450 | "sidebar.unmuteApp": "Enable notifications & audio", |
451 | "signup.company.label": "Company", | ||
451 | "signup.email.label": "Email address", | 452 | "signup.email.label": "Email address", |
452 | "signup.emailDuplicate": "A user with that email address already exists", | 453 | "signup.emailDuplicate": "A user with that email address already exists", |
453 | "signup.firstname.label": "First Name", | 454 | "signup.firstname.label": "First Name", |