From 85d1aac4cd70e79d5ab64684dea09e92b17ed2c2 Mon Sep 17 00:00:00 2001
From: muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com>
Date: Tue, 1 Nov 2022 06:42:12 +0530
Subject: Transform ChangeServer components tree to typescript (#725)
---
src/components/auth/ChangeServer.jsx | 153 ---------------------
src/components/auth/ChangeServer.tsx | 150 ++++++++++++++++++++
src/components/auth/Login.tsx | 10 +-
.../settings/account/AccountDashboard.tsx | 2 +-
.../settings/recipes/RecipesDashboard.tsx | 4 +-
src/components/ui/Infobox.js | 115 ----------------
src/components/ui/Infobox.tsx | 111 +++++++++++++++
src/components/ui/Input.tsx | 21 ++-
src/components/ui/Select.js | 101 --------------
src/components/ui/Select.tsx | 112 +++++++++++++++
src/components/ui/infobox/index.tsx | 88 ++++++------
src/components/ui/select/index.tsx | 151 ++++++++++----------
12 files changed, 513 insertions(+), 505 deletions(-)
delete mode 100644 src/components/auth/ChangeServer.jsx
create mode 100644 src/components/auth/ChangeServer.tsx
delete mode 100644 src/components/ui/Infobox.js
create mode 100644 src/components/ui/Infobox.tsx
delete mode 100644 src/components/ui/Select.js
create mode 100644 src/components/ui/Select.tsx
(limited to 'src/components')
diff --git a/src/components/auth/ChangeServer.jsx b/src/components/auth/ChangeServer.jsx
deleted file mode 100644
index 8f4b85fbb..000000000
--- a/src/components/auth/ChangeServer.jsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { Component } from 'react';
-import PropTypes from 'prop-types';
-import { observer } from 'mobx-react';
-import { defineMessages, injectIntl } from 'react-intl';
-import { mdiArrowLeftCircle } from '@mdi/js';
-import Form from '../../lib/Form';
-import Input from '../ui/Input';
-import Select from '../ui/Select';
-import Button from '../ui/button';
-import Link from '../ui/Link';
-import Infobox from '../ui/Infobox';
-import { url, required } from '../../helpers/validation-helpers';
-import { LIVE_FERDIUM_API, LIVE_FRANZ_API } from '../../config';
-import globalMessages from '../../i18n/globalMessages';
-import { H1 } from '../ui/headline';
-import Icon from '../ui/icon';
-
-const messages = defineMessages({
- headline: {
- id: 'changeserver.headline',
- defaultMessage: 'Change server',
- },
- label: {
- id: 'changeserver.label',
- defaultMessage: 'Server',
- },
- warning: {
- id: 'changeserver.warning',
- defaultMessage: 'Extra settings offered by Ferdium will not be saved',
- },
- customServerLabel: {
- id: 'changeserver.customServerLabel',
- defaultMessage: 'Custom server',
- },
- urlError: {
- id: 'changeserver.urlError',
- defaultMessage: 'Enter a valid URL',
- },
-});
-
-class ChangeServer extends Component {
- static propTypes = {
- onSubmit: PropTypes.func.isRequired,
- server: PropTypes.string.isRequired,
- };
-
- ferdiumServer = LIVE_FERDIUM_API;
-
- franzServer = LIVE_FRANZ_API;
-
- defaultServers = [this.ferdiumServer, this.franzServer];
-
- form = (() => {
- const { intl } = this.props;
- return new Form(
- {
- fields: {
- server: {
- label: intl.formatMessage(messages.label),
- value: this.props.server,
- options: [
- { value: this.ferdiumServer, label: 'Ferdium (Default)' },
- { value: this.franzServer, label: 'Franz' },
- {
- value: this.defaultServers.includes(this.props.server)
- ? ''
- : this.props.server,
- label: 'Custom',
- },
- ],
- },
- customServer: {
- label: intl.formatMessage(messages.customServerLabel),
- value: '',
- validators: [url, required],
- },
- },
- },
- intl,
- );
- })();
-
- componentDidMount() {
- if (this.defaultServers.includes(this.props.server)) {
- this.form.$('server').value = this.props.server;
- } else {
- this.form.$('server').value = '';
- this.form.$('customServer').value = this.props.server;
- }
- }
-
- submit(e) {
- e.preventDefault();
- this.form.submit({
- onSuccess: form => {
- if (!this.defaultServers.includes(form.values().server)) {
- form.$('server').onChange(form.values().customServer);
- }
- this.props.onSubmit(form.values());
- },
- onError: form => {
- if (this.defaultServers.includes(form.values().server)) {
- this.props.onSubmit(form.values());
- }
- },
- });
- }
-
- render() {
- const { form } = this;
- const { intl } = this.props;
- return (
-
- );
- }
-}
-
-export default injectIntl(observer(ChangeServer));
diff --git a/src/components/auth/ChangeServer.tsx b/src/components/auth/ChangeServer.tsx
new file mode 100644
index 000000000..d8509f599
--- /dev/null
+++ b/src/components/auth/ChangeServer.tsx
@@ -0,0 +1,150 @@
+import { Component, FormEvent, ReactElement } from 'react';
+import { observer } from 'mobx-react';
+import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
+import { mdiArrowLeftCircle } from '@mdi/js';
+import { noop } from 'lodash';
+import Form from '../../lib/Form';
+import Input from '../ui/Input';
+import Select from '../ui/Select';
+import Button from '../ui/button';
+import Link from '../ui/Link';
+import Infobox from '../ui/Infobox';
+import { url, required } from '../../helpers/validation-helpers';
+import { LIVE_FERDIUM_API, LIVE_FRANZ_API } from '../../config';
+import globalMessages from '../../i18n/globalMessages';
+import { H1 } from '../ui/headline';
+import Icon from '../ui/icon';
+
+const messages = defineMessages({
+ headline: {
+ id: 'changeserver.headline',
+ defaultMessage: 'Change server',
+ },
+ label: {
+ id: 'changeserver.label',
+ defaultMessage: 'Server',
+ },
+ warning: {
+ id: 'changeserver.warning',
+ defaultMessage: 'Extra settings offered by Ferdium will not be saved',
+ },
+ customServerLabel: {
+ id: 'changeserver.customServerLabel',
+ defaultMessage: 'Custom server',
+ },
+ urlError: {
+ id: 'changeserver.urlError',
+ defaultMessage: 'Enter a valid URL',
+ },
+});
+
+interface IProps extends WrappedComponentProps {
+ onSubmit: (...args: any[]) => void;
+ server: string;
+}
+
+@observer
+class ChangeServer extends Component {
+ ferdiumServer: string = LIVE_FERDIUM_API;
+
+ franzServer: string = LIVE_FRANZ_API;
+
+ defaultServers: string[] = [LIVE_FERDIUM_API, LIVE_FRANZ_API];
+
+ form: Form;
+
+ constructor(props: IProps) {
+ super(props);
+
+ this.form = new Form({
+ fields: {
+ server: {
+ label: this.props.intl.formatMessage(messages.label),
+ value: this.props.server,
+ options: [
+ { value: this.ferdiumServer, label: 'Ferdium (Default)' },
+ { value: this.franzServer, label: 'Franz' },
+ {
+ value: this.defaultServers.includes(this.props.server)
+ ? ''
+ : this.props.server,
+ label: 'Custom',
+ },
+ ],
+ },
+ customServer: {
+ label: this.props.intl.formatMessage(messages.customServerLabel),
+ placeholder: this.props.intl.formatMessage(
+ messages.customServerLabel,
+ ),
+ value: '',
+ validators: [url, required],
+ },
+ },
+ });
+ }
+
+ componentDidMount(): void {
+ if (this.defaultServers.includes(this.props.server)) {
+ this.form.$('server').value = this.props.server;
+ } else {
+ this.form.$('server').value = '';
+ this.form.$('customServer').value = this.props.server;
+ }
+ }
+
+ submit(e: FormEvent): void {
+ e.preventDefault();
+ this.form.submit({
+ onSuccess: form => {
+ if (!this.defaultServers.includes(form.values().server)) {
+ form.$('server').onChange(form.values().customServer);
+ }
+ this.props.onSubmit(form.values());
+ },
+ onError: form => {
+ if (this.defaultServers.includes(form.values().server)) {
+ this.props.onSubmit(form.values());
+ }
+ },
+ });
+ }
+
+ render(): ReactElement {
+ const { form } = this;
+ const { intl } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default injectIntl(ChangeServer);
diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx
index 65381fe04..66a567fe4 100644
--- a/src/components/auth/Login.tsx
+++ b/src/components/auth/Login.tsx
@@ -1,5 +1,5 @@
import { Component, FormEvent, ReactElement } from 'react';
-import { observer, inject } from 'mobx-react';
+import { observer } from 'mobx-react';
import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
import { mdiArrowLeftCircle } from '@mdi/js';
import { noop } from 'lodash';
@@ -13,10 +13,7 @@ import Input from '../ui/Input';
import Button from '../ui/button';
import Link from '../ui/Link';
import { H1 } from '../ui/headline';
-import {
- GlobalError,
- StoresProps,
-} from '../../@types/ferdium-components.types';
+import { GlobalError } from '../../@types/ferdium-components.types';
const messages = defineMessages({
headline: {
@@ -65,7 +62,7 @@ const messages = defineMessages({
},
});
-interface IProps extends Partial, WrappedComponentProps {
+interface IProps extends WrappedComponentProps {
onSubmit: (...args: any[]) => void;
isSubmitting: boolean;
isTokenExpired: boolean;
@@ -76,7 +73,6 @@ interface IProps extends Partial, WrappedComponentProps {
error: GlobalError;
}
-@inject('actions')
@observer
class Login extends Component {
form: Form;
diff --git a/src/components/settings/account/AccountDashboard.tsx b/src/components/settings/account/AccountDashboard.tsx
index b0debdaf2..163b0a160 100644
--- a/src/components/settings/account/AccountDashboard.tsx
+++ b/src/components/settings/account/AccountDashboard.tsx
@@ -6,7 +6,7 @@ import { H1, H2 } from '../../ui/headline';
import Loader from '../../ui/Loader';
import Button from '../../ui/button';
-import Infobox from '../../ui/infobox';
+import Infobox from '../../ui/infobox/index';
import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config';
import User from '../../../models/User';
diff --git a/src/components/settings/recipes/RecipesDashboard.tsx b/src/components/settings/recipes/RecipesDashboard.tsx
index fc687bc79..7b7ba19b1 100644
--- a/src/components/settings/recipes/RecipesDashboard.tsx
+++ b/src/components/settings/recipes/RecipesDashboard.tsx
@@ -8,7 +8,7 @@ import Button from '../../ui/button';
import Input from '../../ui/input/index';
import { H1, H2, H3 } from '../../ui/headline';
import SearchInput from '../../ui/SearchInput';
-import Infobox from '../../ui/infobox';
+import Infobox from '../../ui/infobox/index';
import RecipeItem from './RecipeItem';
import Loader from '../../ui/Loader';
import Appear from '../../ui/effects/Appear';
@@ -166,7 +166,7 @@ class RecipesDashboard extends Component {
{intl.formatMessage(messages.servicesSuccessfulAddedInfo)}
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
deleted file mode 100644
index 8fb80d87f..000000000
--- a/src/components/ui/Infobox.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import { Component } from 'react';
-import PropTypes from 'prop-types';
-import { observer } from 'mobx-react';
-import classnames from 'classnames';
-import Loader from 'react-loader';
-import { defineMessages, injectIntl } from 'react-intl';
-import { mdiAlert, mdiCheckboxMarkedCircleOutline, mdiClose } from '@mdi/js';
-import Icon from '../ui/icon';
-
-const icons = {
- 'checkbox-marked-circle-outline': mdiCheckboxMarkedCircleOutline,
- alert: mdiAlert,
-};
-
-const messages = defineMessages({
- dismiss: {
- id: 'infobox.dismiss',
- defaultMessage: 'Dismiss',
- },
-});
-
-// Can this file be merged into the './infobox/index.tsx' file?
-class Infobox extends Component {
- static propTypes = {
- // eslint-disable-next-line react/forbid-prop-types
- children: PropTypes.any.isRequired,
- icon: PropTypes.string,
- type: PropTypes.string,
- ctaOnClick: PropTypes.func,
- ctaLabel: PropTypes.string,
- ctaLoading: PropTypes.bool,
- dismissable: PropTypes.bool,
- onDismiss: PropTypes.func,
- onSeen: PropTypes.func,
- };
-
- static defaultProps = {
- icon: '',
- type: 'primary',
- dismissable: false,
- ctaOnClick: () => null,
- ctaLabel: '',
- ctaLoading: false,
- onDismiss: () => null,
- onSeen: () => null,
- };
-
- state = {
- dismissed: false,
- };
-
- componentDidMount() {
- const { onSeen } = this.props;
- if (onSeen) onSeen();
- }
-
- render() {
- const {
- children,
- icon,
- type,
- ctaLabel,
- ctaLoading,
- ctaOnClick,
- dismissable,
- onDismiss,
- } = this.props;
-
- const { intl } = this.props;
-
- if (this.state.dismissed) {
- return null;
- }
-
- return (
-
- {icon &&
}
-
{children}
- {ctaLabel && (
-
- )}
- {dismissable && (
-
- )}
-
- );
- }
-}
-
-export default injectIntl(observer(Infobox));
diff --git a/src/components/ui/Infobox.tsx b/src/components/ui/Infobox.tsx
new file mode 100644
index 000000000..1fc24816a
--- /dev/null
+++ b/src/components/ui/Infobox.tsx
@@ -0,0 +1,111 @@
+import { Component, MouseEventHandler, ReactElement, ReactNode } from 'react';
+import classnames from 'classnames';
+import Loader from 'react-loader';
+import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
+import { mdiAlert, mdiCheckboxMarkedCircleOutline, mdiClose } from '@mdi/js';
+import { noop } from 'lodash';
+import { observer } from 'mobx-react';
+import Icon from './icon';
+
+const icons = {
+ 'checkbox-marked-circle-outline': mdiCheckboxMarkedCircleOutline,
+ alert: mdiAlert,
+};
+
+const messages = defineMessages({
+ dismiss: {
+ id: 'infobox.dismiss',
+ defaultMessage: 'Dismiss',
+ },
+});
+
+interface IProps extends WrappedComponentProps {
+ children: ReactNode;
+ icon?: string;
+ type?: string;
+ ctaLabel?: string;
+ ctaLoading?: boolean;
+ dismissible?: boolean;
+ ctaOnClick?: MouseEventHandler;
+ onDismiss?: () => void;
+ onSeen?: () => void;
+}
+
+interface IState {
+ dismissed: boolean;
+}
+
+// Can this file be merged into the './infobox/index.tsx' file?
+@observer
+class Infobox extends Component {
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ dismissed: false,
+ };
+ }
+
+ componentDidMount(): void {
+ const { onSeen = noop } = this.props;
+ onSeen();
+ }
+
+ render(): ReactElement | null {
+ const {
+ children,
+ icon = '',
+ type = 'primary',
+ dismissible = false,
+ ctaOnClick = noop,
+ ctaLabel = '',
+ ctaLoading = false,
+ onDismiss = noop,
+ intl,
+ } = this.props;
+
+ if (this.state.dismissed) {
+ return null;
+ }
+
+ return (
+
+ {icon &&
}
+
{children}
+ {ctaLabel && (
+
+ )}
+ {dismissible && (
+
+ )}
+
+ );
+ }
+}
+
+export default injectIntl(Infobox);
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx
index 78b3a9200..c22dc5838 100644
--- a/src/components/ui/Input.tsx
+++ b/src/components/ui/Input.tsx
@@ -1,8 +1,16 @@
-import { ChangeEvent, Component, createRef, RefObject } from 'react';
+import {
+ ChangeEvent,
+ ChangeEventHandler,
+ Component,
+ createRef,
+ ReactElement,
+ RefObject,
+} from 'react';
import { observer } from 'mobx-react';
import classnames from 'classnames';
import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl';
import { mdiEye, mdiEyeOff } from '@mdi/js';
+import { noop } from 'lodash';
import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers';
import Icon from './icon';
import { Field } from '../../@types/mobx-form.types';
@@ -23,6 +31,8 @@ interface IProps extends WrappedComponentProps {
scorePassword?: boolean;
prefix?: string;
suffix?: string;
+ placeholder?: string;
+ onChange?: ChangeEventHandler;
}
interface IState {
@@ -53,14 +63,17 @@ class Input extends Component {
}
onChange(e: ChangeEvent): void {
- const { field, scorePassword } = this.props;
+ const { field, scorePassword, onChange = noop } = this.props;
if (field.onChange) {
+ onChange(e);
field.onChange(e);
}
if (scorePassword) {
- this.setState({ passwordScore: scorePasswordFunc(field.value) });
+ this.setState({
+ passwordScore: scorePasswordFunc(field.value as string),
+ });
}
}
@@ -70,7 +83,7 @@ class Input extends Component {
}
}
- render() {
+ render(): ReactElement {
const {
field,
className = null,
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js
deleted file mode 100644
index ca5ec9964..000000000
--- a/src/components/ui/Select.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { createRef, Component } from 'react';
-import PropTypes from 'prop-types';
-import { observer } from 'mobx-react';
-import { Field } from 'mobx-react-form';
-import classnames from 'classnames';
-
-// Can this file be merged into the './select/index.tsx' file?
-class Select extends Component {
- static propTypes = {
- field: PropTypes.instanceOf(Field).isRequired,
- className: PropTypes.string,
- showLabel: PropTypes.bool,
- disabled: PropTypes.bool,
- multiple: PropTypes.bool,
- };
-
- static defaultProps = {
- className: null,
- showLabel: true,
- disabled: false,
- multiple: false,
- };
-
- constructor(props) {
- super(props);
-
- this.element = createRef();
- }
-
- multipleChange() {
- const element = this.element.current;
-
- const result = [];
- const options = element && element.options;
-
- for (const option of options) {
- if (option.selected) {
- result.push(option.value || option.text);
- }
- }
-
- const { field } = this.props;
- field.value = result;
- }
-
- render() {
- const { field, className, showLabel, disabled, multiple } = this.props;
-
- let selected = field.value;
-
- if (multiple) {
- if (typeof field.value === 'string' && field.value.slice(0, 1) === '[') {
- // Value is JSON encoded
- selected = JSON.parse(field.value);
- } else if (typeof field.value === 'object') {
- selected = field.value;
- } else {
- selected = [field.value];
- }
- }
-
- return (
-
- {field.label && showLabel && (
-
- )}
-
- {field.error &&
{field.error}
}
-
- );
- }
-}
-
-export default observer(Select);
diff --git a/src/components/ui/Select.tsx b/src/components/ui/Select.tsx
new file mode 100644
index 000000000..1d69a9acf
--- /dev/null
+++ b/src/components/ui/Select.tsx
@@ -0,0 +1,112 @@
+import {
+ createRef,
+ Component,
+ ReactElement,
+ RefObject,
+ ChangeEvent,
+} from 'react';
+import { observer } from 'mobx-react';
+import classnames from 'classnames';
+import { Field } from '../../@types/mobx-form.types';
+
+interface IProps {
+ field: Field;
+ className?: string;
+ showLabel?: boolean;
+ disabled?: boolean;
+ multiple?: boolean;
+}
+
+// Can this file be merged into the './select/index.tsx' file?
+@observer
+class Select extends Component {
+ private element: RefObject =
+ createRef();
+
+ constructor(props: IProps) {
+ super(props);
+ }
+
+ multipleChange(e: ChangeEvent): void {
+ e.preventDefault();
+ if (!this.element.current) {
+ return;
+ }
+ const result: string[] = [];
+ const { options } = this.element.current;
+ for (const option of options) {
+ if (option.selected && (option.value || option.text)) {
+ result.push(option.value || option.text);
+ }
+ }
+
+ const { field } = this.props;
+ field.value = result;
+ }
+
+ render(): ReactElement {
+ const {
+ field,
+ className = null,
+ showLabel = true,
+ disabled = false,
+ multiple = false,
+ } = this.props;
+
+ let selected = field.value;
+
+ if (multiple) {
+ if (typeof field.value === 'string' && field.value.slice(0, 1) === '[') {
+ // Value is JSON encoded
+ selected = JSON.parse(field.value);
+ } else if (typeof field.value === 'object') {
+ selected = field.value;
+ } else {
+ selected = [field.value];
+ }
+ }
+
+ return (
+
+ {field.label && showLabel && (
+
+ )}
+
+ {field.error &&
{field.error}
}
+
+ );
+ }
+}
+
+export default Select;
diff --git a/src/components/ui/infobox/index.tsx b/src/components/ui/infobox/index.tsx
index ad59ea81e..3b878a9de 100644
--- a/src/components/ui/infobox/index.tsx
+++ b/src/components/ui/infobox/index.tsx
@@ -1,32 +1,14 @@
import { mdiClose } from '@mdi/js';
import classnames from 'classnames';
-import { Component, ReactNode } from 'react';
-import injectStyle, { WithStylesProps } from 'react-jss';
-
+import { noop } from 'lodash';
+import { Component, ReactElement, ReactNode } from 'react';
+import withStyles, { WithStylesProps } from 'react-jss';
import { Theme } from '../../../themes';
import Icon from '../icon';
-interface IProps extends WithStylesProps {
- children: ReactNode;
- icon?: string;
- type?: string;
- dismissable?: boolean;
- ctaLabel?: string;
-
- className?: string;
- onDismiss?: () => void;
- onUnmount?: () => void;
- ctaOnClick?: () => void;
-}
-
-interface IState {
- isDismissing: boolean;
- dismissed: boolean;
-}
-
const buttonStyles = (theme: Theme) => {
const styles = {};
- Object.keys(theme.styleTypes).map(style => {
+ for (const style of Object.keys(theme.styleTypes)) {
Object.assign(styles, {
[style]: {
background: theme.styleTypes[style].accent,
@@ -38,7 +20,7 @@ const buttonStyles = (theme: Theme) => {
},
},
});
- });
+ }
return styles;
};
@@ -108,23 +90,35 @@ const styles = (theme: Theme) => ({
...buttonStyles(theme),
});
+interface IProps extends WithStylesProps {
+ children: ReactNode;
+ icon?: string;
+ type?: string;
+ dismissible?: boolean;
+ ctaLabel?: string;
+ className?: string;
+ onDismiss?: () => void;
+ onUnmount?: () => void;
+ ctaOnClick?: () => void;
+}
+
+interface IState {
+ isDismissing: boolean;
+ dismissed: boolean;
+}
+
class InfoboxComponent extends Component {
- public static defaultProps = {
- type: 'primary',
- dismissable: false,
- ctaOnClick: () => {},
- onDismiss: () => {},
- ctaLabel: '',
- className: '',
- };
-
- state = {
- isDismissing: false,
- dismissed: false,
- };
-
- dismiss() {
- const { onDismiss } = this.props;
+ constructor(props: IProps) {
+ super(props);
+
+ this.state = {
+ isDismissing: false,
+ dismissed: false,
+ };
+ }
+
+ dismiss(): void {
+ const { onDismiss = noop } = this.props;
this.setState({
isDismissing: true,
@@ -146,16 +140,16 @@ class InfoboxComponent extends Component {
if (onUnmount) onUnmount();
}
- render() {
+ render(): ReactElement | null {
const {
classes,
children,
icon,
- type,
- ctaLabel,
- ctaOnClick,
- dismissable,
- className,
+ type = 'primary',
+ dismissible = false,
+ ctaOnClick = noop,
+ ctaLabel = '',
+ className = '',
} = this.props;
const { isDismissing, dismissed } = this.state;
@@ -186,7 +180,7 @@ class InfoboxComponent extends Component {
{ctaLabel}
)}
- {dismissable && (
+ {dismissible && (