]> Untitled Git - lemmy.git/blob - ui/src/components/user.tsx
Merge branch 'dev' into icons
[lemmy.git] / ui / src / components / user.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take } from 'rxjs/operators';
5 import {
6   UserOperation,
7   Post,
8   Comment,
9   CommunityUser,
10   GetUserDetailsForm,
11   SortType,
12   ListingType,
13   UserDetailsResponse,
14   UserView,
15   CommentResponse,
16   UserSettingsForm,
17   LoginResponse,
18   BanUserResponse,
19   AddAdminResponse,
20   DeleteAccountForm,
21   PostResponse,
22   WebSocketJsonResponse,
23 } from '../interfaces';
24 import { WebSocketService, UserService } from '../services';
25 import {
26   wsJsonToRes,
27   fetchLimit,
28   routeSortTypeToEnum,
29   capitalizeFirstLetter,
30   themes,
31   setTheme,
32   languages,
33   showAvatars,
34   toast,
35   editCommentRes,
36   saveCommentRes,
37   createCommentLikeRes,
38   createPostLikeFindRes,
39   commentsToFlatNodes,
40 } from '../utils';
41 import { PostListing } from './post-listing';
42 import { SortSelect } from './sort-select';
43 import { ListingTypeSelect } from './listing-type-select';
44 import { CommentNodes } from './comment-nodes';
45 import { MomentTime } from './moment-time';
46 import { i18n } from '../i18next';
47
48 enum View {
49   Overview,
50   Comments,
51   Posts,
52   Saved,
53 }
54
55 interface UserState {
56   user: UserView;
57   user_id: number;
58   username: string;
59   follows: Array<CommunityUser>;
60   moderates: Array<CommunityUser>;
61   comments: Array<Comment>;
62   posts: Array<Post>;
63   saved?: Array<Post>;
64   admins: Array<UserView>;
65   view: View;
66   sort: SortType;
67   page: number;
68   loading: boolean;
69   avatarLoading: boolean;
70   userSettingsForm: UserSettingsForm;
71   userSettingsLoading: boolean;
72   deleteAccountLoading: boolean;
73   deleteAccountShowConfirm: boolean;
74   deleteAccountForm: DeleteAccountForm;
75 }
76
77 export class User extends Component<any, UserState> {
78   private subscription: Subscription;
79   private emptyState: UserState = {
80     user: {
81       id: null,
82       name: null,
83       fedi_name: null,
84       published: null,
85       number_of_posts: null,
86       post_score: null,
87       number_of_comments: null,
88       comment_score: null,
89       banned: null,
90       avatar: null,
91       show_avatars: null,
92       send_notifications_to_email: null,
93     },
94     user_id: null,
95     username: null,
96     follows: [],
97     moderates: [],
98     comments: [],
99     posts: [],
100     admins: [],
101     loading: true,
102     avatarLoading: false,
103     view: this.getViewFromProps(this.props),
104     sort: this.getSortTypeFromProps(this.props),
105     page: this.getPageFromProps(this.props),
106     userSettingsForm: {
107       show_nsfw: null,
108       theme: null,
109       default_sort_type: null,
110       default_listing_type: null,
111       lang: null,
112       show_avatars: null,
113       send_notifications_to_email: null,
114       auth: null,
115     },
116     userSettingsLoading: null,
117     deleteAccountLoading: null,
118     deleteAccountShowConfirm: false,
119     deleteAccountForm: {
120       password: null,
121     },
122   };
123
124   constructor(props: any, context: any) {
125     super(props, context);
126
127     this.state = this.emptyState;
128     this.handleSortChange = this.handleSortChange.bind(this);
129     this.handleUserSettingsSortTypeChange = this.handleUserSettingsSortTypeChange.bind(
130       this
131     );
132     this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
133       this
134     );
135
136     this.state.user_id = Number(this.props.match.params.id);
137     this.state.username = this.props.match.params.username;
138
139     this.subscription = WebSocketService.Instance.subject
140       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
141       .subscribe(
142         msg => this.parseMessage(msg),
143         err => console.error(err),
144         () => console.log('complete')
145       );
146
147     this.refetch();
148   }
149
150   get isCurrentUser() {
151     return (
152       UserService.Instance.user &&
153       UserService.Instance.user.id == this.state.user.id
154     );
155   }
156
157   getViewFromProps(props: any): View {
158     return props.match.params.view
159       ? View[capitalizeFirstLetter(props.match.params.view)]
160       : View.Overview;
161   }
162
163   getSortTypeFromProps(props: any): SortType {
164     return props.match.params.sort
165       ? routeSortTypeToEnum(props.match.params.sort)
166       : SortType.New;
167   }
168
169   getPageFromProps(props: any): number {
170     return props.match.params.page ? Number(props.match.params.page) : 1;
171   }
172
173   componentWillUnmount() {
174     this.subscription.unsubscribe();
175   }
176
177   // Necessary for back button for some reason
178   componentWillReceiveProps(nextProps: any) {
179     if (
180       nextProps.history.action == 'POP' ||
181       nextProps.history.action == 'PUSH'
182     ) {
183       this.state.view = this.getViewFromProps(nextProps);
184       this.state.sort = this.getSortTypeFromProps(nextProps);
185       this.state.page = this.getPageFromProps(nextProps);
186       this.setState(this.state);
187       this.refetch();
188     }
189   }
190
191   componentDidUpdate(lastProps: any, _lastState: UserState, _snapshot: any) {
192     // Necessary if you are on a post and you click another post (same route)
193     if (
194       lastProps.location.pathname.split('/')[2] !==
195       lastProps.history.location.pathname.split('/')[2]
196     ) {
197       // Couldnt get a refresh working. This does for now.
198       location.reload();
199     }
200   }
201
202   render() {
203     return (
204       <div class="container">
205         {this.state.loading ? (
206           <h5>
207             <svg class="icon icon-spinner spin">
208               <use xlinkHref="#icon-spinner"></use>
209             </svg>
210           </h5>
211         ) : (
212           <div class="row">
213             <div class="col-12 col-md-8">
214               <h5>
215                 {this.state.user.avatar && showAvatars() && (
216                   <img
217                     height="80"
218                     width="80"
219                     src={this.state.user.avatar}
220                     class="rounded-circle mr-2"
221                   />
222                 )}
223                 <span>/u/{this.state.user.name}</span>
224               </h5>
225               {this.selects()}
226               {this.state.view == View.Overview && this.overview()}
227               {this.state.view == View.Comments && this.comments()}
228               {this.state.view == View.Posts && this.posts()}
229               {this.state.view == View.Saved && this.overview()}
230               {this.paginator()}
231             </div>
232             <div class="col-12 col-md-4">
233               {this.userInfo()}
234               {this.isCurrentUser && this.userSettings()}
235               {this.moderates()}
236               {this.follows()}
237             </div>
238           </div>
239         )}
240       </div>
241     );
242   }
243
244   selects() {
245     return (
246       <div className="mb-2">
247         <select
248           value={this.state.view}
249           onChange={linkEvent(this, this.handleViewChange)}
250           class="custom-select custom-select-sm w-auto"
251         >
252           <option disabled>{i18n.t('view')}</option>
253           <option value={View.Overview}>{i18n.t('overview')}</option>
254           <option value={View.Comments}>{i18n.t('comments')}</option>
255           <option value={View.Posts}>{i18n.t('posts')}</option>
256           <option value={View.Saved}>{i18n.t('saved')}</option>
257         </select>
258         <span class="ml-2">
259           <SortSelect
260             sort={this.state.sort}
261             onChange={this.handleSortChange}
262             hideHot
263           />
264         </span>
265         <a
266           href={`/feeds/u/${this.state.username}.xml?sort=${
267             SortType[this.state.sort]
268           }`}
269           target="_blank"
270           title="RSS"
271         >
272           <svg class="icon mx-2 text-muted small">
273             <use xlinkHref="#icon-rss">#</use>
274           </svg>
275         </a>
276       </div>
277     );
278   }
279
280   overview() {
281     let combined: Array<{ type_: string; data: Comment | Post }> = [];
282     let comments = this.state.comments.map(e => {
283       return { type_: 'comments', data: e };
284     });
285     let posts = this.state.posts.map(e => {
286       return { type_: 'posts', data: e };
287     });
288
289     combined.push(...comments);
290     combined.push(...posts);
291
292     // Sort it
293     if (this.state.sort == SortType.New) {
294       combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
295     } else {
296       combined.sort((a, b) => b.data.score - a.data.score);
297     }
298
299     return (
300       <div>
301         {combined.map(i => (
302           <div>
303             {i.type_ == 'posts' ? (
304               <PostListing
305                 post={i.data as Post}
306                 admins={this.state.admins}
307                 showCommunity
308               />
309             ) : (
310               <CommentNodes
311                 nodes={[{ comment: i.data as Comment }]}
312                 admins={this.state.admins}
313                 noIndent
314               />
315             )}
316           </div>
317         ))}
318       </div>
319     );
320   }
321
322   comments() {
323     return (
324       <div>
325         <CommentNodes
326           nodes={commentsToFlatNodes(this.state.comments)}
327           admins={this.state.admins}
328           noIndent
329         />
330       </div>
331     );
332   }
333
334   posts() {
335     return (
336       <div>
337         {this.state.posts.map(post => (
338           <PostListing post={post} admins={this.state.admins} showCommunity />
339         ))}
340       </div>
341     );
342   }
343
344   userInfo() {
345     let user = this.state.user;
346     return (
347       <div>
348         <div class="card border-secondary mb-3">
349           <div class="card-body">
350             <h5>
351               <ul class="list-inline mb-0">
352                 <li className="list-inline-item">{user.name}</li>
353                 {user.banned && (
354                   <li className="list-inline-item badge badge-danger">
355                     {i18n.t('banned')}
356                   </li>
357                 )}
358               </ul>
359             </h5>
360             <div>
361               {i18n.t('joined')} <MomentTime data={user} />
362             </div>
363             <div class="table-responsive mt-1">
364               <table class="table table-bordered table-sm mt-2 mb-0">
365                 <tr>
366                   <td class="text-center" colSpan={2}>
367                     {i18n.t('number_of_points', {
368                       count: user.post_score + user.comment_score,
369                     })}
370                   </td>
371                 </tr>
372                 <tr>
373                   <td>
374                     {i18n.t('number_of_points', { count: user.post_score })}
375                   </td>
376                   <td>
377                     {i18n.t('number_of_posts', { count: user.number_of_posts })}
378                   </td>
379                 </tr>
380                 <tr>
381                   <td>
382                     {i18n.t('number_of_points', { count: user.comment_score })}
383                   </td>
384                   <td>
385                     {i18n.t('number_of_comments', {
386                       count: user.number_of_comments,
387                     })}
388                   </td>
389                 </tr>
390               </table>
391             </div>
392             {this.isCurrentUser ? (
393               <button
394                 class="btn btn-block btn-secondary mt-3"
395                 onClick={linkEvent(this, this.handleLogoutClick)}
396               >
397                 {i18n.t('logout')}
398               </button>
399             ) : (
400               <>
401                 <a
402                   className={`btn btn-block btn-secondary mt-3 ${!this.state
403                     .user.matrix_user_id && 'disabled'}`}
404                   target="_blank"
405                   href={`https://matrix.to/#/${this.state.user.matrix_user_id}`}
406                 >
407                   {i18n.t('send_secure_message')}
408                 </a>
409                 <Link
410                   class="btn btn-block btn-secondary mt-3"
411                   to={`/create_private_message?recipient_id=${this.state.user.id}`}
412                 >
413                   {i18n.t('send_message')}
414                 </Link>
415               </>
416             )}
417           </div>
418         </div>
419       </div>
420     );
421   }
422
423   userSettings() {
424     return (
425       <div>
426         <div class="card border-secondary mb-3">
427           <div class="card-body">
428             <h5>{i18n.t('settings')}</h5>
429             <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
430               <div class="form-group">
431                 <label>{i18n.t('avatar')}</label>
432                 <form class="d-inline">
433                   <label
434                     htmlFor="file-upload"
435                     class="pointer ml-4 text-muted small font-weight-bold"
436                   >
437                     {!this.state.userSettingsForm.avatar ? (
438                       <span class="btn btn-sm btn-secondary">
439                         {i18n.t('upload_avatar')}
440                       </span>
441                     ) : (
442                       <img
443                         height="80"
444                         width="80"
445                         src={this.state.userSettingsForm.avatar}
446                         class="rounded-circle"
447                       />
448                     )}
449                   </label>
450                   <input
451                     id="file-upload"
452                     type="file"
453                     accept="image/*,video/*"
454                     name="file"
455                     class="d-none"
456                     disabled={!UserService.Instance.user}
457                     onChange={linkEvent(this, this.handleImageUpload)}
458                   />
459                 </form>
460               </div>
461               <div class="form-group">
462                 <label>{i18n.t('language')}</label>
463                 <select
464                   value={this.state.userSettingsForm.lang}
465                   onChange={linkEvent(this, this.handleUserSettingsLangChange)}
466                   class="ml-2 custom-select custom-select-sm w-auto"
467                 >
468                   <option disabled>{i18n.t('language')}</option>
469                   <option value="browser">{i18n.t('browser_default')}</option>
470                   <option disabled>──</option>
471                   {languages.map(lang => (
472                     <option value={lang.code}>{lang.name}</option>
473                   ))}
474                 </select>
475               </div>
476               <div class="form-group">
477                 <label>{i18n.t('theme')}</label>
478                 <select
479                   value={this.state.userSettingsForm.theme}
480                   onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
481                   class="ml-2 custom-select custom-select-sm w-auto"
482                 >
483                   <option disabled>{i18n.t('theme')}</option>
484                   {themes.map(theme => (
485                     <option value={theme}>{theme}</option>
486                   ))}
487                 </select>
488               </div>
489               <form className="form-group">
490                 <label>
491                   <div class="mr-2">{i18n.t('sort_type')}</div>
492                 </label>
493                 <ListingTypeSelect
494                   type_={this.state.userSettingsForm.default_listing_type}
495                   onChange={this.handleUserSettingsListingTypeChange}
496                 />
497               </form>
498               <form className="form-group">
499                 <label>
500                   <div class="mr-2">{i18n.t('type')}</div>
501                 </label>
502                 <SortSelect
503                   sort={this.state.userSettingsForm.default_sort_type}
504                   onChange={this.handleUserSettingsSortTypeChange}
505                 />
506               </form>
507               <div class="form-group row">
508                 <label class="col-lg-3 col-form-label" htmlFor="user-email">
509                   {i18n.t('email')}
510                 </label>
511                 <div class="col-lg-9">
512                   <input
513                     type="email"
514                     id="user-email"
515                     class="form-control"
516                     placeholder={i18n.t('optional')}
517                     value={this.state.userSettingsForm.email}
518                     onInput={linkEvent(
519                       this,
520                       this.handleUserSettingsEmailChange
521                     )}
522                     minLength={3}
523                   />
524                 </div>
525               </div>
526               <div class="form-group row">
527                 <label class="col-lg-5 col-form-label">
528                   <a href="https://about.riot.im/" target="_blank">
529                     {i18n.t('matrix_user_id')}
530                   </a>
531                 </label>
532                 <div class="col-lg-7">
533                   <input
534                     type="text"
535                     class="form-control"
536                     placeholder="@user:example.com"
537                     value={this.state.userSettingsForm.matrix_user_id}
538                     onInput={linkEvent(
539                       this,
540                       this.handleUserSettingsMatrixUserIdChange
541                     )}
542                     minLength={3}
543                   />
544                 </div>
545               </div>
546               <div class="form-group row">
547                 <label class="col-lg-5 col-form-label" htmlFor="user-password">
548                   {i18n.t('new_password')}
549                 </label>
550                 <div class="col-lg-7">
551                   <input
552                     type="password"
553                     id="user-password"
554                     class="form-control"
555                     value={this.state.userSettingsForm.new_password}
556                     onInput={linkEvent(
557                       this,
558                       this.handleUserSettingsNewPasswordChange
559                     )}
560                   />
561                 </div>
562               </div>
563               <div class="form-group row">
564                 <label
565                   class="col-lg-5 col-form-label"
566                   htmlFor="user-verify-password"
567                 >
568                   {i18n.t('verify_password')}
569                 </label>
570                 <div class="col-lg-7">
571                   <input
572                     type="password"
573                     id="user-verify-password"
574                     class="form-control"
575                     value={this.state.userSettingsForm.new_password_verify}
576                     onInput={linkEvent(
577                       this,
578                       this.handleUserSettingsNewPasswordVerifyChange
579                     )}
580                   />
581                 </div>
582               </div>
583               <div class="form-group row">
584                 <label
585                   class="col-lg-5 col-form-label"
586                   htmlFor="user-old-password"
587                 >
588                   {i18n.t('old_password')}
589                 </label>
590                 <div class="col-lg-7">
591                   <input
592                     type="password"
593                     id="user-old-password"
594                     class="form-control"
595                     value={this.state.userSettingsForm.old_password}
596                     onInput={linkEvent(
597                       this,
598                       this.handleUserSettingsOldPasswordChange
599                     )}
600                   />
601                 </div>
602               </div>
603               {WebSocketService.Instance.site.enable_nsfw && (
604                 <div class="form-group">
605                   <div class="form-check">
606                     <input
607                       class="form-check-input"
608                       id="user-show-nsfw"
609                       type="checkbox"
610                       checked={this.state.userSettingsForm.show_nsfw}
611                       onChange={linkEvent(
612                         this,
613                         this.handleUserSettingsShowNsfwChange
614                       )}
615                     />
616                     <label class="form-check-label" htmlFor="user-show-nsfw">
617                       {i18n.t('show_nsfw')}
618                     </label>
619                   </div>
620                 </div>
621               )}
622               <div class="form-group">
623                 <div class="form-check">
624                   <input
625                     class="form-check-input"
626                     id="user-show-avatars"
627                     type="checkbox"
628                     checked={this.state.userSettingsForm.show_avatars}
629                     onChange={linkEvent(
630                       this,
631                       this.handleUserSettingsShowAvatarsChange
632                     )}
633                   />
634                   <label class="form-check-label" htmlFor="user-show-avatars">
635                     {i18n.t('show_avatars')}
636                   </label>
637                 </div>
638               </div>
639               <div class="form-group">
640                 <div class="form-check">
641                   <input
642                     class="form-check-input"
643                     id="user-send-notifications-to-email"
644                     type="checkbox"
645                     disabled={!this.state.user.email}
646                     checked={
647                       this.state.userSettingsForm.send_notifications_to_email
648                     }
649                     onChange={linkEvent(
650                       this,
651                       this.handleUserSettingsSendNotificationsToEmailChange
652                     )}
653                   />
654                   <label
655                     class="form-check-label"
656                     htmlFor="user-send-notifications-to-email"
657                   >
658                     {i18n.t('send_notifications_to_email')}
659                   </label>
660                 </div>
661               </div>
662               <div class="form-group">
663                 <button type="submit" class="btn btn-block btn-secondary mr-4">
664                   {this.state.userSettingsLoading ? (
665                     <svg class="icon icon-spinner spin">
666                       <use xlinkHref="#icon-spinner"></use>
667                     </svg>
668                   ) : (
669                     capitalizeFirstLetter(i18n.t('save'))
670                   )}
671                 </button>
672               </div>
673               <hr />
674               <div class="form-group mb-0">
675                 <button
676                   class="btn btn-block btn-danger"
677                   onClick={linkEvent(
678                     this,
679                     this.handleDeleteAccountShowConfirmToggle
680                   )}
681                 >
682                   {i18n.t('delete_account')}
683                 </button>
684                 {this.state.deleteAccountShowConfirm && (
685                   <>
686                     <div class="my-2 alert alert-danger" role="alert">
687                       {i18n.t('delete_account_confirm')}
688                     </div>
689                     <input
690                       type="password"
691                       value={this.state.deleteAccountForm.password}
692                       onInput={linkEvent(
693                         this,
694                         this.handleDeleteAccountPasswordChange
695                       )}
696                       class="form-control my-2"
697                     />
698                     <button
699                       class="btn btn-danger mr-4"
700                       disabled={!this.state.deleteAccountForm.password}
701                       onClick={linkEvent(this, this.handleDeleteAccount)}
702                     >
703                       {this.state.deleteAccountLoading ? (
704                         <svg class="icon icon-spinner spin">
705                           <use xlinkHref="#icon-spinner"></use>
706                         </svg>
707                       ) : (
708                         capitalizeFirstLetter(i18n.t('delete'))
709                       )}
710                     </button>
711                     <button
712                       class="btn btn-secondary"
713                       onClick={linkEvent(
714                         this,
715                         this.handleDeleteAccountShowConfirmToggle
716                       )}
717                     >
718                       {i18n.t('cancel')}
719                     </button>
720                   </>
721                 )}
722               </div>
723             </form>
724           </div>
725         </div>
726       </div>
727     );
728   }
729
730   moderates() {
731     return (
732       <div>
733         {this.state.moderates.length > 0 && (
734           <div class="card border-secondary mb-3">
735             <div class="card-body">
736               <h5>{i18n.t('moderates')}</h5>
737               <ul class="list-unstyled mb-0">
738                 {this.state.moderates.map(community => (
739                   <li>
740                     <Link to={`/c/${community.community_name}`}>
741                       {community.community_name}
742                     </Link>
743                   </li>
744                 ))}
745               </ul>
746             </div>
747           </div>
748         )}
749       </div>
750     );
751   }
752
753   follows() {
754     return (
755       <div>
756         {this.state.follows.length > 0 && (
757           <div class="card border-secondary mb-3">
758             <div class="card-body">
759               <h5>{i18n.t('subscribed')}</h5>
760               <ul class="list-unstyled mb-0">
761                 {this.state.follows.map(community => (
762                   <li>
763                     <Link to={`/c/${community.community_name}`}>
764                       {community.community_name}
765                     </Link>
766                   </li>
767                 ))}
768               </ul>
769             </div>
770           </div>
771         )}
772       </div>
773     );
774   }
775
776   paginator() {
777     return (
778       <div class="my-2">
779         {this.state.page > 1 && (
780           <button
781             class="btn btn-sm btn-secondary mr-1"
782             onClick={linkEvent(this, this.prevPage)}
783           >
784             {i18n.t('prev')}
785           </button>
786         )}
787         <button
788           class="btn btn-sm btn-secondary"
789           onClick={linkEvent(this, this.nextPage)}
790         >
791           {i18n.t('next')}
792         </button>
793       </div>
794     );
795   }
796
797   updateUrl() {
798     let viewStr = View[this.state.view].toLowerCase();
799     let sortStr = SortType[this.state.sort].toLowerCase();
800     this.props.history.push(
801       `/u/${this.state.user.name}/view/${viewStr}/sort/${sortStr}/page/${this.state.page}`
802     );
803   }
804
805   nextPage(i: User) {
806     i.state.page++;
807     i.setState(i.state);
808     i.updateUrl();
809     i.refetch();
810   }
811
812   prevPage(i: User) {
813     i.state.page--;
814     i.setState(i.state);
815     i.updateUrl();
816     i.refetch();
817   }
818
819   refetch() {
820     let form: GetUserDetailsForm = {
821       user_id: this.state.user_id,
822       username: this.state.username,
823       sort: SortType[this.state.sort],
824       saved_only: this.state.view == View.Saved,
825       page: this.state.page,
826       limit: fetchLimit,
827     };
828     WebSocketService.Instance.getUserDetails(form);
829   }
830
831   handleSortChange(val: SortType) {
832     this.state.sort = val;
833     this.state.page = 1;
834     this.setState(this.state);
835     this.updateUrl();
836     this.refetch();
837   }
838
839   handleViewChange(i: User, event: any) {
840     i.state.view = Number(event.target.value);
841     i.state.page = 1;
842     i.setState(i.state);
843     i.updateUrl();
844     i.refetch();
845   }
846
847   handleUserSettingsShowNsfwChange(i: User, event: any) {
848     i.state.userSettingsForm.show_nsfw = event.target.checked;
849     i.setState(i.state);
850   }
851
852   handleUserSettingsShowAvatarsChange(i: User, event: any) {
853     i.state.userSettingsForm.show_avatars = event.target.checked;
854     UserService.Instance.user.show_avatars = event.target.checked; // Just for instant updates
855     i.setState(i.state);
856   }
857
858   handleUserSettingsSendNotificationsToEmailChange(i: User, event: any) {
859     i.state.userSettingsForm.send_notifications_to_email = event.target.checked;
860     i.setState(i.state);
861   }
862
863   handleUserSettingsThemeChange(i: User, event: any) {
864     i.state.userSettingsForm.theme = event.target.value;
865     setTheme(event.target.value);
866     i.setState(i.state);
867   }
868
869   handleUserSettingsLangChange(i: User, event: any) {
870     i.state.userSettingsForm.lang = event.target.value;
871     i18n.changeLanguage(i.state.userSettingsForm.lang);
872     i.setState(i.state);
873   }
874
875   handleUserSettingsSortTypeChange(val: SortType) {
876     this.state.userSettingsForm.default_sort_type = val;
877     this.setState(this.state);
878   }
879
880   handleUserSettingsListingTypeChange(val: ListingType) {
881     this.state.userSettingsForm.default_listing_type = val;
882     this.setState(this.state);
883   }
884
885   handleUserSettingsEmailChange(i: User, event: any) {
886     i.state.userSettingsForm.email = event.target.value;
887     if (i.state.userSettingsForm.email == '' && !i.state.user.email) {
888       i.state.userSettingsForm.email = undefined;
889     }
890     i.setState(i.state);
891   }
892
893   handleUserSettingsMatrixUserIdChange(i: User, event: any) {
894     i.state.userSettingsForm.matrix_user_id = event.target.value;
895     if (
896       i.state.userSettingsForm.matrix_user_id == '' &&
897       !i.state.user.matrix_user_id
898     ) {
899       i.state.userSettingsForm.matrix_user_id = undefined;
900     }
901     i.setState(i.state);
902   }
903
904   handleUserSettingsNewPasswordChange(i: User, event: any) {
905     i.state.userSettingsForm.new_password = event.target.value;
906     if (i.state.userSettingsForm.new_password == '') {
907       i.state.userSettingsForm.new_password = undefined;
908     }
909     i.setState(i.state);
910   }
911
912   handleUserSettingsNewPasswordVerifyChange(i: User, event: any) {
913     i.state.userSettingsForm.new_password_verify = event.target.value;
914     if (i.state.userSettingsForm.new_password_verify == '') {
915       i.state.userSettingsForm.new_password_verify = undefined;
916     }
917     i.setState(i.state);
918   }
919
920   handleUserSettingsOldPasswordChange(i: User, event: any) {
921     i.state.userSettingsForm.old_password = event.target.value;
922     if (i.state.userSettingsForm.old_password == '') {
923       i.state.userSettingsForm.old_password = undefined;
924     }
925     i.setState(i.state);
926   }
927
928   handleImageUpload(i: User, event: any) {
929     event.preventDefault();
930     let file = event.target.files[0];
931     const imageUploadUrl = `/pictshare/api/upload.php`;
932     const formData = new FormData();
933     formData.append('file', file);
934
935     i.state.avatarLoading = true;
936     i.setState(i.state);
937
938     fetch(imageUploadUrl, {
939       method: 'POST',
940       body: formData,
941     })
942       .then(res => res.json())
943       .then(res => {
944         let url = `${window.location.origin}/pictshare/${res.url}`;
945         if (res.filetype == 'mp4') {
946           url += '/raw';
947         }
948         i.state.userSettingsForm.avatar = url;
949         console.log(url);
950         i.state.avatarLoading = false;
951         i.setState(i.state);
952       })
953       .catch(error => {
954         i.state.avatarLoading = false;
955         i.setState(i.state);
956         toast(error, 'danger');
957       });
958   }
959
960   handleUserSettingsSubmit(i: User, event: any) {
961     event.preventDefault();
962     i.state.userSettingsLoading = true;
963     i.setState(i.state);
964
965     WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm);
966   }
967
968   handleDeleteAccountShowConfirmToggle(i: User, event: any) {
969     event.preventDefault();
970     i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
971     i.setState(i.state);
972   }
973
974   handleDeleteAccountPasswordChange(i: User, event: any) {
975     i.state.deleteAccountForm.password = event.target.value;
976     i.setState(i.state);
977   }
978
979   handleLogoutClick(i: User) {
980     UserService.Instance.logout();
981     i.context.router.history.push('/');
982   }
983
984   handleDeleteAccount(i: User, event: any) {
985     event.preventDefault();
986     i.state.deleteAccountLoading = true;
987     i.setState(i.state);
988
989     WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm);
990   }
991
992   parseMessage(msg: WebSocketJsonResponse) {
993     console.log(msg);
994     let res = wsJsonToRes(msg);
995     if (msg.error) {
996       toast(i18n.t(msg.error), 'danger');
997       this.state.deleteAccountLoading = false;
998       this.state.avatarLoading = false;
999       this.state.userSettingsLoading = false;
1000       if (msg.error == 'couldnt_find_that_username_or_email') {
1001         this.context.router.history.push('/');
1002       }
1003       this.setState(this.state);
1004       return;
1005     } else if (msg.reconnect) {
1006       this.refetch();
1007     } else if (res.op == UserOperation.GetUserDetails) {
1008       let data = res.data as UserDetailsResponse;
1009       this.state.user = data.user;
1010       this.state.comments = data.comments;
1011       this.state.follows = data.follows;
1012       this.state.moderates = data.moderates;
1013       this.state.posts = data.posts;
1014       this.state.admins = data.admins;
1015       this.state.loading = false;
1016       if (this.isCurrentUser) {
1017         this.state.userSettingsForm.show_nsfw =
1018           UserService.Instance.user.show_nsfw;
1019         this.state.userSettingsForm.theme = UserService.Instance.user.theme
1020           ? UserService.Instance.user.theme
1021           : 'darkly';
1022         this.state.userSettingsForm.default_sort_type =
1023           UserService.Instance.user.default_sort_type;
1024         this.state.userSettingsForm.default_listing_type =
1025           UserService.Instance.user.default_listing_type;
1026         this.state.userSettingsForm.lang = UserService.Instance.user.lang;
1027         this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
1028         this.state.userSettingsForm.email = this.state.user.email;
1029         this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email;
1030         this.state.userSettingsForm.show_avatars =
1031           UserService.Instance.user.show_avatars;
1032         this.state.userSettingsForm.matrix_user_id = this.state.user.matrix_user_id;
1033       }
1034       document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
1035       window.scrollTo(0, 0);
1036       this.setState(this.state);
1037     } else if (res.op == UserOperation.EditComment) {
1038       let data = res.data as CommentResponse;
1039       editCommentRes(data, this.state.comments);
1040       this.setState(this.state);
1041     } else if (res.op == UserOperation.CreateComment) {
1042       let data = res.data as CommentResponse;
1043       if (
1044         UserService.Instance.user &&
1045         data.comment.creator_id == UserService.Instance.user.id
1046       ) {
1047         toast(i18n.t('reply_sent'));
1048       }
1049     } else if (res.op == UserOperation.SaveComment) {
1050       let data = res.data as CommentResponse;
1051       saveCommentRes(data, this.state.comments);
1052       this.setState(this.state);
1053     } else if (res.op == UserOperation.CreateCommentLike) {
1054       let data = res.data as CommentResponse;
1055       createCommentLikeRes(data, this.state.comments);
1056       this.setState(this.state);
1057     } else if (res.op == UserOperation.CreatePostLike) {
1058       let data = res.data as PostResponse;
1059       createPostLikeFindRes(data, this.state.posts);
1060       this.setState(this.state);
1061     } else if (res.op == UserOperation.BanUser) {
1062       let data = res.data as BanUserResponse;
1063       this.state.comments
1064         .filter(c => c.creator_id == data.user.id)
1065         .forEach(c => (c.banned = data.banned));
1066       this.state.posts
1067         .filter(c => c.creator_id == data.user.id)
1068         .forEach(c => (c.banned = data.banned));
1069       this.setState(this.state);
1070     } else if (res.op == UserOperation.AddAdmin) {
1071       let data = res.data as AddAdminResponse;
1072       this.state.admins = data.admins;
1073       this.setState(this.state);
1074     } else if (res.op == UserOperation.SaveUserSettings) {
1075       let data = res.data as LoginResponse;
1076       this.state = this.emptyState;
1077       this.state.userSettingsLoading = false;
1078       this.setState(this.state);
1079       UserService.Instance.login(data);
1080     } else if (res.op == UserOperation.DeleteAccount) {
1081       this.state.deleteAccountLoading = false;
1082       this.state.deleteAccountShowConfirm = false;
1083       this.setState(this.state);
1084       this.context.router.history.push('/');
1085     }
1086   }
1087 }