diff options
Diffstat (limited to 'src/components/settings/services/EditServiceForm.js')
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js new file mode 100644 index 000000000..fac0f6b9a --- /dev/null +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -0,0 +1,277 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { Link } from 'react-router'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import normalizeUrl from 'normalize-url'; | ||
7 | |||
8 | import Form from '../../../lib/Form'; | ||
9 | import User from '../../../models/User'; | ||
10 | import Recipe from '../../../models/Recipe'; | ||
11 | import Service from '../../../models/Service'; | ||
12 | import Tabs, { TabItem } from '../../ui/Tabs'; | ||
13 | import Input from '../../ui/Input'; | ||
14 | import Toggle from '../../ui/Toggle'; | ||
15 | import Button from '../../ui/Button'; | ||
16 | |||
17 | const messages = defineMessages({ | ||
18 | saveService: { | ||
19 | id: 'settings.service.form.saveButton', | ||
20 | defaultMessage: '!!!Save service', | ||
21 | }, | ||
22 | deleteService: { | ||
23 | id: 'settings.service.form.deleteButton', | ||
24 | defaultMessage: '!!!Delete Service', | ||
25 | }, | ||
26 | availableServices: { | ||
27 | id: 'settings.service.form.availableServices', | ||
28 | defaultMessage: '!!!Available services', | ||
29 | }, | ||
30 | yourServices: { | ||
31 | id: 'settings.service.form.yourServices', | ||
32 | defaultMessage: '!!!Your services', | ||
33 | }, | ||
34 | addServiceHeadline: { | ||
35 | id: 'settings.service.form.addServiceHeadline', | ||
36 | defaultMessage: '!!!Add {name}', | ||
37 | }, | ||
38 | editServiceHeadline: { | ||
39 | id: 'settings.service.form.editServiceHeadline', | ||
40 | defaultMessage: '!!!Edit {name}', | ||
41 | }, | ||
42 | tabHosted: { | ||
43 | id: 'settings.service.form.tabHosted', | ||
44 | defaultMessage: '!!!Hosted', | ||
45 | }, | ||
46 | tabOnPremise: { | ||
47 | id: 'settings.service.form.tabOnPremise', | ||
48 | defaultMessage: '!!!Self hosted ⭐️', | ||
49 | }, | ||
50 | customUrlValidationError: { | ||
51 | id: 'settings.service.form.customUrlValidationError', | ||
52 | defaultMessage: '!!!Could not validate custom {name} server.', | ||
53 | }, | ||
54 | customUrlPremiumInfo: { | ||
55 | id: 'settings.service.form.customUrlPremiumInfo', | ||
56 | defaultMessage: '!!!To add self hosted services, you need a Franz Premium Supporter Account.', | ||
57 | }, | ||
58 | customUrlUpgradeAccount: { | ||
59 | id: 'settings.service.form.customUrlUpgradeAccount', | ||
60 | defaultMessage: '!!!Upgrade your account', | ||
61 | }, | ||
62 | indirectMessageInfo: { | ||
63 | id: 'settings.service.form.indirectMessageInfo', | ||
64 | defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', // eslint-disable-line | ||
65 | }, | ||
66 | }); | ||
67 | |||
68 | @observer | ||
69 | export default class EditServiceForm extends Component { | ||
70 | static propTypes = { | ||
71 | recipe: PropTypes.instanceOf(Recipe).isRequired, | ||
72 | // service: PropTypes.oneOfType([ | ||
73 | // PropTypes.object, | ||
74 | // PropTypes.instanceOf(Service), | ||
75 | // ]), | ||
76 | service(props, propName) { | ||
77 | if (props.action === 'edit' && !(props[propName] instanceof Service)) { | ||
78 | return new Error(`'${propName}'' is expected to be of type 'Service' | ||
79 | when editing a Service`); | ||
80 | } | ||
81 | |||
82 | return null; | ||
83 | }, | ||
84 | user: PropTypes.instanceOf(User).isRequired, | ||
85 | action: PropTypes.string.isRequired, | ||
86 | form: PropTypes.instanceOf(Form).isRequired, | ||
87 | onSubmit: PropTypes.func.isRequired, | ||
88 | onDelete: PropTypes.func.isRequired, | ||
89 | isSaving: PropTypes.bool.isRequired, | ||
90 | isDeleting: PropTypes.bool.isRequired, | ||
91 | }; | ||
92 | |||
93 | static defaultProps = { | ||
94 | service: {}, | ||
95 | }; | ||
96 | static contextTypes = { | ||
97 | intl: intlShape, | ||
98 | }; | ||
99 | |||
100 | state = { | ||
101 | isValidatingCustomUrl: false, | ||
102 | } | ||
103 | |||
104 | submit(e) { | ||
105 | const { recipe } = this.props; | ||
106 | |||
107 | e.preventDefault(); | ||
108 | this.props.form.submit({ | ||
109 | onSuccess: async (form) => { | ||
110 | const values = form.values(); | ||
111 | |||
112 | let isValid = true; | ||
113 | |||
114 | if (recipe.validateUrl && values.customUrl) { | ||
115 | this.setState({ isValidatingCustomUrl: true }); | ||
116 | try { | ||
117 | values.customUrl = normalizeUrl(values.customUrl); | ||
118 | isValid = await recipe.validateUrl(values.customUrl); | ||
119 | } catch (err) { | ||
120 | console.warn('ValidateURL', err); | ||
121 | isValid = false; | ||
122 | } | ||
123 | } | ||
124 | |||
125 | if (isValid) { | ||
126 | this.props.onSubmit(values); | ||
127 | } else { | ||
128 | form.invalidate('url-validation-error'); | ||
129 | } | ||
130 | |||
131 | this.setState({ isValidatingCustomUrl: false }); | ||
132 | }, | ||
133 | onError: () => {}, | ||
134 | }); | ||
135 | } | ||
136 | |||
137 | render() { | ||
138 | const { | ||
139 | recipe, | ||
140 | service, | ||
141 | action, | ||
142 | user, | ||
143 | form, | ||
144 | isSaving, | ||
145 | isDeleting, | ||
146 | onDelete, | ||
147 | } = this.props; | ||
148 | const { intl } = this.context; | ||
149 | |||
150 | const { isValidatingCustomUrl } = this.state; | ||
151 | |||
152 | const deleteButton = isDeleting ? ( | ||
153 | <Button | ||
154 | label={intl.formatMessage(messages.deleteService)} | ||
155 | loaded={false} | ||
156 | buttonType="secondary" | ||
157 | className="settings__delete-button" | ||
158 | disabled | ||
159 | /> | ||
160 | ) : ( | ||
161 | <Button | ||
162 | buttonType="danger" | ||
163 | label={intl.formatMessage(messages.deleteService)} | ||
164 | className="settings__delete-button" | ||
165 | onClick={onDelete} | ||
166 | /> | ||
167 | ); | ||
168 | |||
169 | return ( | ||
170 | <div className="settings__main"> | ||
171 | <div className="settings__header"> | ||
172 | <span className="settings__header-item"> | ||
173 | {action === 'add' ? ( | ||
174 | <Link to="/settings/recipes"> | ||
175 | {intl.formatMessage(messages.availableServices)} | ||
176 | </Link> | ||
177 | ) : ( | ||
178 | <Link to="/settings/services"> | ||
179 | {intl.formatMessage(messages.yourServices)} | ||
180 | </Link> | ||
181 | )} | ||
182 | </span> | ||
183 | <span className="separator" /> | ||
184 | <span className="settings__header-item"> | ||
185 | {action === 'add' ? ( | ||
186 | intl.formatMessage(messages.addServiceHeadline, { | ||
187 | name: recipe.name, | ||
188 | }) | ||
189 | ) : ( | ||
190 | intl.formatMessage(messages.editServiceHeadline, { | ||
191 | name: service.name !== '' ? service.name : recipe.name, | ||
192 | }) | ||
193 | )} | ||
194 | </span> | ||
195 | </div> | ||
196 | <div className="settings__body"> | ||
197 | <form onSubmit={e => this.submit(e)} id="form"> | ||
198 | <Input field={form.$('name')} focus /> | ||
199 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | ||
200 | <Tabs | ||
201 | active={service.customUrl ? 1 : 0} | ||
202 | > | ||
203 | {recipe.hasTeamId && ( | ||
204 | <TabItem title={intl.formatMessage(messages.tabHosted)}> | ||
205 | <Input field={form.$('team')} suffix={recipe.urlInputSuffix} /> | ||
206 | </TabItem> | ||
207 | )} | ||
208 | {recipe.hasCustomUrl && ( | ||
209 | <TabItem title={intl.formatMessage(messages.tabOnPremise)}> | ||
210 | {user.isPremium ? ( | ||
211 | <div> | ||
212 | <Input field={form.$('customUrl')} /> | ||
213 | {form.error === 'url-validation-error' && ( | ||
214 | <p className="franz-form__error"> | ||
215 | {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} | ||
216 | </p> | ||
217 | )} | ||
218 | </div> | ||
219 | ) : ( | ||
220 | <div className="center premium-info"> | ||
221 | <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p> | ||
222 | <p> | ||
223 | <Link to="/settings/user" className="button"> | ||
224 | {intl.formatMessage(messages.customUrlUpgradeAccount)} | ||
225 | </Link> | ||
226 | </p> | ||
227 | </div> | ||
228 | )} | ||
229 | </TabItem> | ||
230 | )} | ||
231 | </Tabs> | ||
232 | )} | ||
233 | <div className="settings__options"> | ||
234 | <Toggle field={form.$('isNotificationEnabled')} /> | ||
235 | {recipe.hasIndirectMessages && ( | ||
236 | <div> | ||
237 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | ||
238 | <p className="settings__indirect-message-help"> | ||
239 | {intl.formatMessage(messages.indirectMessageInfo)} | ||
240 | </p> | ||
241 | </div> | ||
242 | )} | ||
243 | <Toggle field={form.$('isEnabled')} /> | ||
244 | </div> | ||
245 | {recipe.message && ( | ||
246 | <p className="settings__message"> | ||
247 | <span className="mdi mdi-information" /> | ||
248 | {recipe.message} | ||
249 | </p> | ||
250 | )} | ||
251 | </form> | ||
252 | </div> | ||
253 | <div className="settings__controls"> | ||
254 | {/* Delete Button */} | ||
255 | {action === 'edit' && deleteButton} | ||
256 | |||
257 | {/* Save Button */} | ||
258 | {isSaving || isValidatingCustomUrl ? ( | ||
259 | <Button | ||
260 | type="submit" | ||
261 | label={intl.formatMessage(messages.saveService)} | ||
262 | loaded={false} | ||
263 | buttonType="secondary" | ||
264 | disabled | ||
265 | /> | ||
266 | ) : ( | ||
267 | <Button | ||
268 | type="submit" | ||
269 | label={intl.formatMessage(messages.saveService)} | ||
270 | htmlForm="form" | ||
271 | /> | ||
272 | )} | ||
273 | </div> | ||
274 | </div> | ||
275 | ); | ||
276 | } | ||
277 | } | ||