Settings.jsx 16.6 KB
Newer Older
1
import React from 'react';
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
2
import { connect } from 'react-redux';
valzav's avatar
valzav committed
3
import tt from 'counterpart';
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
4
5
6
import * as userActions from 'app/redux/UserReducer';
import * as transactionActions from 'app/redux/TransactionReducer';
import * as appActions from 'app/redux/AppReducer';
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
7
8
9
import o2j from 'shared/clash/object2json';
import LoadingIndicator from 'app/components/elements/LoadingIndicator';
import reactForm from 'app/utils/ReactForm';
10
import UserList from 'app/components/elements/UserList';
11
import Dropzone from 'react-dropzone';
12
13

class Settings extends React.Component {
14
    constructor(props) {
15
16
17
18
        super(props);
        this.state = {
            errorMessage: '',
            successMessage: '',
19
            progress: {},
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
20
        };
21
        this.initForm(props);
22
23
    }

24
25
26
27
    initForm(props) {
        reactForm({
            instance: this,
            name: 'accountSettings',
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
28
29
30
31
32
33
34
35
            fields: [
                'profile_image',
                'cover_image',
                'name',
                'about',
                'location',
                'website',
            ],
36
37
            initialValues: props.profile,
            validation: values => ({
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
                profile_image:
                    values.profile_image &&
                    !/^https?:\/\//.test(values.profile_image)
                        ? tt('settings_jsx.invalid_url')
                        : null,
                cover_image:
                    values.cover_image &&
                    !/^https?:\/\//.test(values.cover_image)
                        ? tt('settings_jsx.invalid_url')
                        : null,
                name:
                    values.name && values.name.length > 20
                        ? tt('settings_jsx.name_is_too_long')
                        : values.name && /^\s*@/.test(values.name)
                          ? tt('settings_jsx.name_must_not_begin_with')
                          : null,
                about:
                    values.about && values.about.length > 160
                        ? tt('settings_jsx.about_is_too_long')
                        : null,
                location:
                    values.location && values.location.length > 30
                        ? tt('settings_jsx.location_is_too_long')
                        : null,
                website:
                    values.website && values.website.length > 100
                        ? tt('settings_jsx.website_url_is_too_long')
                        : values.website && !/^https?:\/\//.test(values.website)
                          ? tt('settings_jsx.invalid_url')
                          : null,
            }),
        });
        this.handleSubmitForm = this.state.accountSettings.handleSubmit(args =>
            this.handleSubmit(args)
        );
73
    }
74

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    onDrop = (acceptedFiles, rejectedFiles) => {
        if (!acceptedFiles.length) {
            if (rejectedFiles.length) {
                this.setState({
                    progress: { error: 'Please insert only image files.' },
                });
                console.log('onDrop Rejected files: ', rejectedFiles);
            }
            return;
        }
        const file = acceptedFiles[0];
        this.upload(file, file.name);
    };

    onOpenClick = imageName => {
        this.setState({
            imageInProgress: imageName,
        });
        this.dropzone.open();
    };

