diff --git a/help/invite-new-users.md b/help/invite-new-users.md index 8f6a6b9594..930e324412 100644 --- a/help/invite-new-users.md +++ b/help/invite-new-users.md @@ -57,8 +57,6 @@ permission to invite users. ## Create a reusable invitation link -{!admin-only.md!} - {start_tabs} {!invite-users.md!} diff --git a/help/restrict-account-creation.md b/help/restrict-account-creation.md index 832fe71f39..0d2b4de06c 100644 --- a/help/restrict-account-creation.md +++ b/help/restrict-account-creation.md @@ -16,9 +16,8 @@ account and how users access their accounts: to sign up (default), or you can [allow anyone to join](#set-whether-invitations-are-required-to-join) without an invitation. -* You can [restrict who can invite users](#change-who-can-send-invitations) to - your organization. To protect your organization, creating *reusable* invite - links is always limited to administrators. +* You can [restrict the ability to invite new users](#change-who-can-send-invitations) to + join your Zulip organzation to specific [roles](/help/roles-and-permissions). Regardless of whether invitations are required, you can: @@ -49,19 +48,13 @@ Regardless of whether invitations are required, you can: ## Change who can send invitations -{!owner-only.md!} - -You can restrict the ability to invite new users to join your Zulip organization -to specific [roles](/help/roles-and-permissions). To protect your organization, -while permission to send out individual email invitations is configurable, creating -*reusable* invitation links is always limited to administrators. - {start_tabs} {settings_tab|organization-permissions} 1. Under **Joining the organization**, configure - **Who can send email invitations to new users**. + **Who can send email invitations to new users** and + **Who can create reusable invitation links**. {!save-changes.md!} diff --git a/web/src/admin.js b/web/src/admin.js index a76ac4d0fe..56ec22063d 100644 --- a/web/src/admin.js +++ b/web/src/admin.js @@ -160,6 +160,7 @@ export function build_page() { email_address_visibility_values: settings_config.email_address_visibility_values, waiting_period_threshold_dropdown_values: settings_config.waiting_period_threshold_dropdown_values, + can_create_multiuse_invite: settings_data.user_can_create_multiuse_invite(), can_invite_users_by_email: settings_data.user_can_invite_users_by_email(), realm_invite_required: page_params.realm_invite_required, can_edit_user_groups: settings_data.user_can_edit_user_groups(), diff --git a/web/src/gear_menu.ts b/web/src/gear_menu.ts index eb00adf2ee..39deed7f0e 100644 --- a/web/src/gear_menu.ts +++ b/web/src/gear_menu.ts @@ -126,6 +126,7 @@ export function initialize(): void { server_needs_upgrade: page_params.server_needs_upgrade, version_display_string: version_display_string(), apps_page_url: page_params.apps_page_url, + can_create_multiuse_invite: settings_data.user_can_create_multiuse_invite(), can_invite_users_by_email: settings_data.user_can_invite_users_by_email(), corporate_enabled: page_params.corporate_enabled, is_guest: page_params.is_guest, diff --git a/web/src/group_permission_settings.ts b/web/src/group_permission_settings.ts index f59f9c60cb..56b789536d 100644 --- a/web/src/group_permission_settings.ts +++ b/web/src/group_permission_settings.ts @@ -15,6 +15,15 @@ const group_permission_config_dict = new Map([ allow_nobody_group: false, }, ], + [ + "create_multiuse_invite_group", + { + require_system_group: true, + allow_internet_group: false, + allow_owners_group: false, + allow_nobody_group: true, + }, + ], ]); export function get_group_permission_setting_config( diff --git a/web/src/invite.js b/web/src/invite.js index 3d3afb2e94..7d5c9d7067 100644 --- a/web/src/invite.js +++ b/web/src/invite.js @@ -273,7 +273,7 @@ function open_invite_user_modal(e) { $("#invite-user-modal .dialog_submit_button").prop("disabled", true); $("#email_invite_radio").prop("checked", true); - if (!page_params.is_admin) { + if (!settings_data.user_can_create_multiuse_invite()) { $("#generate_multiuse_invite_radio").prop("disabled", true); $("#generate_multiuse_invite_radio_container").addClass("control-label-disabled"); $("#generate_multiuse_invite_radio_container").addClass("disabled_setting_tooltip"); @@ -359,6 +359,16 @@ function open_invite_user_modal(e) { if (!user_has_email_set) { $("#invite-user-form :input").prop("disabled", !user_has_email_set); } + + if (!settings_data.user_can_invite_users_by_email()) { + $("#email_invite_radio").prop("disabled", true); + $("#email_invite_radio_container").addClass( + "control-label-disabled disabled_setting_tooltip", + ); + + $("#generate_multiuse_invite_radio").prop("checked", true); + $("#generate_multiuse_invite_radio").trigger("change"); + } } function invite_users() { diff --git a/web/src/page_params.ts b/web/src/page_params.ts index 3b4b2d0140..4c391012cf 100644 --- a/web/src/page_params.ts +++ b/web/src/page_params.ts @@ -25,6 +25,7 @@ export const page_params: { promote_sponsoring_zulip: boolean; realm_add_custom_emoji_policy: number; realm_avatar_changes_disabled: boolean; + realm_create_multiuse_invite_group: number; realm_create_private_stream_policy: number; realm_create_public_stream_policy: number; realm_create_web_public_stream_policy: number; diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index f62d60e0dc..f6516e2e1c 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -197,6 +197,7 @@ export function dispatch_normal_event(event) { user_group_edit_policy: noop, avatar_changes_disabled: settings_account.update_avatar_change_display, bot_creation_policy: settings_bots.update_bot_permissions_ui, + create_multiuse_invite_group: noop, create_public_stream_policy: noop, create_private_stream_policy: noop, create_web_public_stream_policy: noop, @@ -279,6 +280,12 @@ export function dispatch_normal_event(event) { settings_org.sync_realm_settings(key); } + if (key === "create_multiuse_invite_group") { + settings_invites.update_invite_user_panel(); + ui_init.update_invite_user_option(); + gear_menu.initialize(); + } + if (key === "edit_topic_policy") { message_live_update.rerender_messages_view(); } diff --git a/web/src/settings_data.ts b/web/src/settings_data.ts index 84de43f863..c417460614 100644 --- a/web/src/settings_data.ts +++ b/web/src/settings_data.ts @@ -1,5 +1,6 @@ import {page_params} from "./page_params"; import * as settings_config from "./settings_config"; +import * as user_groups from "./user_groups"; import {user_settings} from "./user_settings"; let user_join_date: Date; @@ -141,6 +142,16 @@ export function user_can_invite_users_by_email(): boolean { return user_has_permission(page_params.realm_invite_to_realm_policy); } +export function user_can_create_multiuse_invite(): boolean { + if (!page_params.user_id) { + return false; + } + return user_groups.is_user_in_group( + page_params.realm_create_multiuse_invite_group, + page_params.user_id, + ); +} + export function user_can_subscribe_other_users(): boolean { return user_has_permission(page_params.realm_invite_to_stream_policy); } diff --git a/web/src/settings_invites.js b/web/src/settings_invites.js index 95bf102420..f34787341b 100644 --- a/web/src/settings_invites.js +++ b/web/src/settings_invites.js @@ -296,7 +296,10 @@ export function update_invite_users_setting_tip() { export function update_invite_user_panel() { update_invite_users_setting_tip(); - if (!settings_data.user_can_invite_users_by_email()) { + if ( + !settings_data.user_can_invite_users_by_email() && + !settings_data.user_can_create_multiuse_invite() + ) { $("#admin-invites-list .invite-user-link").hide(); } else { $("#admin-invites-list .invite-user-link").show(); diff --git a/web/src/settings_org.js b/web/src/settings_org.js index 9ec32cc9e5..10efaa9996 100644 --- a/web/src/settings_org.js +++ b/web/src/settings_org.js @@ -28,6 +28,7 @@ import * as stream_data from "./stream_data"; import * as stream_edit from "./stream_edit"; import * as stream_settings_data from "./stream_settings_data"; import * as ui_report from "./ui_report"; +import * as user_groups from "./user_groups"; const meta = { loaded: false, @@ -53,7 +54,7 @@ export function maybe_disable_widgets() { $(".deactivate_realm_button").prop("disabled", true); $("#deactivate_realm_button_container").addClass("disabled_setting_tooltip"); $("#org-message-retention").find("input, select").prop("disabled", true); - $("#org-join-settings").find("input, select").prop("disabled", true); + $("#org-join-settings").find("input, select, button").prop("disabled", true); $("#id_realm_invite_required_label").parent().addClass("control-label-disabled"); return; } @@ -608,6 +609,7 @@ function update_dependent_subsettings(property_name) { export let default_code_language_widget = null; export let notifications_stream_widget = null; export let signup_notifications_stream_widget = null; +export let create_multiuse_invite_group_widget = null; export function get_widget_for_dropdown_list_settings(property_name) { switch (property_name) { @@ -617,6 +619,8 @@ export function get_widget_for_dropdown_list_settings(property_name) { return signup_notifications_stream_widget; case "realm_default_code_block_language": return default_code_language_widget; + case "realm_create_multiuse_invite_group": + return create_multiuse_invite_group_widget; case "can_remove_subscribers_group": return stream_edit.can_remove_subscribers_group_widget; default: @@ -655,6 +659,7 @@ export function discard_property_element_changes(elem, for_realm_default_setting case "realm_signup_notifications_stream_id": case "realm_default_code_block_language": case "can_remove_subscribers_group": + case "realm_create_multiuse_invite_group": set_dropdown_list_widget_setting_value(property_name, property_value); break; case "realm_default_language": @@ -971,6 +976,7 @@ export function check_property_changed(elem, for_realm_default_settings, sub) { case "realm_signup_notifications_stream_id": case "realm_default_code_block_language": case "can_remove_subscribers_group": + case "realm_create_multiuse_invite_group": proposed_val = get_dropdown_list_widget_setting_value($elem); break; case "email_notifications_batching_period_seconds": @@ -1157,6 +1163,31 @@ export function init_dropdown_widgets() { }, }); default_code_language_widget.setup(); + + create_multiuse_invite_group_widget = new dropdown_widget.DropdownWidget({ + widget_name: "realm_create_multiuse_invite_group", + get_options: () => + user_groups.get_realm_user_groups_for_dropdown_list_widget( + "create_multiuse_invite_group", + ), + $events_container: $("#settings_overlay_container #organization-permissions"), + item_click_callback(event, dropdown) { + dropdown.hide(); + event.preventDefault(); + event.stopPropagation(); + create_multiuse_invite_group_widget.render(); + save_discard_widget_status_handler($("#org-join-settings")); + }, + tippy_props: { + placement: "bottom-start", + }, + default_id: page_params.realm_create_multiuse_invite_group, + unique_id_type: dropdown_widget.DATA_TYPES.NUMBER, + on_mount_callback(dropdown) { + $(dropdown.popper).css("min-width", "300px"); + }, + }); + create_multiuse_invite_group_widget.setup(); } function check_maximum_valid_value($custom_input_elem, property_name) { diff --git a/web/src/tippyjs.js b/web/src/tippyjs.js index b514f42837..dd5342265b 100644 --- a/web/src/tippyjs.js +++ b/web/src/tippyjs.js @@ -373,6 +373,18 @@ export function initialize() { }, }); + delegate("body", { + target: ["#email_invite_radio_container.disabled_setting_tooltip"], + content: $t({ + defaultMessage: + "You do not have permissions to send email invitations in this organization.", + }), + appendTo: () => document.body, + onHidden(instance) { + instance.destroy(); + }, + }); + delegate("body", { target: "#pm_tooltip_container", onShow(instance) { diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 57c66b59c6..3daf815a44 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -190,7 +190,10 @@ function initialize_left_sidebar() { } export function update_invite_user_option() { - if (!settings_data.user_can_invite_users_by_email()) { + if ( + !settings_data.user_can_invite_users_by_email() && + !settings_data.user_can_create_multiuse_invite() + ) { $("#right-sidebar .invite-user-link").hide(); } else { $("#right-sidebar .invite-user-link").show(); diff --git a/web/styles/dark_theme.css b/web/styles/dark_theme.css index 0586f54837..394bafaab1 100644 --- a/web/styles/dark_theme.css +++ b/web/styles/dark_theme.css @@ -420,7 +420,8 @@ /* these are converting grey things to "new grey" */ :disabled, input:not([type="radio"]):read-only, - textarea:read-only { + textarea:read-only, + #org-join-settings .dropdown-widget-button:disabled { color: inherit; opacity: 0.5; } @@ -479,7 +480,8 @@ .pill-container, .user-status-content-wrapper, #custom-expiration-time-input, - #searchbox #search_query { + #searchbox #search_query, + #org-join-settings .dropdown-widget-button { background-color: hsl(0deg 0% 0% / 20%); border-color: hsl(0deg 0% 0% / 60%); color: inherit; diff --git a/web/styles/settings.css b/web/styles/settings.css index 995071c6db..5fb7f73744 100644 --- a/web/styles/settings.css +++ b/web/styles/settings.css @@ -775,6 +775,24 @@ input[type="checkbox"] { margin: 20px 0 0; } +#org-join-settings { + .dropdown-widget-button { + width: 325px; + color: hsl(0deg 0% 33%); + + & i { + font-size: 10px; + margin-right: -3px; + } + + &:disabled { + cursor: not-allowed; + background-color: hsl(0deg 0% 93%); + opacity: 0.7; + } + } +} + .progressive-table-wrapper { position: relative; max-height: calc(95vh - 220px); diff --git a/web/styles/zulip.css b/web/styles/zulip.css index f703e8f7fe..431101a54e 100644 --- a/web/styles/zulip.css +++ b/web/styles/zulip.css @@ -2521,6 +2521,10 @@ select.invite-as { .invite_type_radio_section { margin: 2px 2px 2px 5px; + & div { + width: fit-content; + } + & input[type="radio"] { cursor: pointer; @@ -2535,10 +2539,6 @@ select.invite-as { } } -#generate_multiuse_invite_radio_container { - width: fit-content; -} - #custom-expiration-time-input { width: 5ch; margin-right: 15px; diff --git a/web/templates/gear_menu.hbs b/web/templates/gear_menu.hbs index 64d51fcbce..c641a1a45b 100644 --- a/web/templates/gear_menu.hbs +++ b/web/templates/gear_menu.hbs @@ -155,7 +155,7 @@ {{/if}} - {{#if can_invite_users_by_email}} + {{#if (or can_invite_users_by_email can_create_multiuse_invite)}}
  • {{t 'Invite users' }} diff --git a/web/templates/settings/organization_permissions_admin.hbs b/web/templates/settings/organization_permissions_admin.hbs index 00b38816e5..633e8b34c2 100644 --- a/web/templates/settings/organization_permissions_admin.hbs +++ b/web/templates/settings/organization_permissions_admin.hbs @@ -23,6 +23,11 @@ + {{> ../dropdown_widget_with_label + widget_name="realm_create_multiuse_invite_group" + label=(t 'Who can create reusable invitation links') + value_type="number"}} +