Settings.jsx 11.8 KB
Newer Older
1
2
import React from 'react';
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';
Tim's avatar
Tim committed
7
import o2j from 'shared/clash/object2json'
8
import LoadingIndicator from 'app/components/elements/LoadingIndicator'
9
import reactForm from 'app/utils/ReactForm'
10
import UserList from 'app/components/elements/UserList';
11

valzav's avatar
valzav committed
12

13
14
class Settings extends React.Component {

15
    constructor(props) {
16
17
18
19
20
21
22
        super(props);
        this.state = {
            errorMessage: '',
            successMessage: '',
        }
        this.initForm(props);
        this.onNsfwPrefChange = this.onNsfwPrefChange.bind(this);
23
24
    }

25
26
27
28
    initForm(props) {
        reactForm({
            instance: this,
            name: 'accountSettings',
29
            fields: ['profile_image', 'cover_image', 'name', 'about', 'location', 'website'],
30
31
            initialValues: props.profile,
            validation: values => ({
valzav's avatar
valzav committed
32
                profile_image: values.profile_image && !/^https?:\/\//.test(values.profile_image) ? tt('settings_jsx.invalid_url') : null,
33
                cover_image: values.cover_image && !/^https?:\/\//.test(values.cover_image) ? tt('settings_jsx.invalid_url') : null,
valzav's avatar
valzav committed
34
35
36
37
                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,
38
39
40
            })
        })
        this.handleSubmitForm =
41
            this.state.accountSettings.handleSubmit(args => this.handleSubmit(args))
42
    }
43

44
45
46
    componentWillMount() {
        const {accountname} = this.props
        const nsfwPref = (process.env.BROWSER ? localStorage.getItem('nsfwPref-' + accountname) : null) || 'warn'
Tim Fesenko's avatar
Tim Fesenko committed
47
        this.setState({nsfwPref, oldNsfwPref: nsfwPref})
48
49
50
51
    }

