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 Slider from '../../ui/Slider'; 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'; import { isMac } from '../../../environment'; 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', }, openUserCss: { id: 'settings.service.form.openUserCss', defaultMessage: '!!!Open user.css', }, openUserJs: { id: 'settings.service.form.openUserJs', defaultMessage: '!!!Open user.js', }, recipeFileInfo: { id: 'settings.service.form.recipeFileInfo', defaultMessage: '!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.', }, 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', }, disableHibernationInfo: { id: 'settings.service.form.disableHibernationInfo', defaultMessage: '!!!You currently have hibernation enabled but you can disable hibernation for individual services using this option.', }, 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', }, headlineDarkReaderSettings: { id: 'settings.service.form.headlineDarkReaderSettings', defaultMessage: '!!!Dark Reader Settings', }, 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, openRecipeFile: PropTypes.func.isRequired, isSaving: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired, isProxyFeatureEnabled: PropTypes.bool.isRequired, isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired, isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, isHibernationFeatureActive: 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, removeTrailingSlash: 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, openRecipeFile, isProxyFeatureEnabled, isServiceProxyIncludedInCurrentPlan, isSpellcheckerIncludedInCurrentPlan, isHibernationFeatureActive, } = this.props; const { intl } = this.context; const { isValidatingCustomUrl } = this.state; const deleteButton = isDeleting ? (