mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-27 10:03:56 +00:00 
			
		
		
		
	user_groups: Allow adding a user to groups via user profile.
Fixes: #32488.
This commit is contained in:
		
				
					committed by
					
						 Tim Abbott
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							e33fe6779b
						
					
				
				
					commit
					faa8b0d4a5
				
			| @@ -101,12 +101,11 @@ export function generate_pill_html(item: CombinedPill): string { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function set_up_handlers_for_add_button_state( | export function set_up_handlers_for_add_button_state( | ||||||
|     pill_widget: CombinedPillContainer, |     pill_widget: CombinedPillContainer | user_group_pill.UserGroupPillWidget, | ||||||
|     $pill_container: JQuery, |     $pill_container: JQuery, | ||||||
| ): void { | ): void { | ||||||
|     const $pill_widget_input = $pill_container.find(".input"); |     const $pill_widget_input = $pill_container.find(".input"); | ||||||
|     const $pill_widget_button = $pill_container.parent().find(".add-users-button"); |     const $pill_widget_button = $pill_container.closest(".add-button-container").find(".button"); | ||||||
|  |  | ||||||
|     // Disable the add button first time the pill container is created. |     // Disable the add button first time the pill container is created. | ||||||
|     $pill_widget_button.prop("disabled", true); |     $pill_widget_button.prop("disabled", true); | ||||||
|  |  | ||||||
| @@ -114,6 +113,8 @@ export function set_up_handlers_for_add_button_state( | |||||||
|     pill_widget.onPillRemove(() => |     pill_widget.onPillRemove(() => | ||||||
|         $pill_widget_button.prop("disabled", pill_widget.items().length === 0), |         $pill_widget_button.prop("disabled", pill_widget.items().length === 0), | ||||||
|     ); |     ); | ||||||
|  |     // If a pill is added, enable the add button. | ||||||
|  |     pill_widget.onPillCreate(() => $pill_widget_button.prop("disabled", false)); | ||||||
|     // Disable the add button when there is no pending text that can be converted |     // Disable the add button when there is no pending text that can be converted | ||||||
|     // into a pill and the number of existing pills is zero. |     // into a pill and the number of existing pills is zero. | ||||||
|     $pill_widget_input.on("input", () => |     $pill_widget_input.on("input", () => | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ export type InputPillConfig = { | |||||||
|     exclude_inaccessible_users?: boolean; |     exclude_inaccessible_users?: boolean; | ||||||
|     setting_name?: string; |     setting_name?: string; | ||||||
|     setting_type?: "realm" | "stream" | "group"; |     setting_type?: "realm" | "stream" | "group"; | ||||||
|  |     user_id?: number; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| type InputPillCreateOptions<ItemType> = { | type InputPillCreateOptions<ItemType> = { | ||||||
| @@ -68,6 +69,10 @@ export type InputPillContainer<ItemType> = { | |||||||
|     appendValidatedData: (item: ItemType) => void; |     appendValidatedData: (item: ItemType) => void; | ||||||
|     getByElement: (element: HTMLElement) => InputPill<ItemType> | undefined; |     getByElement: (element: HTMLElement) => InputPill<ItemType> | undefined; | ||||||
|     items: () => ItemType[]; |     items: () => ItemType[]; | ||||||
|  |     removePill: ( | ||||||
|  |         element: HTMLElement, | ||||||
|  |         trigger: RemovePillTrigger, | ||||||
|  |     ) => InputPill<ItemType> | undefined; | ||||||
|     onPillCreate: (callback: () => void) => void; |     onPillCreate: (callback: () => void) => void; | ||||||
|     onPillRemove: ( |     onPillRemove: ( | ||||||
|         callback: (pill: InputPill<ItemType>, trigger: RemovePillTrigger) => void, |         callback: (pill: InputPill<ItemType>, trigger: RemovePillTrigger) => void, | ||||||
| @@ -480,6 +485,7 @@ export function create<ItemType extends {type: string}>( | |||||||
|         getByElement: funcs.getByElement.bind(funcs), |         getByElement: funcs.getByElement.bind(funcs), | ||||||
|         getCurrentText: funcs.getCurrentText.bind(funcs), |         getCurrentText: funcs.getCurrentText.bind(funcs), | ||||||
|         items: funcs.items.bind(funcs), |         items: funcs.items.bind(funcs), | ||||||
|  |         removePill: funcs.removePill.bind(funcs), | ||||||
|  |  | ||||||
|         onPillCreate(callback) { |         onPillCreate(callback) { | ||||||
|             store.onPillCreate = callback; |             store.onPillCreate = callback; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import render_input_pill from "../templates/input_pill.hbs"; | import render_input_pill from "../templates/input_pill.hbs"; | ||||||
|  |  | ||||||
|  | import {set_up_handlers_for_add_button_state} from "./add_subscribers_pill.ts"; | ||||||
| import * as input_pill from "./input_pill.ts"; | import * as input_pill from "./input_pill.ts"; | ||||||
| import {set_up_user_group} from "./pill_typeahead.ts"; | import {set_up_user_group} from "./pill_typeahead.ts"; | ||||||
| import * as settings_data from "./settings_data.ts"; | import * as settings_data from "./settings_data.ts"; | ||||||
| @@ -16,6 +17,7 @@ type SetUpPillTypeaheadConfig = { | |||||||
| function create_item_from_group_name( | function create_item_from_group_name( | ||||||
|     group_name: string, |     group_name: string, | ||||||
|     current_items: UserGroupPill[], |     current_items: UserGroupPill[], | ||||||
|  |     pill_config?: input_pill.InputPillConfig, | ||||||
| ): UserGroupPill | undefined { | ): UserGroupPill | undefined { | ||||||
|     group_name = group_name.trim(); |     group_name = group_name.trim(); | ||||||
|     const group = user_groups.get_user_group_from_name(group_name); |     const group = user_groups.get_user_group_from_name(group_name); | ||||||
| @@ -31,6 +33,12 @@ function create_item_from_group_name( | |||||||
|         return undefined; |         return undefined; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Check if the user is already a direct member of the user group. | ||||||
|  |     const user_id = pill_config?.user_id; | ||||||
|  |     if (user_id !== undefined && user_groups.is_direct_member_of(user_id, group.id)) { | ||||||
|  |         return undefined; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         type: "user_group", |         type: "user_group", | ||||||
|         group_id: group.id, |         group_id: group.id, | ||||||
| @@ -43,9 +51,17 @@ export function get_user_groups_allowed_to_add_members(): UserGroup[] { | |||||||
|     return all_user_groups.filter((group) => settings_data.can_add_members_to_user_group(group.id)); |     return all_user_groups.filter((group) => settings_data.can_add_members_to_user_group(group.id)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function set_up_pill_typeahead({pill_widget, $pill_container}: SetUpPillTypeaheadConfig): void { | function set_up_pill_typeahead( | ||||||
|  |     {pill_widget, $pill_container}: SetUpPillTypeaheadConfig, | ||||||
|  |     user_id?: number, | ||||||
|  | ): void { | ||||||
|     const user_group_source: () => UserGroup[] = () => { |     const user_group_source: () => UserGroup[] = () => { | ||||||
|         const groups_with_permission = get_user_groups_allowed_to_add_members(); |         let groups_with_permission = get_user_groups_allowed_to_add_members(); | ||||||
|  |         if (user_id !== undefined) { | ||||||
|  |             groups_with_permission = groups_with_permission.filter( | ||||||
|  |                 (group) => !user_groups.is_direct_member_of(user_id, group.id), | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|         return user_group_pill.filter_taken_groups(groups_with_permission, pill_widget); |         return user_group_pill.filter_taken_groups(groups_with_permission, pill_widget); | ||||||
|     }; |     }; | ||||||
|     set_up_user_group($pill_container.find(".input"), pill_widget, {user_group_source}); |     set_up_user_group($pill_container.find(".input"), pill_widget, {user_group_source}); | ||||||
| @@ -62,15 +78,24 @@ function generate_pill_html(item: UserGroupPill): string { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function create($user_group_pill_container: JQuery): user_group_pill.UserGroupPillWidget { | export function create( | ||||||
|  |     $user_group_pill_container: JQuery, | ||||||
|  |     user_id?: number, | ||||||
|  | ): user_group_pill.UserGroupPillWidget { | ||||||
|  |     const pill_config = user_id ? {user_id} : undefined; | ||||||
|  |  | ||||||
|     const pill_widget = input_pill.create({ |     const pill_widget = input_pill.create({ | ||||||
|         $container: $user_group_pill_container, |         $container: $user_group_pill_container, | ||||||
|  |         pill_config, | ||||||
|         create_item_from_text: create_item_from_group_name, |         create_item_from_text: create_item_from_group_name, | ||||||
|         get_text_from_item: user_group_pill.get_group_name_from_item, |         get_text_from_item: user_group_pill.get_group_name_from_item, | ||||||
|         generate_pill_html, |         generate_pill_html, | ||||||
|         get_display_value_from_item, |         get_display_value_from_item, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     set_up_pill_typeahead({pill_widget, $pill_container: $user_group_pill_container}); |     set_up_pill_typeahead({pill_widget, $pill_container: $user_group_pill_container}, user_id); | ||||||
|  |     if (user_id) { | ||||||
|  |         set_up_handlers_for_add_button_state(pill_widget, $user_group_pill_container); | ||||||
|  |     } | ||||||
|     return pill_widget; |     return pill_widget; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -57,6 +57,8 @@ import * as ui_report from "./ui_report.ts"; | |||||||
| import type {UploadWidget} from "./upload_widget.ts"; | import type {UploadWidget} from "./upload_widget.ts"; | ||||||
| import * as user_deactivation_ui from "./user_deactivation_ui.ts"; | import * as user_deactivation_ui from "./user_deactivation_ui.ts"; | ||||||
| import * as user_group_edit_members from "./user_group_edit_members.ts"; | import * as user_group_edit_members from "./user_group_edit_members.ts"; | ||||||
|  | import * as user_group_picker_pill from "./user_group_picker_pill.ts"; | ||||||
|  | import * as user_group_pill from "./user_group_pill.ts"; | ||||||
| import * as user_groups from "./user_groups.ts"; | import * as user_groups from "./user_groups.ts"; | ||||||
| import type {UserGroup} from "./user_groups.ts"; | import type {UserGroup} from "./user_groups.ts"; | ||||||
| import * as user_pill from "./user_pill.ts"; | import * as user_pill from "./user_pill.ts"; | ||||||
| @@ -80,6 +82,7 @@ export type CustomProfileFieldData = { | |||||||
| let user_streams_list_widget: ListWidgetType<StreamSubscription> | undefined; | let user_streams_list_widget: ListWidgetType<StreamSubscription> | undefined; | ||||||
| let user_groups_list_widget: ListWidgetType<UserGroup> | undefined; | let user_groups_list_widget: ListWidgetType<UserGroup> | undefined; | ||||||
| let user_profile_subscribe_widget: DropdownWidget | undefined; | let user_profile_subscribe_widget: DropdownWidget | undefined; | ||||||
|  | let user_group_pill_widget: user_group_pill.UserGroupPillWidget; | ||||||
| let toggler: components.Toggle; | let toggler: components.Toggle; | ||||||
| let bot_owner_dropdown_widget: DropdownWidget | undefined; | let bot_owner_dropdown_widget: DropdownWidget | undefined; | ||||||
| let original_values: (Record<string, unknown> & {user_id?: string | undefined}) | undefined; | let original_values: (Record<string, unknown> & {user_id?: string | undefined}) | undefined; | ||||||
| @@ -548,6 +551,82 @@ export function show_user_profile_access_error_modal(): void { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function add_user_to_groups(group_ids: number[], user_id: number, $alert_box: JQuery): void { | ||||||
|  |     const group_ids_successfully_added: number[] = []; | ||||||
|  |  | ||||||
|  |     function add_user_to_next_group(): void { | ||||||
|  |         if (group_ids_successfully_added.length >= group_ids.length) { | ||||||
|  |             if (group_ids_successfully_added.length > 0) { | ||||||
|  |                 ui_report.success( | ||||||
|  |                     $t_html({ | ||||||
|  |                         defaultMessage: "Added successfully!", | ||||||
|  |                     }), | ||||||
|  |                     $alert_box, | ||||||
|  |                     1200, | ||||||
|  |                 ); | ||||||
|  |                 clear_successful_pills(); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const group_id = group_ids[group_ids_successfully_added.length]!; | ||||||
|  |         const target_user_group = user_groups.get_user_group_from_id(group_id); | ||||||
|  |  | ||||||
|  |         if (!target_user_group) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         user_group_edit_members.edit_user_group_membership({ | ||||||
|  |             group: target_user_group, | ||||||
|  |             added: [user_id], | ||||||
|  |             success(): void { | ||||||
|  |                 group_ids_successfully_added.push(group_id); | ||||||
|  |                 add_user_to_next_group(); | ||||||
|  |             }, | ||||||
|  |             error(xhr): void { | ||||||
|  |                 const parsed = z | ||||||
|  |                     .object({ | ||||||
|  |                         result: z.literal("error"), | ||||||
|  |                         msg: z.string(), | ||||||
|  |                         code: z.string(), | ||||||
|  |                     }) | ||||||
|  |                     .safeParse(xhr?.responseJSON); | ||||||
|  |  | ||||||
|  |                 const error_message = people.is_my_user_id(user_id) | ||||||
|  |                     ? $t( | ||||||
|  |                           {defaultMessage: "Error joining {group_name}: {error}"}, | ||||||
|  |                           { | ||||||
|  |                               group_name: target_user_group.name, | ||||||
|  |                               error: parsed.success ? parsed.data.msg : "Unknown error", | ||||||
|  |                           }, | ||||||
|  |                       ) | ||||||
|  |                     : $t( | ||||||
|  |                           {defaultMessage: "Error adding user to {group_name}: {error}"}, | ||||||
|  |                           { | ||||||
|  |                               group_name: target_user_group.name, | ||||||
|  |                               error: parsed.success ? parsed.data.msg : "Unknown error", | ||||||
|  |                           }, | ||||||
|  |                       ); | ||||||
|  |  | ||||||
|  |                 ui_report.client_error(error_message, $alert_box); | ||||||
|  |                 clear_successful_pills(); | ||||||
|  |             }, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function clear_successful_pills(): void { | ||||||
|  |         for (const id of group_ids_successfully_added) { | ||||||
|  |             const $pill = $(`#user-group-to-add .pill-container .pill[data-user-group-id="${id}"]`); | ||||||
|  |             if ($pill.length > 0) { | ||||||
|  |                 user_group_pill_widget.removePill($pill[0]!, "close"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Start the process | ||||||
|  |     add_user_to_next_group(); | ||||||
|  | } | ||||||
|  |  | ||||||
| export function show_user_profile(user: User, default_tab_key = "profile-tab"): void { | export function show_user_profile(user: User, default_tab_key = "profile-tab"): void { | ||||||
|     const field_types = realm.custom_profile_field_types; |     const field_types = realm.custom_profile_field_types; | ||||||
|     const profile_data = realm.custom_profile_fields |     const profile_data = realm.custom_profile_fields | ||||||
| @@ -565,6 +644,9 @@ export function show_user_profile(user: User, default_tab_key = "profile-tab"): | |||||||
|         (people.can_admin_user(user) || user_unsub_streams.length > 0) && |         (people.can_admin_user(user) || user_unsub_streams.length > 0) && | ||||||
|         !user.is_system_bot && |         !user.is_system_bot && | ||||||
|         people.is_person_active(user.user_id); |         people.is_person_active(user.user_id); | ||||||
|  |     const show_user_group_container = | ||||||
|  |         user_group_picker_pill.get_user_groups_allowed_to_add_members().length > 0 && | ||||||
|  |         people.is_person_active(user.user_id); | ||||||
|     // We currently have the main UI for editing your own profile in |     // We currently have the main UI for editing your own profile in | ||||||
|     // settings, so can_manage_profile is artificially false for those. |     // settings, so can_manage_profile is artificially false for those. | ||||||
|     const can_manage_profile = |     const can_manage_profile = | ||||||
| @@ -586,6 +668,7 @@ export function show_user_profile(user: User, default_tab_key = "profile-tab"): | |||||||
|         profile_data, |         profile_data, | ||||||
|         should_add_guest_user_indicator: people.should_add_guest_user_indicator(user.user_id), |         should_add_guest_user_indicator: people.should_add_guest_user_indicator(user.user_id), | ||||||
|         show_user_subscribe_widget, |         show_user_subscribe_widget, | ||||||
|  |         show_user_group_container, | ||||||
|         user_avatar: people.medium_avatar_url_for_person(user), |         user_avatar: people.medium_avatar_url_for_person(user), | ||||||
|         user_circle_class: buddy_data.get_user_circle_class(user.user_id), |         user_circle_class: buddy_data.get_user_circle_class(user.user_id), | ||||||
|         user_id: user.user_id, |         user_id: user.user_id, | ||||||
| @@ -695,6 +778,13 @@ export function show_user_profile(user: User, default_tab_key = "profile-tab"): | |||||||
|     if (show_user_subscribe_widget) { |     if (show_user_subscribe_widget) { | ||||||
|         reset_subscribe_widget(); |         reset_subscribe_widget(); | ||||||
|     } |     } | ||||||
|  |     if (show_user_group_container) { | ||||||
|  |         const $user_group_pill_container = $("#user-group-to-add .pill-container"); | ||||||
|  |         user_group_pill_widget = user_group_picker_pill.create( | ||||||
|  |             $user_group_pill_container, | ||||||
|  |             user.user_id, | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function handle_remove_stream_subscription( | function handle_remove_stream_subscription( | ||||||
| @@ -1314,6 +1404,27 @@ export function initialize(): void { | |||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     $("body").on("click", "#user-profile-modal .add-groups-button", (e) => { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         const user_id = Number.parseInt($("#user-profile-modal").attr("data-user-id")!, 10); | ||||||
|  |         const $alert_box = $("#user-profile-groups-tab .user-profile-group-list-alert"); | ||||||
|  |         const item = $("#user-group-to-add .pill-container .input").text().trim(); | ||||||
|  |         if (item) { | ||||||
|  |             $("#user-group-to-add .pill-container .input").addClass("shake"); | ||||||
|  |             if ( | ||||||
|  |                 $("#user-group-to-add .pill-container .input").hasClass( | ||||||
|  |                     "show-outline-on-invalid-input", | ||||||
|  |                 ) | ||||||
|  |             ) { | ||||||
|  |                 $("#user-group-to-add .pill-container").addClass("invalid"); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const group_ids = user_group_pill.get_group_ids(user_group_pill_widget); | ||||||
|  |         add_user_to_groups(group_ids, user_id, $alert_box); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     $("body").on("click", "#user-profile-modal #clear_stream_search", (e) => { |     $("body").on("click", "#user-profile-modal #clear_stream_search", (e) => { | ||||||
|         const $input = $("#user-profile-streams-tab .stream-search"); |         const $input = $("#user-profile-streams-tab .stream-search"); | ||||||
|         $input.val(""); |         $input.val(""); | ||||||
|   | |||||||
| @@ -1506,3 +1506,19 @@ ul.popover-menu-list { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #groups-to-add { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: space-between; | ||||||
|  |     gap: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #user-group-to-add { | ||||||
|  |     flex: 1; | ||||||
|  |     min-width: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .add-groups-button-wrapper { | ||||||
|  |     flex: 0 0 auto; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <div class="add_subscribers_container"> | <div class="add_subscribers_container add-button-container"> | ||||||
|     <div class="pill-container person_picker"> |     <div class="pill-container person_picker"> | ||||||
|         <div class="input" contenteditable="true" |         <div class="input" contenteditable="true" | ||||||
|           data-placeholder="{{t 'Add subscribers. Use usergroup or #channelname to bulk add subscribers.' }}"> |           data-placeholder="{{t 'Add subscribers. Use usergroup or #channelname to bulk add subscribers.' }}"> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| <div class="add_members_container"> | <div class="add_members_container add-button-container"> | ||||||
|     <div class="pill-container person_picker"> |     <div class="pill-container person_picker"> | ||||||
|         <div class="input" contenteditable="true" |         <div class="input" contenteditable="true" | ||||||
|           data-placeholder="{{t 'Add users or groups. Use #channelname to add all subscribers.' }}"> |           data-placeholder="{{t 'Add users or groups. Use #channelname to add all subscribers.' }}"> | ||||||
|   | |||||||
| @@ -131,6 +131,28 @@ | |||||||
|  |  | ||||||
|                     <div class="tabcontent" id="user-profile-groups-tab"> |                     <div class="tabcontent" id="user-profile-groups-tab"> | ||||||
|                         <div class="alert user-profile-group-list-alert"></div> |                         <div class="alert user-profile-group-list-alert"></div> | ||||||
|  |                         {{#if show_user_group_container}} | ||||||
|  |                             <div class="header-section"> | ||||||
|  |                                 <h3 class="group-tab-element-header">{{t 'Add {full_name} to groups'}}</h3> | ||||||
|  |                             </div> | ||||||
|  |                             <div id="groups-to-add" class="add-button-container"> | ||||||
|  |                                 <div id="user-group-to-add"> | ||||||
|  |                                     <div class="add-user-group-container"> | ||||||
|  |                                         <div class="pill-container"> | ||||||
|  |                                             <div class="input" contenteditable="true" | ||||||
|  |                                               data-placeholder="{{t 'Add user groups' }}"> | ||||||
|  |                                                 {{~! Squash whitespace so that placeholder is displayed when empty. ~}} | ||||||
|  |                                             </div> | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div class="add-groups-button-wrapper"> | ||||||
|  |                                     <button type="button" name="subscribe" class="add-groups-button button small rounded"> | ||||||
|  |                                         {{t 'Add' }} | ||||||
|  |                                     </button> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         {{/if}} | ||||||
|                         <div class="group-list-top-section"> |                         <div class="group-list-top-section"> | ||||||
|                             <div class="header-section"> |                             <div class="header-section"> | ||||||
|                                 <h3 class="group-tab-element-header">{{t 'Group membership' }}</h3> |                                 <h3 class="group-tab-element-header">{{t 'Group membership' }}</h3> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user