    onNsfwPrefChange(e) {
        const nsfwPref = e.currentTarget.value;
valzav's avatar
valzav committed
52
53
        const userPreferences = {...this.props.user_preferences, nsfwPref}
        this.props.setUserPreferences(userPreferences)
54
55
    }

56
    handleSubmit = ({updateInitialValues}) => {
57
58
        let {metaData} = this.props
        if (!metaData) metaData = {}
59
        if(!metaData.profile) metaData.profile = {}
Tim's avatar
Tim committed
60
61
        delete metaData.user_image; // old field... cleanup

62
        const {profile_image, cover_image, name, about, location, website} = this.state
63

Tim's avatar
Tim committed
64
65
        // Update relevant fields
        metaData.profile.profile_image = profile_image.value
66
        metaData.profile.cover_image = cover_image.value
Tim's avatar
Tim committed
67
68
69
70
71
72
73
        metaData.profile.name = name.value
        metaData.profile.about = about.value
        metaData.profile.location = location.value
        metaData.profile.website = website.value

        // Remove empty keys
        if(!metaData.profile.profile_image) delete metaData.profile.profile_image;
74
        if(!metaData.profile.cover_image) delete metaData.profile.cover_image;
Tim's avatar
Tim committed
75
76
77
78
79
80
81
        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;

        const {account, updateAccount} = this.props
        this.setState({loading: true})
82
        updateAccount({
Tim's avatar
Tim committed
83
            json_metadata: JSON.stringify(metaData),
84
85
            account: account.name,
            memo_key: account.memo_key,
86
87
88
89
90
91
92
93
94
95
96
            errorCallback: (e) => {
                if (e === 'Canceled') {
                    this.setState({
                        loading: false,
                        errorMessage: ''
                    })
                } else {
                    console.log('updateAccount ERROR', e)
                    this.setState({
                        loading: false,
                        changed: false,
valzav's avatar
valzav committed
97
                        errorMessage: tt('g.server_returned_error')
98
99
                    })
                }
100
101
102
103
            },
            successCallback: () => {
                this.setState({
                    loading: false,
104
                    changed: false,
105
                    errorMessage: '',
valzav's avatar
valzav committed
106
                    successMessage: tt('g.saved') + '!',
107
                })
Tim's avatar
Tim committed
108
                // remove successMessage after a while
109
                setTimeout(() => this.setState({successMessage: ''}), 4000)
110
                updateInitialValues()
111
112
113
114
            }
        })
    }

valzav's avatar
valzav committed
115
    handleLanguageChange = (event) => {
116
        const locale = event.target.value;
valzav's avatar
valzav committed
117
118
        const userPreferences = {...this.props.user_preferences, locale}
        this.props.setUserPreferences(userPreferences)
valzav's avatar
valzav committed
119
120
    }

121
122
    render() {
        const {state, props} = this
Tim's avatar
Tim committed
123

124
125
        const {submitting, valid, touched} = this.state.accountSettings
        const disabled = !props.isOwnAccount || state.loading || submitting || !valid || !touched
Tim's avatar
Tim committed
126

127
        const {profile_image, cover_image, name, about, location, website} = this.state
Tim's avatar
Tim committed
128

129
        const {follow, account, isOwnAccount, user_preferences} = this.props
130
        const following = follow && follow.getIn(['getFollowingAsync', account.name]);
131
132
        const ignores = isOwnAccount && following && following.get('ignore_result')

133
        return <div className="Settings">
valzav's avatar
valzav committed
134
            <div className="row">
valzav's avatar
valzav committed
135
                <div className="small-12 medium-6 large-4 columns">
valzav's avatar
valzav committed
136
                    <label>{tt('g.choose_language')}
137
                        <select defaultValue={user_preferences.locale} onChange={this.handleLanguageChange}>
valzav's avatar
valzav committed
138
139
                            <option value="en">English</option>
                            <option value="es">Spanish</option>
valzav's avatar
valzav committed
140
                            <option value="ru">Russian</option>
valzav's avatar
valzav committed
141
142
                            <option value="fr">French</option>
                            <option value="it">Italian</option>
143
                            <option value="ko">Korean</option>
valzav's avatar
valzav committed
144
145
                        </select>
                    </label>
146
                </div>
valzav's avatar
valzav committed
147
            </div>
valzav's avatar
valzav committed
148
            <br />
valzav's avatar
valzav committed
149
            <div className="row">
150
                <form onSubmit={this.handleSubmitForm} className="small-12 medium-6 large-4 columns">
valzav's avatar
valzav committed
151
                    <h4>{tt('settings_jsx.public_profile_settings')}</h4>
Tim's avatar
Tim committed
152
                    <label>
valzav's avatar
valzav committed
153
                        {tt('settings_jsx.profile_image_url')}
154
                        <input type="url" {...profile_image.props} autoComplete="off" />
Tim's avatar
Tim committed
155
                    </label>
156
                    <div className="error">{profile_image.blur && profile_image.touched && profile_image.error}</div>
Tim's avatar
Tim committed
157

158
159
160
161
162
163
                    <label>
                        {tt('settings_jsx.cover_image_url')}
                        <input type="url" {...cover_image.props} autoComplete="off" />
                    </label>
                    <div className="error">{cover_image.blur && cover_image.touched && cover_image.error}</div>

Tim's avatar
Tim committed
164
                    <label>
valzav's avatar
valzav committed
165
                        {tt('settings_jsx.profile_name')}
166
                        <input type="text" {...name.props} maxLength="20" autoComplete="off" />
Tim's avatar
Tim committed
167
168
169
170
                    </label>
                    <div className="error">{name.touched && name.error}</div>

                    <label>
valzav's avatar
valzav committed
171
                        {tt('settings_jsx.profile_about')}
172
                        <input type="text" {...about.props} maxLength="160" autoComplete="off" />
Tim's avatar
Tim committed
173
174
175
176
                    </label>
                    <div className="error">{about.touched && about.error}</div>

                    <label>
valzav's avatar
valzav committed
177
                        {tt('settings_jsx.profile_location')}
178
                        <input type="text" {...location.props} maxLength="30" autoComplete="off" />
Tim's avatar
Tim committed
179
180
181
182
                    </label>
                    <div className="error">{location.touched && location.error}</div>

                    <label>
valzav's avatar
valzav committed
183
                        {tt('settings_jsx.profile_website')}
184
                        <input type="url" {...website.props} maxLength="100" autoComplete="off" />
Tim's avatar
Tim committed
185
                    </label>
186
                    <div className="error">{website.blur && website.touched && website.error}</div>
Tim's avatar
Tim committed
187
188

                    <br />
189
                    {state.loading && <span><LoadingIndicator type="circle" /><br /></span>}
valzav's avatar
valzav committed
190
                    {!state.loading && <input type="submit" className="button" value={tt('settings_jsx.update')} disabled={disabled} />}
Tim's avatar
Tim committed
191
                    {' '}{
valzav's avatar
valzav committed
192
193
                            state.errorMessage
                                ? <small className="error">{state.errorMessage}</small>
Tim's avatar
Tim committed
194
195
                                : state.successMessage
                                ? <small className="success uppercase">{state.successMessage}</small>
valzav's avatar
valzav committed
196
197
198
199
                                : null
                        }
                </form>
            </div>
Tim's avatar
Tim committed
200

201
202
            {isOwnAccount &&
                <div className="row">
valzav's avatar
valzav committed
203
                    <div className="small-12 medium-6 large-4 columns">
Tim's avatar
Tim committed
204
                        <br /><br />
valzav's avatar
valzav committed
205
                        <h4>{tt('settings_jsx.private_post_display_settings')}</h4>
206
                        <div>
valzav's avatar
valzav committed
207
                            {tt('settings_jsx.not_safe_for_work_nsfw_content')}
208
                        </div>
209
                        <select value={user_preferences.nsfwPref} onChange={this.onNsfwPrefChange}>
valzav's avatar
valzav committed
210
211
212
                            <option value="hide">{tt('settings_jsx.always_hide')}</option>
                            <option value="warn">{tt('settings_jsx.always_warn')}</option>
                            <option value="show">{tt('settings_jsx.always_show')}</option>
213
                        </select>
valzav's avatar
valzav committed
214
215
                        <br />
                        <div>&nbsp;</div>
216
217
                    </div>
                </div>}
218
219
            {ignores && ignores.size > 0 &&
                <div className="row">
valzav's avatar
valzav committed
220
                    <div className="small-12 medium-6 large-4 columns">
221
                        <br /><br />
valzav's avatar
valzav committed
222
                        <UserList title={tt('settings_jsx.muted_users')} account={account} users={ignores} />
223
224
                    </div>
                </div>}
valzav's avatar
valzav committed
225
        </div>
226
227
228
    }
}

