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