    upload = (file, name = '') => {
        const { uploadImage } = this.props;
        this.setState({
            progress: { message: tt('settings_jsx.uploading_image') + '...' },
        });
        uploadImage(file, progress => {
            if (progress.url) {
                this.setState({ progress: {} });
                const { url } = progress;
                const image_md = `${url}`;
                let field;
                if (this.state.imageInProgress === 'profile_image') {
                    field = this.state.profile_image;
                } else if (this.state.imageInProgress === 'cover_image') {
                    field = this.state.cover_image;
                } else {
                    return;
                }
                field.props.onChange(image_md);
            } else {
                this.setState({ progress });
            }
            setTimeout(() => {
                this.setState({ progress: {} });
            }, 4000); // clear message
        });
    };

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
124
125
126
127
    handleSubmit = ({ updateInitialValues }) => {
        let { metaData } = this.props;
        if (!metaData) metaData = {};
        if (!metaData.profile) metaData.profile = {};
Tim's avatar
Tim committed
128
129
        delete metaData.user_image; // old field... cleanup

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
130
131
132
133
134
135
136
137
        const {
            profile_image,
            cover_image,
            name,
            about,
            location,
            website,
        } = this.state;
138

Tim's avatar
Tim committed
139
        // Update relevant fields
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
140
141
142
143
144
145
        metaData.profile.profile_image = profile_image.value;
        metaData.profile.cover_image = cover_image.value;
        metaData.profile.name = name.value;
        metaData.profile.about = about.value;
        metaData.profile.location = location.value;
        metaData.profile.website = website.value;
Tim's avatar
Tim committed
146
147

        // Remove empty keys
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
148
149
150
151
152
153
154
        if (!metaData.profile.profile_image)
            delete metaData.profile.profile_image;
        if (!metaData.profile.cover_image) delete metaData.profile.cover_image;
        if (!metaData.profile.name) delete metaData.profile.name;
        if (!metaData.profile.about) delete metaData.profile.about;
        if (!metaData.profile.location) delete metaData.profile.location;
        if (!metaData.profile.website) delete metaData.profile.website;
Tim's avatar
Tim committed
155

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
156
157
        const { account, updateAccount } = this.props;
        this.setState({ loading: true });
158
        updateAccount({
Tim's avatar
Tim committed
159
            json_metadata: JSON.stringify(metaData),
160
161
            account: account.name,
            memo_key: account.memo_key,
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
162
            errorCallback: e => {
163
164
165
                if (e === 'Canceled') {
                    this.setState({
                        loading: false,
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
166
167
                        errorMessage: '',
                    });
168
                } else {
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
169
                    console.log('updateAccount ERROR', e);
170
171
172
                    this.setState({
                        loading: false,
                        changed: false,
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
173
174
                        errorMessage: tt('g.server_returned_error'),
                    });
175
                }
176
177
178
179
            },
            successCallback: () => {
                this.setState({
                    loading: false,
180
                    changed: false,
181
                    errorMessage: '',
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
182
                    successMessage: tt('settings_jsx.saved'),
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
183
                });
Tim's avatar
Tim committed
184
                // remove successMessage after a while
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
185
186
187
188
189
                setTimeout(() => this.setState({ successMessage: '' }), 4000);
                updateInitialValues();
            },
        });
    };
190

roadscape's avatar
roadscape committed
191
192
193
194
195
196
    handleLanguageChange = event => {
        const locale = event.target.value;
        const userPreferences = { ...this.props.user_preferences, locale };
        this.props.setUserPreferences(userPreferences);
    };

197
    render() {
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
198
        const { state, props } = this;
199

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
200
201
202
203
204
205
206
        const { submitting, valid, touched } = this.state.accountSettings;
        const disabled =
            !props.isOwnAccount ||
            state.loading ||
            submitting ||
            !valid ||
            !touched;
Tim's avatar
Tim committed
207

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
208
209
210
211
212
213
214
        const {
            profile_image,
            cover_image,
            name,
            about,
            location,
            website,
215
            progress,
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
216
        } = this.state;
Tim's avatar
Tim committed
217

218
        const { account, isOwnAccount, user_preferences } = this.props;
Tim's avatar
Tim committed
219

Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
220
221
        return (
            <div className="Settings">
222
                <div className="row">
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
223
224
225
226
227
                    <form
                        onSubmit={this.handleSubmitForm}
                        className="small-12 medium-6 large-4 columns"
                    >
                        <h4>{tt('settings_jsx.public_profile_settings')}</h4>
228
229
230
231
232
                        {progress.message && (
                            <div className="info">{progress.message}</div>
                        )}
                        {progress.error && (
                            <div className="error">
roadscape's avatar
roadscape committed
233
234
                                {tt('reply_editor.image_upload')}
                                {': '}
235
236
237
                                {progress.error}
                            </div>
                        )}
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
238
239
                        <label>
                            {tt('settings_jsx.profile_image_url')}
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
                            <Dropzone
                                onDrop={this.onDrop}
                                className={'none'}
                                disableClick
                                multiple={false}
                                accept="image/*"
                                ref={node => {
                                    this.dropzone = node;
                                }}
                            >
                                <input
                                    type="url"
                                    {...profile_image.props}
                                    autoComplete="off"
                                />
                            </Dropzone>
                            <a
                                onClick={() =>
                                    this.onOpenClick('profile_image')
                                }
                            >
                                {tt('settings_jsx.upload_image')}
                            </a>
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
263
264
265
266
267
268
269
270
271
272
273
274
275
                        </label>
                        <div className="error">
                            {profile_image.blur &&
                                profile_image.touched &&
                                profile_image.error}
                        </div>
                        <label>
                            {tt('settings_jsx.cover_image_url')}
                            <input
                                type="url"
                                {...cover_image.props}
                                autoComplete="off"
                            />
276
277
278
                            <a onClick={() => this.onOpenClick('cover_image')}>
                                {tt('settings_jsx.upload_image')}
                            </a>
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
                        </label>
                        <div className="error">
                            {cover_image.blur &&
                                cover_image.touched &&
                                cover_image.error}
                        </div>
                        <label>
                            {tt('settings_jsx.profile_name')}
                            <input
                                type="text"
                                {...name.props}
                                maxLength="20"
                                autoComplete="off"
                            />
                        </label>
                        <div className="error">
                            {name.touched && name.error}
                        </div>
                        <label>
                            {tt('settings_jsx.profile_about')}
                            <input
                                type="text"
                                {...about.props}
                                maxLength="160"
                                autoComplete="off"
                            />
                        </label>
                        <div className="error">
                            {about.touched && about.error}
                        </div>
                        <label>
                            {tt('settings_jsx.profile_location')}
                            <input
                                type="text"
                                {...location.props}
                                maxLength="30"
                                autoComplete="off"
                            />
                        </label>
                        <div className="error">
                            {location.touched && location.error}
                        </div>
                        <label>
                            {tt('settings_jsx.profile_website')}
                            <input
                                type="url"
                                {...website.props}
                                maxLength="100"
                                autoComplete="off"
                            />
                        </label>
                        <div className="error">
                            {website.blur && website.touched && website.error}
                        </div>
                        <br />
                        {state.loading && (
                            <span>
                                <LoadingIndicator type="circle" />
                                <br />
                            </span>
                        )}
                        {!state.loading && (
                            <input
                                type="submit"
                                className="button"
                                value={tt('settings_jsx.update')}
                                disabled={disabled}
                            />
                        )}{' '}
                        {state.errorMessage ? (
                            <small className="error">
                                {state.errorMessage}
                            </small>
                        ) : state.successMessage ? (
                            <small className="success uppercase">
                                {state.successMessage}
                            </small>
                        ) : null}
                    </form>
                </div>
roadscape's avatar
roadscape committed
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

                <br />
                <br />
                <h4>{tt('settings_jsx.preferences')}</h4>

                <div className="row">
                    <div className="small-12 medium-6 large-12 columns">
                        <label>
                            {tt('g.choose_language')}
                            <select
                                defaultValue={user_preferences.locale}
                                onChange={this.handleLanguageChange}
                            >
                                <option value="en">English</option>
                                <option value="es">Spanish Español</option>
                                <option value="ru">Russian русский</option>
                                <option value="fr">French français</option>
                                <option value="it">Italian italiano</option>
                                <option value="ko">Korean 한국어</option>
                                <option value="ja">Japanese 日本語</option>
                                <option value="pl">Polish</option>
                                <option value="zh">Chinese 简体中文</option>
                            </select>
                        </label>
                    </div>
                </div>
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
385
386
            </div>
        );
387
388
389
    }
}