229
export default connect(
230
231
    // mapStateToProps
    (state, ownProps) => {
232
        const {accountname} = ownProps.routeParams
valzav's avatar
valzav committed
233
234
235
        const account = state.global.getIn(['accounts', accountname]).toJS()
        const current_user = state.user.get('current')
        const username = current_user ? current_user.get('username') : ''
236
        let metaData = account ? o2j.ifStringParseJSON(account.json_metadata) : {}
237
        if (typeof metaData === 'string') metaData = o2j.ifStringParseJSON(metaData); // issue #1237
valzav's avatar
valzav committed
238
        const profile = metaData && metaData.profile ? metaData.profile : {};
239
        const user_preferences = state.app.get('user_preferences').toJS();
Tim's avatar
Tim committed
240

241
242
243
        return {
            account,
            metaData,
244
            accountname,
245
            isOwnAccount: username == accountname,
246
            profile,
247
            follow: state.global.get('follow'),
248
            user_preferences,
249
250
251
252
253
254
            ...ownProps
        }
    },
    // mapDispatchToProps
    dispatch => ({
        changeLanguage: (language) => {
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
255
            dispatch(userActions.changeLanguage(language))
256
257
        },
        updateAccount: ({successCallback, errorCallback, ...operation}) => {
valzav's avatar
valzav committed
258
            const options = {type: 'account_update', operation, successCallback, errorCallback}
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
259
            dispatch(transactionActions.broadcastOperation(options))
260
        },
261
        setUserPreferences: (payload) => {
Benjamin Chodoroff's avatar
Benjamin Chodoroff committed
262
            dispatch(appActions.setUserPreferences(payload));
263
264
265
        }
    })
)(Settings)