import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { Subscription } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, CommunityUser, SortType, ListingType, UserView, UserSettingsForm, LoginResponse, DeleteAccountForm, WebSocketJsonResponse, GetSiteResponse, UserDetailsView, UserDetailsResponse, AddAdminResponse, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { wsJsonToRes, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter, themes, setTheme, languages, showAvatars, toast, setupTippy, } from '../utils'; import { UserListing } from './user-listing'; import { SortSelect } from './sort-select'; import { ListingTypeSelect } from './listing-type-select'; import { MomentTime } from './moment-time'; import { i18n } from '../i18next'; import moment from 'moment'; import { UserDetails } from './user-details'; interface UserState { user: UserView; user_id: number; username: string; follows: Array; moderates: Array; view: UserDetailsView; sort: SortType; page: number; loading: boolean; avatarLoading: boolean; userSettingsForm: UserSettingsForm; userSettingsLoading: boolean; deleteAccountLoading: boolean; deleteAccountShowConfirm: boolean; deleteAccountForm: DeleteAccountForm; siteRes: GetSiteResponse; } interface UserProps { view: UserDetailsView; sort: SortType; page: number; user_id: number | null; username: string; } interface UrlParams { view?: string; sort?: string; page?: number; } export class User extends Component { private subscription: Subscription; private emptyState: UserState = { user: { id: null, name: null, published: null, number_of_posts: null, post_score: null, number_of_comments: null, comment_score: null, banned: null, avatar: null, show_avatars: null, send_notifications_to_email: null, actor_id: null, local: null, }, user_id: null, username: null, follows: [], moderates: [], loading: true, avatarLoading: false, view: User.getViewFromProps(this.props.match.view), sort: User.getSortTypeFromProps(this.props.match.sort), page: User.getPageFromProps(this.props.match.page), userSettingsForm: { show_nsfw: null, theme: null, default_sort_type: null, default_listing_type: null, lang: null, show_avatars: null, send_notifications_to_email: null, auth: null, }, userSettingsLoading: null, deleteAccountLoading: null, deleteAccountShowConfirm: false, deleteAccountForm: { password: null, }, siteRes: { admins: [], banned: [], online: undefined, site: { id: undefined, name: undefined, creator_id: undefined, published: undefined, creator_name: undefined, number_of_users: undefined, number_of_posts: undefined, number_of_comments: undefined, number_of_communities: undefined, enable_downvotes: undefined, open_registration: undefined, enable_nsfw: undefined, }, }, }; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handleUserSettingsSortTypeChange = this.handleUserSettingsSortTypeChange.bind( this ); this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind( this ); this.handlePageChange = this.handlePageChange.bind(this); this.state.user_id = Number(this.props.match.params.id) || null; this.state.username = this.props.match.params.username; this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) .subscribe( msg => this.parseMessage(msg), err => console.error(err), () => console.log('complete') ); WebSocketService.Instance.getSite(); } get isCurrentUser() { return ( UserService.Instance.user && UserService.Instance.user.id == this.state.user.id ); } static getViewFromProps(view: any): UserDetailsView { return view ? UserDetailsView[capitalizeFirstLetter(view)] : UserDetailsView.Overview; } static getSortTypeFromProps(sort: any): SortType { return sort ? routeSortTypeToEnum(sort) : SortType.New; } static getPageFromProps(page: any): number { return page ? Number(page) : 1; } componentWillUnmount() { this.subscription.unsubscribe(); } static getDerivedStateFromProps(props: any): UserProps { return { view: this.getViewFromProps(props.match.params.view), sort: this.getSortTypeFromProps(props.match.params.sort), page: this.getPageFromProps(props.match.params.page), user_id: Number(props.match.params.id) || null, username: props.match.params.username, }; } componentDidUpdate(lastProps: any, _lastState: UserState, _snapshot: any) { // Necessary if you are on a post and you click another post (same route) if ( lastProps.location.pathname.split('/')[2] !== lastProps.history.location.pathname.split('/')[2] ) { // Couldnt get a refresh working. This does for now. location.reload(); } document.title = `/u/${this.state.username} - ${this.state.siteRes.site.name}`; setupTippy(); } render() { return (
{this.state.user.avatar && showAvatars() && ( )} /u/{this.state.username}
{this.state.loading ? (
) : ( this.selects() )}
{!this.state.loading && (
{this.userInfo()} {this.isCurrentUser && this.userSettings()} {this.moderates()} {this.follows()}
)}
); } viewRadios() { return (
); } selects() { return (
{this.viewRadios()} #
); } userInfo() { let user = this.state.user; return (
  • {user.banned && (
  • {i18n.t('banned')}
  • )}
{i18n.t('cake_day_title')}{' '} {moment.utc(user.published).local().format('MMM DD, YYYY')}
{i18n.t('joined')}
{/* */} {/* */} {/* */}
{i18n.t('number_of_points', { count: user.post_score + user.comment_score, })}
{i18n.t('number_of_points', { count: user.post_score })} {i18n.t('number_of_posts', { count: user.number_of_posts })}
{i18n.t('number_of_points', { count: user.comment_score })} {i18n.t('number_of_comments', { count: user.number_of_comments, })}
{this.isCurrentUser ? ( ) : ( <> {i18n.t('send_secure_message')} {i18n.t('send_message')} )}
); } userSettings() { return (
{i18n.t('settings')}
{this.checkSettingsAvatar && (
)}
{this.state.siteRes.site.enable_nsfw && (
)}

{this.state.deleteAccountShowConfirm && ( <> )}
); } moderates() { return (
{this.state.moderates.length > 0 && (
{i18n.t('moderates')}
    {this.state.moderates.map(community => (
  • {community.community_name}
  • ))}
)}
); } follows() { return (
{this.state.follows.length > 0 && (
{i18n.t('subscribed')}
    {this.state.follows.map(community => (
  • {community.community_name}
  • ))}
)}
); } updateUrl(paramUpdates: UrlParams) { const page = paramUpdates.page || this.state.page; const viewStr = paramUpdates.view || UserDetailsView[this.state.view].toLowerCase(); const sortStr = paramUpdates.sort || SortType[this.state.sort].toLowerCase(); this.props.history.push( `/u/${this.state.username}/view/${viewStr}/sort/${sortStr}/page/${page}` ); } handlePageChange(page: number) { this.updateUrl({ page }); } handleSortChange(val: SortType) { this.updateUrl({ sort: SortType[val].toLowerCase(), page: 1 }); } handleViewChange(i: User, event: any) { i.updateUrl({ view: UserDetailsView[Number(event.target.value)].toLowerCase(), page: 1, }); } handleUserSettingsShowNsfwChange(i: User, event: any) { i.state.userSettingsForm.show_nsfw = event.target.checked; i.setState(i.state); } handleUserSettingsShowAvatarsChange(i: User, event: any) { i.state.userSettingsForm.show_avatars = event.target.checked; UserService.Instance.user.show_avatars = event.target.checked; // Just for instant updates i.setState(i.state); } handleUserSettingsSendNotificationsToEmailChange(i: User, event: any) { i.state.userSettingsForm.send_notifications_to_email = event.target.checked; i.setState(i.state); } handleUserSettingsThemeChange(i: User, event: any) { i.state.userSettingsForm.theme = event.target.value; setTheme(event.target.value, true); i.setState(i.state); } handleUserSettingsLangChange(i: User, event: any) { i.state.userSettingsForm.lang = event.target.value; i18n.changeLanguage(i.state.userSettingsForm.lang); i.setState(i.state); } handleUserSettingsSortTypeChange(val: SortType) { this.state.userSettingsForm.default_sort_type = val; this.setState(this.state); } handleUserSettingsListingTypeChange(val: ListingType) { this.state.userSettingsForm.default_listing_type = val; this.setState(this.state); } handleUserSettingsEmailChange(i: User, event: any) { i.state.userSettingsForm.email = event.target.value; if (i.state.userSettingsForm.email == '' && !i.state.user.email) { i.state.userSettingsForm.email = undefined; } i.setState(i.state); } handleUserSettingsMatrixUserIdChange(i: User, event: any) { i.state.userSettingsForm.matrix_user_id = event.target.value; if ( i.state.userSettingsForm.matrix_user_id == '' && !i.state.user.matrix_user_id ) { i.state.userSettingsForm.matrix_user_id = undefined; } i.setState(i.state); } handleUserSettingsNewPasswordChange(i: User, event: any) { i.state.userSettingsForm.new_password = event.target.value; if (i.state.userSettingsForm.new_password == '') { i.state.userSettingsForm.new_password = undefined; } i.setState(i.state); } handleUserSettingsNewPasswordVerifyChange(i: User, event: any) { i.state.userSettingsForm.new_password_verify = event.target.value; if (i.state.userSettingsForm.new_password_verify == '') { i.state.userSettingsForm.new_password_verify = undefined; } i.setState(i.state); } handleUserSettingsOldPasswordChange(i: User, event: any) { i.state.userSettingsForm.old_password = event.target.value; if (i.state.userSettingsForm.old_password == '') { i.state.userSettingsForm.old_password = undefined; } i.setState(i.state); } handleImageUpload(i: User, event: any) { event.preventDefault(); let file = event.target.files[0]; const imageUploadUrl = `/pictrs/image`; const formData = new FormData(); formData.append('images[]', file); i.state.avatarLoading = true; i.setState(i.state); fetch(imageUploadUrl, { method: 'POST', body: formData, }) .then(res => res.json()) .then(res => { console.log('pictrs upload:'); console.log(res); if (res.msg == 'ok') { let hash = res.files[0].file; let url = `${window.location.origin}/pictrs/image/${hash}`; i.state.userSettingsForm.avatar = url; i.state.avatarLoading = false; i.setState(i.state); } else { i.state.avatarLoading = false; i.setState(i.state); toast(JSON.stringify(res), 'danger'); } }) .catch(error => { i.state.avatarLoading = false; i.setState(i.state); toast(error, 'danger'); }); } removeAvatar(i: User, event: any) { event.preventDefault(); i.state.userSettingsLoading = true; i.state.userSettingsForm.avatar = ''; i.setState(i.state); WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm); } get checkSettingsAvatar(): boolean { return ( this.state.userSettingsForm.avatar && this.state.userSettingsForm.avatar != '' ); } handleUserSettingsSubmit(i: User, event: any) { event.preventDefault(); i.state.userSettingsLoading = true; i.setState(i.state); WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm); } handleDeleteAccountShowConfirmToggle(i: User, event: any) { event.preventDefault(); i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm; i.setState(i.state); } handleDeleteAccountPasswordChange(i: User, event: any) { i.state.deleteAccountForm.password = event.target.value; i.setState(i.state); } handleLogoutClick(i: User) { UserService.Instance.logout(); i.context.router.history.push('/'); } handleDeleteAccount(i: User, event: any) { event.preventDefault(); i.state.deleteAccountLoading = true; i.setState(i.state); WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm); } parseMessage(msg: WebSocketJsonResponse) { console.log(msg); const res = wsJsonToRes(msg); if (msg.error) { toast(i18n.t(msg.error), 'danger'); if (msg.error == 'couldnt_find_that_username_or_email') { this.context.router.history.push('/'); } this.setState({ deleteAccountLoading: false, avatarLoading: false, userSettingsLoading: false, }); return; } else if (res.op == UserOperation.GetUserDetails) { // Since the UserDetails contains posts/comments as well as some general user info we listen here as well // and set the parent state if it is not set or differs const data = res.data as UserDetailsResponse; if (this.state.user.id !== data.user.id) { this.state.user = data.user; this.state.follows = data.follows; this.state.moderates = data.moderates; if (this.isCurrentUser) { this.state.userSettingsForm.show_nsfw = UserService.Instance.user.show_nsfw; this.state.userSettingsForm.theme = UserService.Instance.user.theme ? UserService.Instance.user.theme : 'darkly'; this.state.userSettingsForm.default_sort_type = UserService.Instance.user.default_sort_type; this.state.userSettingsForm.default_listing_type = UserService.Instance.user.default_listing_type; this.state.userSettingsForm.lang = UserService.Instance.user.lang; this.state.userSettingsForm.avatar = UserService.Instance.user.avatar; this.state.userSettingsForm.email = this.state.user.email; this.state.userSettingsForm.send_notifications_to_email = this.state.user.send_notifications_to_email; this.state.userSettingsForm.show_avatars = UserService.Instance.user.show_avatars; this.state.userSettingsForm.matrix_user_id = this.state.user.matrix_user_id; } this.state.loading = false; this.setState(this.state); } } else if (res.op == UserOperation.SaveUserSettings) { const data = res.data as LoginResponse; UserService.Instance.login(data); this.setState({ userSettingsLoading: false, }); window.scrollTo(0, 0); } else if (res.op == UserOperation.DeleteAccount) { this.setState({ deleteAccountLoading: false, deleteAccountShowConfirm: false, }); this.context.router.history.push('/'); } else if (res.op == UserOperation.GetSite) { const data = res.data as GetSiteResponse; this.state.siteRes = data; this.setState(this.state); } else if (res.op == UserOperation.AddAdmin) { const data = res.data as AddAdminResponse; this.state.siteRes.admins = data.admins; this.setState(this.state); } } }