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