1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from 'rxjs';
12 GetUserDetailsResponse,
18 } from 'lemmy-js-client';
19 import { InitialFetchRequest, UserDetailsView } from '../interfaces';
20 import { WebSocketService, UserService } from '../services';
25 capitalizeFirstLetter,
41 createPostLikeFindRes,
49 import { UserListing } from './user-listing';
50 import { HtmlTags } from './html-tags';
51 import { SortSelect } from './sort-select';
52 import { ListingTypeSelect } from './listing-type-select';
53 import { MomentTime } from './moment-time';
54 import { i18n } from '../i18next';
55 import moment from 'moment';
56 import { UserDetails } from './user-details';
57 import { MarkdownTextArea } from './markdown-textarea';
58 import { ImageUploadForm } from './image-upload-form';
59 import { BannerIconHeader } from './banner-icon-header';
60 import { CommunityLink } from './community-link';
63 userRes: GetUserDetailsResponse;
66 view: UserDetailsView;
70 userSettingsForm: SaveUserSettings;
71 userSettingsLoading: boolean;
72 deleteAccountLoading: boolean;
73 deleteAccountShowConfirm: boolean;
74 deleteAccountForm: DeleteAccount;
75 siteRes: GetSiteResponse;
79 view: UserDetailsView;
82 user_id: number | null;
92 export class User extends Component<any, UserState> {
93 private isoData = setIsoData(this.context);
94 private subscription: Subscription;
95 private emptyState: UserState = {
97 userId: getIdFromProps(this.props),
98 userName: getUsernameFromProps(this.props),
100 view: User.getViewFromProps(this.props.match.view),
101 sort: User.getSortTypeFromProps(this.props.match.sort),
102 page: User.getPageFromProps(this.props.match.page),
106 default_sort_type: null,
107 default_listing_type: null,
110 send_notifications_to_email: null,
112 preferred_username: null,
113 auth: authField(false),
115 userSettingsLoading: null,
116 deleteAccountLoading: null,
117 deleteAccountShowConfirm: false,
120 auth: authField(false),
122 siteRes: this.isoData.site_res,
125 constructor(props: any, context: any) {
126 super(props, context);
128 this.state = this.emptyState;
129 this.handleSortChange = this.handleSortChange.bind(this);
130 this.handleUserSettingsSortTypeChange = this.handleUserSettingsSortTypeChange.bind(
133 this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
136 this.handlePageChange = this.handlePageChange.bind(this);
137 this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind(
141 this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
142 this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
144 this.handleBannerUpload = this.handleBannerUpload.bind(this);
145 this.handleBannerRemove = this.handleBannerRemove.bind(this);
147 this.parseMessage = this.parseMessage.bind(this);
148 this.subscription = wsSubscribe(this.parseMessage);
150 // Only fetch the data if coming from another route
151 if (this.isoData.path == this.context.router.route.match.url) {
152 this.state.userRes = this.isoData.routeData[0];
153 this.state.userRes.user_view =
154 this.state.userRes.user_view || this.state.userRes.user_view_dangerous;
156 this.state.loading = false;
158 this.fetchUserData();
165 let form: GetUserDetails = {
166 user_id: this.state.userId,
167 username: this.state.userName,
168 sort: this.state.sort,
169 saved_only: this.state.view === UserDetailsView.Saved,
170 page: this.state.page,
172 auth: authField(false),
174 WebSocketService.Instance.send(wsClient.getUserDetails(form));
177 get isCurrentUser() {
179 UserService.Instance.user &&
180 UserService.Instance.user.id == this.state.userRes.user_view.user.id
184 static getViewFromProps(view: string): UserDetailsView {
185 return view ? UserDetailsView[view] : UserDetailsView.Overview;
188 static getSortTypeFromProps(sort: string): SortType {
189 return sort ? routeSortTypeToEnum(sort) : SortType.New;
192 static getPageFromProps(page: number): number {
193 return page ? Number(page) : 1;
196 static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
197 let pathSplit = req.path.split('/');
198 let promises: Promise<any>[] = [];
200 // It can be /u/me, or /username/1
201 let idOrName = pathSplit[2];
203 let username: string;
204 if (isNaN(Number(idOrName))) {
207 user_id = Number(idOrName);
210 let view = this.getViewFromProps(pathSplit[4]);
211 let sort = this.getSortTypeFromProps(pathSplit[6]);
212 let page = this.getPageFromProps(Number(pathSplit[8]));
214 let form: GetUserDetails = {
216 saved_only: view === UserDetailsView.Saved,
220 setOptionalAuth(form, req.auth);
221 this.setIdOrName(form, user_id, username);
222 promises.push(req.client.getUserDetails(form));
226 static setIdOrName(obj: any, id: number, name_: string) {
230 obj.username = name_;
234 componentWillUnmount() {
235 this.subscription.unsubscribe();
238 static getDerivedStateFromProps(props: any): UserProps {
240 view: this.getViewFromProps(props.match.params.view),
241 sort: this.getSortTypeFromProps(props.match.params.sort),
242 page: this.getPageFromProps(props.match.params.page),
243 user_id: Number(props.match.params.id) || null,
244 username: props.match.params.username,
248 componentDidUpdate(lastProps: any, _lastState: UserState, _snapshot: any) {
249 // Necessary if you are on a post and you click another post (same route)
251 lastProps.location.pathname.split('/')[2] !==
252 lastProps.history.location.pathname.split('/')[2]
254 // Couldnt get a refresh working. This does for now.
259 get documentTitle(): string {
260 return `@${this.state.userRes.user_view.user.name} - ${this.state.siteRes.site_view.site.name}`;
263 get bioTag(): string {
264 return this.state.userRes.user_view.user.bio
265 ? previewLines(this.state.userRes.user_view.user.bio)
271 <div class="container">
272 {this.state.loading ? (
274 <svg class="icon icon-spinner spin">
275 <use xlinkHref="#icon-spinner"></use>
280 <div class="col-12 col-md-8">
283 title={this.documentTitle}
284 path={this.context.router.route.match.url}
285 description={this.bioTag}
286 image={this.state.userRes.user_view.user.avatar}
291 {!this.state.loading && this.selects()}
293 userRes={this.state.userRes}
294 admins={this.state.siteRes.admins}
295 sort={this.state.sort}
296 page={this.state.page}
299 this.state.siteRes.site_view.site.enable_downvotes
301 enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
302 view={this.state.view}
303 onPageChange={this.handlePageChange}
307 {!this.state.loading && (
308 <div class="col-12 col-md-4">
309 {this.isCurrentUser && this.userSettings()}
322 <div class="btn-group btn-group-toggle flex-wrap mb-2">
324 className={`btn btn-outline-secondary pointer
325 ${this.state.view == UserDetailsView.Overview && 'active'}
330 value={UserDetailsView.Overview}
331 checked={this.state.view === UserDetailsView.Overview}
332 onChange={linkEvent(this, this.handleViewChange)}
337 className={`btn btn-outline-secondary pointer
338 ${this.state.view == UserDetailsView.Comments && 'active'}
343 value={UserDetailsView.Comments}
344 checked={this.state.view == UserDetailsView.Comments}
345 onChange={linkEvent(this, this.handleViewChange)}
350 className={`btn btn-outline-secondary pointer
351 ${this.state.view == UserDetailsView.Posts && 'active'}
356 value={UserDetailsView.Posts}
357 checked={this.state.view == UserDetailsView.Posts}
358 onChange={linkEvent(this, this.handleViewChange)}
363 className={`btn btn-outline-secondary pointer
364 ${this.state.view == UserDetailsView.Saved && 'active'}
369 value={UserDetailsView.Saved}
370 checked={this.state.view == UserDetailsView.Saved}
371 onChange={linkEvent(this, this.handleViewChange)}
381 <div className="mb-2">
382 <span class="mr-3">{this.viewRadios()}</span>
384 sort={this.state.sort}
385 onChange={this.handleSortChange}
389 href={`/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`}
394 <svg class="icon mx-2 text-muted small">
395 <use xlinkHref="#icon-rss">#</use>
403 let uv = this.state.userRes?.user_view;
407 <BannerIconHeader banner={uv.user.banner} icon={uv.user.avatar} />
410 <div class="mb-0 d-flex flex-wrap">
412 {uv.user.preferred_username && (
413 <h5 class="mb-0">{uv.user.preferred_username}</h5>
415 <ul class="list-inline mb-2">
416 <li className="list-inline-item">
426 <li className="list-inline-item badge badge-danger">
432 <div className="flex-grow-1 unselectable pointer mx-2"></div>
433 {this.isCurrentUser ? (
435 class="d-flex align-self-start btn btn-secondary mr-2"
436 onClick={linkEvent(this, this.handleLogoutClick)}
443 className={`d-flex align-self-start btn btn-secondary mr-2 ${
444 !uv.user.matrix_user_id && 'invisible'
448 href={`https://matrix.to/#/${uv.user.matrix_user_id}`}
450 {i18n.t('send_secure_message')}
453 className={'d-flex align-self-start btn btn-secondary'}
454 to={`/create_private_message/recipient/${uv.user.id}`}
456 {i18n.t('send_message')}
462 <div className="d-flex align-items-center mb-2">
465 dangerouslySetInnerHTML={mdToHtml(uv.user.bio)}
470 <ul class="list-inline mb-2">
471 <li className="list-inline-item badge badge-light">
472 {i18n.t('number_of_posts', { count: uv.counts.post_count })}
474 <li className="list-inline-item badge badge-light">
475 {i18n.t('number_of_comments', {
476 count: uv.counts.comment_count,
481 <div class="text-muted">
482 {i18n.t('joined')} <MomentTime data={uv.user} showAgo />
484 <div className="d-flex align-items-center text-muted mb-2">
486 <use xlinkHref="#icon-cake"></use>
488 <span className="ml-2">
489 {i18n.t('cake_day_title')}{' '}
490 {moment.utc(uv.user.published).local().format('MMM DD, YYYY')}
502 <div class="card border-secondary mb-3">
503 <div class="card-body">
504 <h5>{i18n.t('settings')}</h5>
505 <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
506 <div class="form-group">
507 <label>{i18n.t('avatar')}</label>
509 uploadTitle={i18n.t('upload_avatar')}
510 imageSrc={this.state.userSettingsForm.avatar}
511 onUpload={this.handleAvatarUpload}
512 onRemove={this.handleAvatarRemove}
516 <div class="form-group">
517 <label>{i18n.t('banner')}</label>
519 uploadTitle={i18n.t('upload_banner')}
520 imageSrc={this.state.userSettingsForm.banner}
521 onUpload={this.handleBannerUpload}
522 onRemove={this.handleBannerRemove}
525 <div class="form-group">
526 <label>{i18n.t('language')}</label>
528 value={this.state.userSettingsForm.lang}
529 onChange={linkEvent(this, this.handleUserSettingsLangChange)}
530 class="ml-2 custom-select w-auto"
532 <option disabled>{i18n.t('language')}</option>
533 <option value="browser">{i18n.t('browser_default')}</option>
534 <option disabled>──</option>
535 {languages.map(lang => (
536 <option value={lang.code}>{lang.name}</option>
540 <div class="form-group">
541 <label>{i18n.t('theme')}</label>
543 value={this.state.userSettingsForm.theme}
544 onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
545 class="ml-2 custom-select w-auto"
547 <option disabled>{i18n.t('theme')}</option>
548 <option value="browser">{i18n.t('browser_default')}</option>
549 {themes.map(theme => (
550 <option value={theme}>{theme}</option>
554 <form className="form-group">
556 <div class="mr-2">{i18n.t('sort_type')}</div>
560 Object.values(ListingType)[
561 this.state.userSettingsForm.default_listing_type
565 this.state.siteRes.federated_instances &&
566 this.state.siteRes.federated_instances.length > 0
568 onChange={this.handleUserSettingsListingTypeChange}
571 <form className="form-group">
573 <div class="mr-2">{i18n.t('type')}</div>
577 Object.values(SortType)[
578 this.state.userSettingsForm.default_sort_type
581 onChange={this.handleUserSettingsSortTypeChange}
584 <div class="form-group row">
585 <label class="col-lg-5 col-form-label">
586 {i18n.t('display_name')}
588 <div class="col-lg-7">
592 placeholder={i18n.t('optional')}
593 value={this.state.userSettingsForm.preferred_username}
596 this.handleUserSettingsPreferredUsernameChange
598 pattern="^(?!@)(.+)$"
604 <div class="form-group row">
605 <label class="col-lg-3 col-form-label" htmlFor="user-bio">
608 <div class="col-lg-9">
610 initialContent={this.state.userSettingsForm.bio}
611 onContentChange={this.handleUserSettingsBioChange}
613 hideNavigationWarnings
617 <div class="form-group row">
618 <label class="col-lg-3 col-form-label" htmlFor="user-email">
621 <div class="col-lg-9">
626 placeholder={i18n.t('optional')}
627 value={this.state.userSettingsForm.email}
630 this.handleUserSettingsEmailChange
636 <div class="form-group row">
637 <label class="col-lg-5 col-form-label">
638 <a href={elementUrl} target="_blank" rel="noopener">
639 {i18n.t('matrix_user_id')}
642 <div class="col-lg-7">
646 placeholder="@user:example.com"
647 value={this.state.userSettingsForm.matrix_user_id}
650 this.handleUserSettingsMatrixUserIdChange
656 <div class="form-group row">
657 <label class="col-lg-5 col-form-label" htmlFor="user-password">
658 {i18n.t('new_password')}
660 <div class="col-lg-7">
665 value={this.state.userSettingsForm.new_password}
666 autoComplete="new-password"
669 this.handleUserSettingsNewPasswordChange
674 <div class="form-group row">
676 class="col-lg-5 col-form-label"
677 htmlFor="user-verify-password"
679 {i18n.t('verify_password')}
681 <div class="col-lg-7">
684 id="user-verify-password"
686 value={this.state.userSettingsForm.new_password_verify}
687 autoComplete="new-password"
690 this.handleUserSettingsNewPasswordVerifyChange
695 <div class="form-group row">
697 class="col-lg-5 col-form-label"
698 htmlFor="user-old-password"
700 {i18n.t('old_password')}
702 <div class="col-lg-7">
705 id="user-old-password"
707 value={this.state.userSettingsForm.old_password}
708 autoComplete="new-password"
711 this.handleUserSettingsOldPasswordChange
716 {this.state.siteRes.site_view.site.enable_nsfw && (
717 <div class="form-group">
718 <div class="form-check">
720 class="form-check-input"
723 checked={this.state.userSettingsForm.show_nsfw}
726 this.handleUserSettingsShowNsfwChange
729 <label class="form-check-label" htmlFor="user-show-nsfw">
730 {i18n.t('show_nsfw')}
735 <div class="form-group">
736 <div class="form-check">
738 class="form-check-input"
739 id="user-show-avatars"
741 checked={this.state.userSettingsForm.show_avatars}
744 this.handleUserSettingsShowAvatarsChange
747 <label class="form-check-label" htmlFor="user-show-avatars">
748 {i18n.t('show_avatars')}
752 <div class="form-group">
753 <div class="form-check">
755 class="form-check-input"
756 id="user-send-notifications-to-email"
758 disabled={!this.state.userSettingsForm.email}
760 this.state.userSettingsForm.send_notifications_to_email
764 this.handleUserSettingsSendNotificationsToEmailChange
768 class="form-check-label"
769 htmlFor="user-send-notifications-to-email"
771 {i18n.t('send_notifications_to_email')}
775 <div class="form-group">
776 <button type="submit" class="btn btn-block btn-secondary mr-4">
777 {this.state.userSettingsLoading ? (
778 <svg class="icon icon-spinner spin">
779 <use xlinkHref="#icon-spinner"></use>
782 capitalizeFirstLetter(i18n.t('save'))
787 <div class="form-group mb-0">
789 class="btn btn-block btn-danger"
792 this.handleDeleteAccountShowConfirmToggle
795 {i18n.t('delete_account')}
797 {this.state.deleteAccountShowConfirm && (
799 <div class="my-2 alert alert-danger" role="alert">
800 {i18n.t('delete_account_confirm')}
804 value={this.state.deleteAccountForm.password}
805 autoComplete="new-password"
808 this.handleDeleteAccountPasswordChange
810 class="form-control my-2"
813 class="btn btn-danger mr-4"
814 disabled={!this.state.deleteAccountForm.password}
815 onClick={linkEvent(this, this.handleDeleteAccount)}
817 {this.state.deleteAccountLoading ? (
818 <svg class="icon icon-spinner spin">
819 <use xlinkHref="#icon-spinner"></use>
822 capitalizeFirstLetter(i18n.t('delete'))
826 class="btn btn-secondary"
829 this.handleDeleteAccountShowConfirmToggle
847 {this.state.userRes.moderates.length > 0 && (
848 <div class="card border-secondary mb-3">
849 <div class="card-body">
850 <h5>{i18n.t('moderates')}</h5>
851 <ul class="list-unstyled mb-0">
852 {this.state.userRes.moderates.map(cmv => (
854 <CommunityLink community={cmv.community} />
868 {this.state.userRes.follows.length > 0 && (
869 <div class="card border-secondary mb-3">
870 <div class="card-body">
871 <h5>{i18n.t('subscribed')}</h5>
872 <ul class="list-unstyled mb-0">
873 {this.state.userRes.follows.map(cfv => (
875 <CommunityLink community={cfv.community} />
886 updateUrl(paramUpdates: UrlParams) {
887 const page = paramUpdates.page || this.state.page;
888 const viewStr = paramUpdates.view || UserDetailsView[this.state.view];
889 const sortStr = paramUpdates.sort || this.state.sort;
890 this.props.history.push(
891 `/u/${this.state.userName}/view/${viewStr}/sort/${sortStr}/page/${page}`
893 this.state.loading = true;
894 this.setState(this.state);
895 this.fetchUserData();
898 handlePageChange(page: number) {
899 this.updateUrl({ page });
902 handleSortChange(val: SortType) {
903 this.updateUrl({ sort: val, page: 1 });
906 handleViewChange(i: User, event: any) {
908 view: UserDetailsView[Number(event.target.value)],
913 handleUserSettingsShowNsfwChange(i: User, event: any) {
914 i.state.userSettingsForm.show_nsfw = event.target.checked;
918 handleUserSettingsShowAvatarsChange(i: User, event: any) {
919 i.state.userSettingsForm.show_avatars = event.target.checked;
920 UserService.Instance.user.show_avatars = event.target.checked; // Just for instant updates
924 handleUserSettingsSendNotificationsToEmailChange(i: User, event: any) {
925 i.state.userSettingsForm.send_notifications_to_email = event.target.checked;
929 handleUserSettingsThemeChange(i: User, event: any) {
930 i.state.userSettingsForm.theme = event.target.value;
931 setTheme(event.target.value, true);
935 handleUserSettingsLangChange(i: User, event: any) {
936 i.state.userSettingsForm.lang = event.target.value;
937 i18n.changeLanguage(getLanguage(i.state.userSettingsForm.lang));
941 handleUserSettingsSortTypeChange(val: SortType) {
942 this.state.userSettingsForm.default_sort_type = Object.keys(
945 this.setState(this.state);
948 handleUserSettingsListingTypeChange(val: ListingType) {
949 this.state.userSettingsForm.default_listing_type = Object.keys(
952 this.setState(this.state);
955 handleUserSettingsEmailChange(i: User, event: any) {
956 i.state.userSettingsForm.email = event.target.value;
960 handleUserSettingsBioChange(val: string) {
961 this.state.userSettingsForm.bio = val;
962 this.setState(this.state);
965 handleAvatarUpload(url: string) {
966 this.state.userSettingsForm.avatar = url;
967 this.setState(this.state);
970 handleAvatarRemove() {
971 this.state.userSettingsForm.avatar = '';
972 this.setState(this.state);
975 handleBannerUpload(url: string) {
976 this.state.userSettingsForm.banner = url;
977 this.setState(this.state);
980 handleBannerRemove() {
981 this.state.userSettingsForm.banner = '';
982 this.setState(this.state);
985 handleUserSettingsPreferredUsernameChange(i: User, event: any) {
986 i.state.userSettingsForm.preferred_username = event.target.value;
990 handleUserSettingsMatrixUserIdChange(i: User, event: any) {
991 i.state.userSettingsForm.matrix_user_id = event.target.value;
993 i.state.userSettingsForm.matrix_user_id == '' &&
994 !i.state.userRes.user_view.user.matrix_user_id
996 i.state.userSettingsForm.matrix_user_id = undefined;
1001 handleUserSettingsNewPasswordChange(i: User, event: any) {
1002 i.state.userSettingsForm.new_password = event.target.value;
1003 if (i.state.userSettingsForm.new_password == '') {
1004 i.state.userSettingsForm.new_password = undefined;
1006 i.setState(i.state);
1009 handleUserSettingsNewPasswordVerifyChange(i: User, event: any) {
1010 i.state.userSettingsForm.new_password_verify = event.target.value;
1011 if (i.state.userSettingsForm.new_password_verify == '') {
1012 i.state.userSettingsForm.new_password_verify = undefined;
1014 i.setState(i.state);
1017 handleUserSettingsOldPasswordChange(i: User, event: any) {
1018 i.state.userSettingsForm.old_password = event.target.value;
1019 if (i.state.userSettingsForm.old_password == '') {
1020 i.state.userSettingsForm.old_password = undefined;
1022 i.setState(i.state);
1025 handleUserSettingsSubmit(i: User, event: any) {
1026 event.preventDefault();
1027 i.state.userSettingsLoading = true;
1028 i.setState(i.state);
1030 WebSocketService.Instance.send(
1031 wsClient.saveUserSettings(i.state.userSettingsForm)
1035 handleDeleteAccountShowConfirmToggle(i: User, event: any) {
1036 event.preventDefault();
1037 i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
1038 i.setState(i.state);
1041 handleDeleteAccountPasswordChange(i: User, event: any) {
1042 i.state.deleteAccountForm.password = event.target.value;
1043 i.setState(i.state);
1046 handleLogoutClick(i: User) {
1047 UserService.Instance.logout();
1048 i.context.router.history.push('/');
1051 handleDeleteAccount(i: User, event: any) {
1052 event.preventDefault();
1053 i.state.deleteAccountLoading = true;
1054 i.setState(i.state);
1056 WebSocketService.Instance.send(
1057 wsClient.deleteAccount(i.state.deleteAccountForm)
1059 i.handleLogoutClick(i);
1063 if (this.isCurrentUser) {
1064 this.state.userSettingsForm.show_nsfw =
1065 UserService.Instance.user.show_nsfw;
1066 this.state.userSettingsForm.theme = UserService.Instance.user.theme
1067 ? UserService.Instance.user.theme
1069 this.state.userSettingsForm.default_sort_type =
1070 UserService.Instance.user.default_sort_type;
1071 this.state.userSettingsForm.default_listing_type =
1072 UserService.Instance.user.default_listing_type;
1073 this.state.userSettingsForm.lang = UserService.Instance.user.lang;
1074 this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
1075 this.state.userSettingsForm.banner = UserService.Instance.user.banner;
1076 this.state.userSettingsForm.preferred_username =
1077 UserService.Instance.user.preferred_username;
1078 this.state.userSettingsForm.show_avatars =
1079 UserService.Instance.user.show_avatars;
1080 this.state.userSettingsForm.email = UserService.Instance.user.email;
1081 this.state.userSettingsForm.bio = UserService.Instance.user.bio;
1082 this.state.userSettingsForm.send_notifications_to_email =
1083 UserService.Instance.user.send_notifications_to_email;
1084 this.state.userSettingsForm.matrix_user_id =
1085 UserService.Instance.user.matrix_user_id;
1089 parseMessage(msg: any) {
1090 let op = wsUserOp(msg);
1092 toast(i18n.t(msg.error), 'danger');
1093 if (msg.error == 'couldnt_find_that_username_or_email') {
1094 this.context.router.history.push('/');
1097 deleteAccountLoading: false,
1098 userSettingsLoading: false,
1101 } else if (msg.reconnect) {
1102 this.fetchUserData();
1103 } else if (op == UserOperation.GetUserDetails) {
1104 // Since the UserDetails contains posts/comments as well as some general user info we listen here as well
1105 // and set the parent state if it is not set or differs
1106 // TODO this might need to get abstracted
1107 let data = wsJsonToRes<GetUserDetailsResponse>(msg).data;
1108 this.state.userRes = data;
1109 this.state.userRes.user_view =
1110 this.state.userRes.user_view || this.state.userRes.user_view_dangerous;
1112 this.state.loading = false;
1113 this.setState(this.state);
1114 } else if (op == UserOperation.SaveUserSettings) {
1115 let data = wsJsonToRes<LoginResponse>(msg).data;
1116 UserService.Instance.login(data);
1117 this.state.userRes.user_view.user.bio = this.state.userSettingsForm.bio;
1118 this.state.userRes.user_view.user.preferred_username = this.state.userSettingsForm.preferred_username;
1119 this.state.userRes.user_view.user.banner = this.state.userSettingsForm.banner;
1120 this.state.userRes.user_view.user.avatar = this.state.userSettingsForm.avatar;
1121 this.state.userSettingsLoading = false;
1122 this.setState(this.state);
1124 window.scrollTo(0, 0);
1125 } else if (op == UserOperation.DeleteAccount) {
1127 deleteAccountLoading: false,
1128 deleteAccountShowConfirm: false,
1130 this.context.router.history.push('/');
1131 } else if (op == UserOperation.AddAdmin) {
1132 let data = wsJsonToRes<AddAdminResponse>(msg).data;
1133 this.state.siteRes.admins = data.admins;
1134 this.setState(this.state);
1135 } else if (op == UserOperation.CreateCommentLike) {
1136 let data = wsJsonToRes<CommentResponse>(msg).data;
1137 createCommentLikeRes(data.comment_view, this.state.userRes.comments);
1138 this.setState(this.state);
1140 op == UserOperation.EditComment ||
1141 op == UserOperation.DeleteComment ||
1142 op == UserOperation.RemoveComment
1144 let data = wsJsonToRes<CommentResponse>(msg).data;
1145 editCommentRes(data.comment_view, this.state.userRes.comments);
1146 this.setState(this.state);
1147 } else if (op == UserOperation.CreateComment) {
1148 let data = wsJsonToRes<CommentResponse>(msg).data;
1150 UserService.Instance.user &&
1151 data.comment_view.creator.id == UserService.Instance.user.id
1153 toast(i18n.t('reply_sent'));
1155 } else if (op == UserOperation.SaveComment) {
1156 let data = wsJsonToRes<CommentResponse>(msg).data;
1157 saveCommentRes(data.comment_view, this.state.userRes.comments);
1158 this.setState(this.state);
1160 op == UserOperation.EditPost ||
1161 op == UserOperation.DeletePost ||
1162 op == UserOperation.RemovePost ||
1163 op == UserOperation.LockPost ||
1164 op == UserOperation.StickyPost ||
1165 op == UserOperation.SavePost
1167 let data = wsJsonToRes<PostResponse>(msg).data;
1168 editPostFindRes(data.post_view, this.state.userRes.posts);
1169 this.setState(this.state);
1170 } else if (op == UserOperation.CreatePostLike) {
1171 let data = wsJsonToRes<PostResponse>(msg).data;
1172 createPostLikeFindRes(data.post_view, this.state.userRes.posts);
1173 this.setState(this.state);
1174 } else if (op == UserOperation.BanUser) {
1175 let data = wsJsonToRes<BanUserResponse>(msg).data;
1176 this.state.userRes.comments
1177 .filter(c => c.creator.id == data.user_view.user.id)
1178 .forEach(c => (c.creator.banned = data.banned));
1179 this.state.userRes.posts
1180 .filter(c => c.creator.id == data.user_view.user.id)
1181 .forEach(c => (c.creator.banned = data.banned));
1182 this.setState(this.state);