diff options
Diffstat (limited to 'app/assets/javascripts/invite_members')
11 files changed, 295 insertions, 131 deletions
diff --git a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue index 31b7fd4cc42..b4e9a3a1559 100644 --- a/app/assets/javascripts/invite_members/components/import_project_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/import_project_members_modal.vue @@ -5,6 +5,10 @@ import { importProjectMembers } from '~/api/projects_api'; import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { s__, __, sprintf } from '~/locale'; import eventHub from '../event_hub'; +import { + displaySuccessfulInvitationAlert, + reloadOnInvitationSuccess, +} from '../utils/trigger_successful_invite_alert'; import ProjectSelect from './project_select.vue'; export default { @@ -24,6 +28,11 @@ export default { type: String, required: true, }, + reloadPageOnSubmit: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -59,6 +68,10 @@ export default { }, }, mounted() { + if (this.reloadPageOnSubmit) { + displaySuccessfulInvitationAlert(); + } + eventHub.$on('openProjectMembersModal', () => { this.openModal(); }); @@ -74,16 +87,22 @@ export default { submitImport() { this.isLoading = true; return importProjectMembers(this.projectId, this.projectToBeImported.id) - .then(this.showToastMessage) + .then(this.onInviteSuccess) .catch(this.showErrorAlert) .finally(() => { this.isLoading = false; this.projectToBeImported = {}; }); }, + onInviteSuccess() { + if (this.reloadPageOnSubmit) { + reloadOnInvitationSuccess(); + } else { + this.showToastMessage(); + } + }, showToastMessage() { this.$toast.show(this.$options.i18n.successMessage, this.$options.toastOptions); - this.closeModal(); }, showErrorAlert() { diff --git a/app/assets/javascripts/invite_members/components/invite_group_notification.vue b/app/assets/javascripts/invite_members/components/invite_group_notification.vue new file mode 100644 index 00000000000..767675cc64c --- /dev/null +++ b/app/assets/javascripts/invite_members/components/invite_group_notification.vue @@ -0,0 +1,37 @@ +<script> +import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import { GROUP_MODAL_ALERT_BODY } from '../constants'; + +const SHARE_GROUP_LINK = + 'https://docs.gitlab.com/ee/user/group/manage.html#share-a-group-with-another-group'; + +export default { + SHARE_GROUP_LINK, + name: 'InviteGroupNotification', + components: { GlAlert, GlSprintf, GlLink }, + inject: ['freeUsersLimit'], + props: { + name: { + type: String, + required: true, + }, + }, + i18n: { + body: GROUP_MODAL_ALERT_BODY, + }, +}; +</script> + +<template> + <gl-alert variant="warning" :dismissible="false"> + <gl-sprintf :message="$options.i18n.body"> + <template #link="{ content }"> + <gl-link :href="$options.SHARE_GROUP_LINK" target="_blank" class="gl-label-link">{{ + content + }}</gl-link> + </template> + + <template #count>{{ freeUsersLimit }}</template> + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue index 2ad4bb1a11a..3be3b9df747 100644 --- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue @@ -6,13 +6,19 @@ import InviteModalBase from 'ee_else_ce/invite_members/components/invite_modal_b import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants'; import eventHub from '../event_hub'; import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message'; +import { + displaySuccessfulInvitationAlert, + reloadOnInvitationSuccess, +} from '../utils/trigger_successful_invite_alert'; import GroupSelect from './group_select.vue'; +import InviteGroupNotification from './invite_group_notification.vue'; export default { name: 'InviteMembersModal', components: { GroupSelect, InviteModalBase, + InviteGroupNotification, }, props: { id: { @@ -31,6 +37,10 @@ export default { type: String, required: true, }, + fullPath: { + type: String, + required: true, + }, accessLevels: { type: Object, required: true, @@ -57,6 +67,15 @@ export default { type: Array, required: true, }, + freeUserCapEnabled: { + type: Boolean, + required: true, + }, + reloadPageOnSubmit: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -85,6 +104,10 @@ export default { }, }, mounted() { + if (this.reloadPageOnSubmit) { + displaySuccessfulInvitationAlert(); + } + eventHub.$on('openGroupModal', () => { this.openModal(); }); @@ -114,7 +137,7 @@ export default { expires_at: expiresAt, }) .then(() => { - this.showSuccessMessage(); + this.onInviteSuccess(); }) .catch((e) => { this.showInvalidFeedbackMessage(e); @@ -128,6 +151,13 @@ export default { this.isLoading = false; this.groupToBeSharedWith = {}; }, + onInviteSuccess() { + if (this.reloadPageOnSubmit) { + reloadOnInvitationSuccess(); + } else { + this.showSuccessMessage(); + } + }, showSuccessMessage() { this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions); this.closeModal(); @@ -155,9 +185,14 @@ export default { :root-group-id="rootId" :invalid-feedback-message="invalidFeedbackMessage" :is-loading="isLoading" + :full-path="fullPath" @reset="resetFields" @submit="sendInvite" > + <template #alert> + <invite-group-notification v-if="freeUserCapEnabled" :name="name" /> + </template> + <template #select> <group-select v-model="groupToBeSharedWith" diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue index f61e822bf7e..fbb547c28ff 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue @@ -29,6 +29,10 @@ import eventHub from '../event_hub'; import { responseFromSuccess } from '../utils/response_message_parser'; import { memberName } from '../utils/member_utils'; import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message'; +import { + displaySuccessfulInvitationAlert, + reloadOnInvitationSuccess, +} from '../utils/trigger_successful_invite_alert'; import ModalConfetti from './confetti.vue'; import MembersTokenSelect from './members_token_select.vue'; import UserLimitNotification from './user_limit_notification.vue'; @@ -98,11 +102,20 @@ export default { type: Array, required: true, }, + fullPath: { + type: String, + required: true, + }, usersLimitDataset: { type: Object, required: false, default: () => ({}), }, + reloadPageOnSubmit: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -119,7 +132,7 @@ export default { selectedAccessLevel: undefined, errorsLimit: 2, isErrorsSectionExpanded: false, - emptyInvitesError: false, + shouldShowEmptyInvitesAlert: false, }; }, computed: { @@ -204,12 +217,15 @@ export default { count: this.errorsExpanded.length, }); }, + formGroupDescription() { + return this.invalidFeedbackMessage ? null : this.$options.labels.placeHolder; + }, }, watch: { isEmptyInvites: { handler(updatedValue) { // nothing to do if the invites are **still** empty and the emptyInvites were never set from submit - if (!updatedValue && !this.emptyInvitesError) { + if (!updatedValue && !this.shouldShowEmptyInvitesAlert) { return; } @@ -218,6 +234,10 @@ export default { }, }, mounted() { + if (this.reloadPageOnSubmit) { + displaySuccessfulInvitationAlert(); + } + eventHub.$on('openModal', (options) => { this.openModal(options); if (this.isOnLearnGitlab) { @@ -258,16 +278,17 @@ export default { const tracking = new ExperimentTracking(experimentName); tracking.event(eventName); }, - showEmptyInvitesError() { - this.invalidFeedbackMessage = this.$options.labels.emptyInvitesErrorText; - this.emptyInvitesError = true; + showEmptyInvitesAlert() { + this.invalidFeedbackMessage = this.$options.labels.placeHolder; + this.shouldShowEmptyInvitesAlert = true; + this.$refs.alerts.focus(); }, sendInvite({ accessLevel, expiresAt }) { this.isLoading = true; this.clearValidation(); if (!this.isEmptyInvites) { - this.showEmptyInvitesError(); + this.showEmptyInvitesAlert(); return; } @@ -298,7 +319,7 @@ export default { if (error) { this.showMemberErrors(message); } else { - this.showSuccessMessage(); + this.onInviteSuccess(); } }) .catch((e) => this.showInvalidFeedbackMessage(e)) @@ -308,6 +329,7 @@ export default { }, showMemberErrors(message) { this.invalidMembers = message; + this.$refs.alerts.focus(); }, tokenName(username) { // initial token creation hits this and nothing is found... so safe navigation @@ -322,6 +344,7 @@ export default { resetFields() { this.clearValidation(); this.isLoading = false; + this.shouldShowEmptyInvitesAlert = false; this.newUsersToInvite = []; this.selectedTasksToBeDone = []; [this.selectedTaskProject] = this.projects; @@ -329,6 +352,13 @@ export default { changeSelectedTaskProject(project) { this.selectedTaskProject = project; }, + onInviteSuccess() { + if (this.reloadPageOnSubmit) { + reloadOnInvitationSuccess(); + } else { + this.showSuccessMessage(); + } + }, showSuccessMessage() { if (this.isOnLearnGitlab) { eventHub.$emit('showSuccessfulInvitationsAlert'); @@ -347,7 +377,7 @@ export default { }, clearEmptyInviteError() { this.invalidFeedbackMessage = ''; - this.emptyInvitesError = false; + this.shouldShowEmptyInvitesAlert = false; }, removeToken(token) { delete this.invalidMembers[memberName(token)]; @@ -370,12 +400,13 @@ export default { :help-link="helpLink" :label-intro-text="labelIntroText" :label-search-field="$options.labels.searchField" - :form-group-description="$options.labels.placeHolder" + :form-group-description="formGroupDescription" :invalid-feedback-message="invalidFeedbackMessage" :is-loading="isLoading" :new-users-to-invite="newUsersToInvite" :root-group-id="rootId" :users-limit-dataset="usersLimitDataset" + :full-path="fullPath" @reset="resetFields" @submit="sendInvite" @access-level="onAccessLevelUpdate" @@ -390,59 +421,77 @@ export default { </template> <template #alert> - <gl-alert - v-if="hasInvalidMembers" - variant="danger" - :dismissible="false" - :title="memberErrorTitle" - data-testid="alert-member-error" - > - {{ $options.labels.memberErrorListText }} - <ul class="gl-pl-5 gl-mb-0"> - <li v-for="error in errorsLimited" :key="error.member" data-testid="errors-limited-item"> - <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} - </li> - </ul> - <template v-if="shouldErrorsSectionExpand"> - <gl-collapse v-model="isErrorsSectionExpanded"> - <ul class="gl-pl-5 gl-mb-0"> - <li - v-for="error in errorsExpanded" - :key="error.member" - data-testid="errors-expanded-item" - > - <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} - </li> - </ul> - </gl-collapse> - <gl-button - class="gl-text-decoration-none! gl-shadow-none! gl-mt-3" - data-testid="accordion-button" - variant="link" - @click="toggleErrorExpansion" - > - {{ errorCollapseText }} - <gl-icon - name="chevron-down" - class="gl-transition-medium" - :class="{ 'gl-rotate-180': isErrorsSectionExpanded }" - /> - </gl-button> - </template> - </gl-alert> - <user-limit-notification - v-else-if="showUserLimitNotification" - :limit-variant="limitVariant" - :users-limit-dataset="usersLimitDataset" - /> + <div ref="alerts" tabindex="-1"> + <gl-alert + v-if="shouldShowEmptyInvitesAlert" + id="empty-invites-alert" + class="gl-mb-4" + variant="danger" + :dismissible="false" + data-testid="empty-invites-alert" + > + {{ $options.labels.emptyInvitesAlertText }} + </gl-alert> + <gl-alert + v-if="hasInvalidMembers" + class="gl-mb-4" + variant="danger" + :dismissible="false" + :title="memberErrorTitle" + data-testid="alert-member-error" + > + {{ $options.labels.memberErrorListText }} + <ul class="gl-pl-5 gl-mb-0"> + <li + v-for="error in errorsLimited" + :key="error.member" + data-testid="errors-limited-item" + > + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + <template v-if="shouldErrorsSectionExpand"> + <gl-collapse v-model="isErrorsSectionExpanded"> + <ul class="gl-pl-5 gl-mb-0"> + <li + v-for="error in errorsExpanded" + :key="error.member" + data-testid="errors-expanded-item" + > + <strong>{{ error.displayedMemberName }}:</strong> {{ error.message }} + </li> + </ul> + </gl-collapse> + <gl-button + class="gl-text-decoration-none! gl-shadow-none! gl-mt-3" + data-testid="accordion-button" + variant="link" + @click="toggleErrorExpansion" + > + {{ errorCollapseText }} + <gl-icon + name="chevron-down" + class="gl-transition-medium" + :class="{ 'gl-rotate-180': isErrorsSectionExpanded }" + /> + </gl-button> + </template> + </gl-alert> + <user-limit-notification + v-else-if="showUserLimitNotification" + :limit-variant="limitVariant" + :users-limit-dataset="usersLimitDataset" + /> + </div> </template> - <template #select="{ exceptionState, labelId }"> + <template #select="{ exceptionState, inputId }"> <members-token-select v-model="newUsersToInvite" class="gl-mb-2" + aria-labelledby="empty-invites-alert" + :input-id="inputId" :exception-state="exceptionState" - :aria-labelledby="labelId" :users-filter="usersFilter" :filter-id="filterId" :invalid-members="invalidMembers" diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index e3511a49fc5..2cbd681c67d 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -1,14 +1,5 @@ <script> -import { - GlFormGroup, - GlModal, - GlDropdown, - GlDropdownItem, - GlDatepicker, - GlLink, - GlSprintf, - GlFormInput, -} from '@gitlab/ui'; +import { GlFormGroup, GlFormSelect, GlModal, GlDatepicker, GlLink, GlSprintf } from '@gitlab/ui'; import Tracking from '~/tracking'; import { sprintf } from '~/locale'; import ContentTransition from '~/vue_shared/components/content_transition.vue'; @@ -37,13 +28,11 @@ const DEFAULT_SLOTS = [ export default { components: { GlFormGroup, + GlFormSelect, GlDatepicker, GlLink, GlModal, - GlDropdown, - GlDropdownItem, GlSprintf, - GlFormInput, ContentTransition, }, mixins: [Tracking.mixin()], @@ -141,14 +130,23 @@ export default { }; }, computed: { + accessLevelsOptions() { + return Object.entries(this.accessLevels).map(([text, value]) => ({ text, value })); + }, introText() { return sprintf(this.labelIntroText, { name: this.name }); }, exceptionState() { return this.invalidFeedbackMessage ? false : null; }, - selectLabelId() { - return `${this.modalId}_select`; + selectId() { + return `${this.modalId}_search`; + }, + dropdownId() { + return `${this.modalId}_dropdown`; + }, + datepickerId() { + return `${this.modalId}_expires_at`; }, selectedRoleName() { return Object.keys(this.accessLevels).find( @@ -218,9 +216,6 @@ export default { this.$emit('cancel'); }, - changeSelectedItem(item) { - this.selectedAccessLevel = item; - }, onSubmit(e) { // We never want to hide when submitting e.preventDefault(); @@ -279,64 +274,50 @@ export default { <slot name="alert"></slot> <gl-form-group + :label="labelSearchField" + :label-for="selectId" :invalid-feedback="invalidFeedbackMessage" :state="exceptionState" :description="formGroupDescription" data-testid="members-form-group" > - <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> - <slot name="select" v-bind="{ exceptionState, labelId: selectLabelId }"></slot> + <slot name="select" v-bind="{ exceptionState, inputId: selectId }"></slot> </gl-form-group> - <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-dropdown - class="gl-shadow-none gl-w-full" + <gl-form-group + class="gl-w-half gl-xs-w-full" + :label="$options.ACCESS_LEVEL" + :label-for="dropdownId" + > + <template #description> + <gl-sprintf :message="$options.READ_MORE_TEXT"> + <template #link="{ content }"> + <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </template> + <gl-form-select + :id="dropdownId" + v-model="selectedAccessLevel" data-qa-selector="access_level_dropdown" - v-bind="$attrs" - :text="selectedRoleName" - > - <template v-for="(key, item) in accessLevels"> - <gl-dropdown-item - :key="key" - active-class="is-active" - is-check-item - :is-checked="key === selectedAccessLevel" - @click="changeSelectedItem(key)" - > - <div>{{ item }}</div> - </gl-dropdown-item> - </template> - </gl-dropdown> - </div> - - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-sprintf :message="$options.READ_MORE_TEXT"> - <template #link="{ content }"> - <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </div> + :options="accessLevelsOptions" + /> + </gl-form-group> - <label class="gl-mt-5 gl-display-block" for="expires_at">{{ - $options.ACCESS_EXPIRE_DATE - }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block"> + <gl-form-group + class="gl-w-half gl-xs-w-full" + :label="$options.ACCESS_EXPIRE_DATE" + :label-for="datepickerId" + > <gl-datepicker v-model="selectedDate" - class="gl-display-inline!" + :input-id="datepickerId" + class="gl-display-block!" :min-date="minDate" :target="null" - > - <template #default="{ formattedDate }"> - <gl-form-input - class="gl-w-full" - :value="formattedDate" - :placeholder="__(`YYYY-MM-DD`)" - /> - </template> - </gl-datepicker> - </div> + /> + </gl-form-group> + <slot name="form-after"></slot> </template> diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue index 2ddb04e1eeb..68602068699 100644 --- a/app/assets/javascripts/invite_members/components/members_token_select.vue +++ b/app/assets/javascripts/invite_members/components/members_token_select.vue @@ -49,6 +49,11 @@ export default { type: Object, required: true, }, + inputId: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -84,6 +89,13 @@ export default { hasInvalidMembers() { return !isEmpty(this.invalidMembers); }, + textInputAttrs() { + return { + 'data-testid': 'members-token-select-input', + 'data-qa-selector': 'members_token_select_input', + id: this.inputId, + }; + }, }, watch: { // We might not really want this to be *reactive* since we want the "class" state to be @@ -183,10 +195,7 @@ export default { :hide-dropdown-with-no-items="hideDropdownWithNoItems" :placeholder="placeholderText" :aria-labelledby="ariaLabelledby" - :text-input-attrs="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { - 'data-testid': 'members-token-select-input', - 'data-qa-selector': 'members_token_select_input', - } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + :text-input-attrs="textInputAttrs" @blur="handleBlur" @text-input="handleTextInput" @input="handleInput" diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js index de7b1019782..a894eb24d38 100644 --- a/app/assets/javascripts/invite_members/constants.js +++ b/app/assets/javascripts/invite_members/constants.js @@ -9,6 +9,7 @@ export const INVITE_MEMBERS_FOR_TASK = { view: 'modal_opened_from_email', submit: 'submit', }; +export const TOAST_MESSAGE_LOCALSTORAGE_KEY = 'members_invited_successfully'; export const GROUP_FILTERS = { ALL: 'all', @@ -57,6 +58,10 @@ export const GROUP_MODAL_TO_PROJECT_DEFAULT_INTRO_TEXT = s__( "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} project.", ); +export const GROUP_MODAL_ALERT_BODY = s__( + 'InviteMembersModal| Inviting a group %{linkStart}adds its members to your group%{linkEnd}, including members who join after the invite. This might put your group over the free %{count} user limit.', +); + export const GROUP_SEARCH_FIELD = s__('InviteMembersModal|Select a group to invite'); export const GROUP_PLACEHOLDER = s__('InviteMembersModal|Search for a group to invite'); @@ -77,9 +82,7 @@ export const MEMBER_ERROR_LIST_TEXT = s__( ); export const COLLAPSED_ERRORS = s__('InviteMembersModal|Show more (%{count})'); export const EXPANDED_ERRORS = s__('InviteMembersModal|Show less'); -export const EMPTY_INVITES_ERROR_TEXT = s__( - 'InviteMembersModal|Please select members or type email addresses to invite', -); +export const EMPTY_INVITES_ALERT_TEXT = s__('InviteMembersModal|Please add members to invite'); export const MEMBER_MODAL_LABELS = { modal: { @@ -117,7 +120,7 @@ export const MEMBER_MODAL_LABELS = { memberErrorListText: MEMBER_ERROR_LIST_TEXT, collapsedErrors: COLLAPSED_ERRORS, expandedErrors: EXPANDED_ERRORS, - emptyInvitesErrorText: EMPTY_INVITES_ERROR_TEXT, + emptyInvitesAlertText: EMPTY_INVITES_ALERT_TEXT, }; export const GROUP_MODAL_LABELS = { diff --git a/app/assets/javascripts/invite_members/init_import_project_members_modal.js b/app/assets/javascripts/invite_members/init_import_project_members_modal.js index daaa1315884..227d8395250 100644 --- a/app/assets/javascripts/invite_members/init_import_project_members_modal.js +++ b/app/assets/javascripts/invite_members/init_import_project_members_modal.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import ImportProjectMembersModal from '~/invite_members/components/import_project_members_modal.vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; export default function initImportProjectMembersModal() { const el = document.querySelector('.js-import-project-members-modal'); @@ -8,7 +9,7 @@ export default function initImportProjectMembersModal() { return false; } - const { projectId, projectName } = el.dataset; + const { projectId, projectName, reloadPageOnSubmit } = el.dataset; return new Vue({ el, @@ -17,6 +18,7 @@ export default function initImportProjectMembersModal() { props: { projectId, projectName, + reloadPageOnSubmit: parseBoolean(reloadPageOnSubmit), }, }), }); diff --git a/app/assets/javascripts/invite_members/init_invite_groups_modal.js b/app/assets/javascripts/invite_members/init_invite_groups_modal.js index be1576ad0b0..53b756b610f 100644 --- a/app/assets/javascripts/invite_members/init_invite_groups_modal.js +++ b/app/assets/javascripts/invite_members/init_invite_groups_modal.js @@ -28,6 +28,9 @@ export default function initInviteGroupsModal() { return new Vue({ el, + provide: { + freeUsersLimit: parseInt(el.dataset.freeUsersLimit, 10), + }, render: (createElement) => createElement(InviteGroupsModal, { props: { @@ -38,6 +41,8 @@ export default function initInviteGroupsModal() { groupSelectFilter: el.dataset.groupsFilter, groupSelectParentId: parseInt(el.dataset.parentId, 10), invalidGroups: JSON.parse(el.dataset.invalidGroups || '[]'), + freeUserCapEnabled: parseBoolean(el.dataset.freeUserCapEnabled), + reloadPageOnSubmit: parseBoolean(el.dataset.reloadPageOnSubmit), }, }), }); diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js index a4be3f205a3..842ab07f368 100644 --- a/app/assets/javascripts/invite_members/init_invite_members_modal.js +++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js @@ -41,6 +41,7 @@ export default (function initInviteMembersModal() { usersLimitDataset: convertObjectPropsToCamelCase( JSON.parse(el.dataset.usersLimitDataset || '{}'), ), + reloadPageOnSubmit: parseBoolean(el.dataset.reloadPageOnSubmit), }, }), }); diff --git a/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js b/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js new file mode 100644 index 00000000000..4d3a7951265 --- /dev/null +++ b/app/assets/javascripts/invite_members/utils/trigger_successful_invite_alert.js @@ -0,0 +1,23 @@ +import { createAlert } from '~/flash'; +import AccessorUtilities from '~/lib/utils/accessor'; + +import { TOAST_MESSAGE_LOCALSTORAGE_KEY, TOAST_MESSAGE_SUCCESSFUL } from '../constants'; + +export function displaySuccessfulInvitationAlert() { + if (!AccessorUtilities.canUseLocalStorage()) { + return; + } + + const showAlert = Boolean(localStorage.getItem(TOAST_MESSAGE_LOCALSTORAGE_KEY)); + if (showAlert) { + localStorage.removeItem(TOAST_MESSAGE_LOCALSTORAGE_KEY); + createAlert({ message: TOAST_MESSAGE_SUCCESSFUL, variant: 'info' }); + } +} + +export function reloadOnInvitationSuccess() { + if (AccessorUtilities.canUseLocalStorage()) { + localStorage.setItem(TOAST_MESSAGE_LOCALSTORAGE_KEY, 'true'); + } + window.location.reload(); +} |