390
export default connect(
391
392
    // mapStateToProps
    (state, ownProps) => {
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
393
394
395
396
397
398
399
400
401
        const { accountname } = ownProps.routeParams;
        const account = state.global.getIn(['accounts', accountname]).toJS();
        const current_user = state.user.get('current');
        const username = current_user ? current_user.get('username') : '';
        let metaData = account
            ? o2j.ifStringParseJSON(account.json_metadata)
            : {};
        if (typeof metaData === 'string')
            metaData = o2j.ifStringParseJSON(metaData); // issue #1237
valzav's avatar
valzav committed
402
        const profile = metaData && metaData.profile ? metaData.profile : {};
403
        const user_preferences = state.app.get('user_preferences').toJS();
Tim's avatar
Tim committed
404

405
406
407
        return {
            account,
            metaData,
408
            accountname,
409
            isOwnAccount: username == accountname,
410
            profile,
411
            user_preferences,
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
412
413
            ...ownProps,
        };
414
415
416
    },
    // mapDispatchToProps
    dispatch => ({
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
417
418
        changeLanguage: language => {
            dispatch(userActions.changeLanguage(language));
419
        },
roadscape's avatar
roadscape committed
420
421
422
        setUserPreferences: payload => {
            dispatch(appActions.setUserPreferences(payload));
        },
423
424
        uploadImage: (file, progress) =>
            dispatch(userActions.uploadImage({ file, progress })),
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
425
426
427
428
429
430
431
432
        updateAccount: ({ successCallback, errorCallback, ...operation }) => {
            const options = {
                type: 'account_update',
                operation,
                successCallback,
                errorCallback,
            };
            dispatch(transactionActions.broadcastOperation(options));
433
        },
434
    })
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
435
)(Settings);