import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { observer } from 'mobx-react'; import { Link } from 'react-router'; import { defineMessages, intlShape } from 'react-intl'; import normalizeUrl from 'normalize-url'; import Form from '../../../lib/Form'; import User from '../../../models/User'; import Recipe from '../../../models/Recipe'; import Service from '../../../models/Service'; import Tabs, { TabItem } from '../../ui/Tabs'; import Input from '../../ui/Input'; import Toggle from '../../ui/Toggle'; import Button from '../../ui/Button'; import ImageUpload from '../../ui/ImageUpload'; import Select from '../../ui/Select'; import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; import { serviceLimitStore } from '../../../features/serviceLimit'; const messages = defineMessages({ saveService: { id: 'settings.service.form.saveButton', defaultMessage: '!!!Save service', }, deleteService: { id: 'settings.service.form.deleteButton', defaultMessage: '!!!Delete Service', }, openDarkmodeCss: { id: 'settings.service.form.openDarkmodeCss', defaultMessage: '!!!Open darkmode.css', }, availableServices: { id: 'settings.service.form.availableServices', defaultMessage: '!!!Available services', }, yourServices: { id: 'settings.service.form.yourServices', defaultMessage: '!!!Your services', }, addServiceHeadline: { id: 'settings.service.form.addServiceHeadline', defaultMessage: '!!!Add {name}', }, editServiceHeadline: { id: 'settings.service.form.editServiceHeadline', defaultMessage: '!!!Edit {name}', }, tabHosted: { id: 'settings.service.form.tabHosted', defaultMessage: '!!!Hosted', }, tabOnPremise: { id: 'settings.service.form.tabOnPremise', defaultMessage: '!!!Self hosted ⭐️', }, useHostedService: { id: 'settings.service.form.useHostedService', defaultMessage: '!!!Use the hosted {name} service.', }, customUrlValidationError: { id: 'settings.service.form.customUrlValidationError', defaultMessage: '!!!Could not validate custom {name} server.', }, customUrlPremiumInfo: { id: 'settings.service.form.customUrlPremiumInfo', defaultMessage: '!!!To add self hosted services, you need a Ferdi Premium Supporter Account.', }, customUrlUpgradeAccount: { id: 'settings.service.form.customUrlUpgradeAccount', defaultMessage: '!!!Upgrade your account', }, indirectMessageInfo: { id: 'settings.service.form.indirectMessageInfo', defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', }, isMutedInfo: { id: 'settings.service.form.isMutedInfo', defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', }, headlineNotifications: { id: 'settings.service.form.headlineNotifications', defaultMessage: '!!!Notifications', }, headlineBadges: { id: 'settings.service.form.headlineBadges', defaultMessage: '!!!Unread message badges', }, headlineGeneral: { id: 'settings.service.form.headlineGeneral', defaultMessage: '!!!General', }, iconDelete: { id: 'settings.service.form.iconDelete', defaultMessage: '!!!Delete', }, iconUpload: { id: 'settings.service.form.iconUpload', defaultMessage: '!!!Drop your image, or click here', }, headlineProxy: { id: 'settings.service.form.proxy.headline', defaultMessage: '!!!HTTP/HTTPS Proxy Settings', }, proxyRestartInfo: { id: 'settings.service.form.proxy.restartInfo', defaultMessage: '!!!Please restart Ferdi after changing proxy Settings.', }, proxyInfo: { id: 'settings.service.form.proxy.info', defaultMessage: '!!!Proxy settings will not be synchronized with the Ferdi servers.', }, }); export default @observer class EditServiceForm extends Component { static propTypes = { recipe: PropTypes.instanceOf(Recipe).isRequired, service(props, propName) { if (props.action === 'edit' && !(props[propName] instanceof Service)) { return new Error(`'${propName}'' is expected to be of type 'Service' when editing a Service`); } return null; }, user: PropTypes.instanceOf(User).isRequired, action: PropTypes.string.isRequired, form: PropTypes.instanceOf(Form).isRequired, onSubmit: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, openDarkmodeCss: PropTypes.func.isRequired, isOpeningDarkModeCss: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired, isProxyFeatureEnabled: PropTypes.bool.isRequired, isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired, isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, }; static defaultProps = { service: {}, }; static contextTypes = { intl: intlShape, }; state = { isValidatingCustomUrl: false, } submit(e) { const { recipe } = this.props; e.preventDefault(); this.props.form.submit({ onSuccess: async (form) => { const values = form.values(); let isValid = true; const { files } = form.$('customIcon'); if (files) { values.iconFile = files[0]; } if (recipe.validateUrl && values.customUrl) { this.setState({ isValidatingCustomUrl: true }); try { values.customUrl = normalizeUrl(values.customUrl, { stripWWW: false }); isValid = await recipe.validateUrl(values.customUrl); } catch (err) { console.warn('ValidateURL', err); isValid = false; } } if (isValid) { this.props.onSubmit(values); } else { form.invalidate('url-validation-error'); } this.setState({ isValidatingCustomUrl: false }); }, onError: () => {}, }); } render() { const { recipe, service, action, user, form, isSaving, isDeleting, onDelete, openDarkmodeCss, isOpeningDarkModeCss, isProxyFeatureEnabled, isServiceProxyIncludedInCurrentPlan, isSpellcheckerIncludedInCurrentPlan, } = this.props; const { intl } = this.context; const { isValidatingCustomUrl } = this.state; const deleteButton = isDeleting ? (