diff --git a/ee/app/assets/javascripts/subscriptions/new/components/checkout/subscription_details.vue b/ee/app/assets/javascripts/subscriptions/new/components/checkout/subscription_details.vue index 69ba8ccae4c1fbd256fe455fe2743bf71a8f346e..d34dcdccdcf967e52f97791c1db88db7b8e505c1 100644 --- a/ee/app/assets/javascripts/subscriptions/new/components/checkout/subscription_details.vue +++ b/ee/app/assets/javascripts/subscriptions/new/components/checkout/subscription_details.vue @@ -83,6 +83,7 @@ export default { 'selectedGroupId', 'selectedGroupData', 'isGroupSelected', + 'maximumSeatLimit', 'selectedGroupName', 'isSelectedGroupPresent', ]), @@ -198,6 +199,9 @@ export default { shouldDisableNumberOfUsers() { return this.isNewUser && !this.isSetupForCompany; }, + hasMaximumSeatLimit() { + return Boolean(this.maximumSeatLimit); + }, }, watch: { billableData: { @@ -258,6 +262,9 @@ export default { numberOfUsersLabelDescription: s__( 'Checkout|Must be %{minimumNumberOfUsers} (your seats in use) or more.', ), + maximumSeatLimitMessage: s__( + 'Checkout|The maximum amount of seats available to buy is %{maximumSeatLimit}. %{maxSeatLinkStart}Learn more.%{maxSeatLinkEnd}', + ), numberOfUsersLabelDescriptionFreeUserCap: s__( 'Checkout|Must be %{minimumNumberOfUsers} (your seats in use, plus all over limit members) or more. To buy fewer seats, remove members from the group.', ), @@ -332,14 +339,25 @@ export default { > <gl-form-input ref="organization-name" v-model="organizationNameModel" type="text" /> </gl-form-group> - <div class="combined d-flex"> + <div class="combined gl-display-flex"> <gl-form-group data-testid="number-of-users-field" :label="$options.i18n.numberOfUsersLabel" label-size="sm" - class="gl-mb-0" + class="gl-mb-5" :label-description="numberOfUsersLabelDescription" > + <template v-if="hasMaximumSeatLimit" #label-description> + <div class="gl-mb-3"> + <gl-sprintf :message="$options.i18n.maximumSeatLimitMessage" class=""> + <template #maximumSeatLimit>{{ maximumSeatLimit }}</template> + <template #maxSeatLink="{ content }"> + <gl-link href="" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> + </template> + <gl-form-input ref="number-of-users" v-model.number="numberOfUsersModel" @@ -363,7 +381,8 @@ export default { </gl-form-group> </div> <gl-alert - class="gl-my-5" + v-if="!hasMaximumSeatLimit" + class="gl-mb-5" :dismissible="false" variant="info" data-testid="qsr-overage-message" diff --git a/ee/app/assets/javascripts/subscriptions/new/constants.js b/ee/app/assets/javascripts/subscriptions/new/constants.js index 3175b1bdf9fa90788be269b9c7164bc7effce73d..0ddb129f842695ea1c0dc87a4812b21d9196f3c2 100644 --- a/ee/app/assets/javascripts/subscriptions/new/constants.js +++ b/ee/app/assets/javascripts/subscriptions/new/constants.js @@ -3,7 +3,6 @@ import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility'; export const TAX_RATE = 0; export const NEW_GROUP = 'new_group'; -export const ULTIMATE = 'ultimate'; export const PurchaseEvent = Object.freeze({ ERROR: 'error', diff --git a/ee/app/assets/javascripts/subscriptions/new/store/getters.js b/ee/app/assets/javascripts/subscriptions/new/store/getters.js index 84085132a36cf6ea112c3d5ef7a26dc387b5ab1f..b9d4b821fae0fbc84b3b01ed25625b48c4ebfe66 100644 --- a/ee/app/assets/javascripts/subscriptions/new/store/getters.js +++ b/ee/app/assets/javascripts/subscriptions/new/store/getters.js @@ -1,18 +1,13 @@ import { s__ } from '~/locale'; -import { - NEW_GROUP, - ULTIMATE, - CHARGE_PROCESSING_TYPE, - DISCOUNT_PROCESSING_TYPE, -} from '../constants'; +import { NEW_GROUP, CHARGE_PROCESSING_TYPE, DISCOUNT_PROCESSING_TYPE } from '../constants'; export const selectedPlanText = (state, getters) => getters.selectedPlanDetails.text; export const selectedPlanDetails = (state) => state.availablePlans.find((plan) => plan.value === state.selectedPlan); -export const isUltimatePlan = (state, getters) => { - return getters.selectedPlanDetails?.code === ULTIMATE; +export const maximumSeatLimit = (state, getters) => { + return getters.selectedPlanDetails?.maximumSeatLimit || 0; }; export const confirmOrderParams = (state, getters) => ({ diff --git a/ee/app/assets/javascripts/subscriptions/new/store/state.js b/ee/app/assets/javascripts/subscriptions/new/store/state.js index 97b3065a177e2985352e529c996aa79f50f1b153..fae49f689a166996e4fa08c7a4c59992beb563f0 100644 --- a/ee/app/assets/javascripts/subscriptions/new/store/state.js +++ b/ee/app/assets/javascripts/subscriptions/new/store/state.js @@ -8,6 +8,7 @@ const parsePlanData = (planData) => text: capitalizeFirstCharacter(plan.name), code: plan.code, isEligibleToUsePromoCode: plan.eligible_to_use_promo_code, + maximumSeatLimit: plan.maximum_seat_limit, })); const parseGroupData = (groupData) => diff --git a/ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js b/ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js index 7eaeecadfd1fd553519886e68109392fd77aa781..a567cf91c6700413886fad1e8b668d8d9fc34745 100644 --- a/ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js +++ b/ee/spec/frontend/subscriptions/new/components/checkout/subscription_details_spec.js @@ -21,6 +21,13 @@ jest.mock('~/lib/logger'); const availablePlans = [ { id: 'firstPlanId', code: 'bronze', price_per_year: 48, name: 'bronze plan' }, { id: 'secondPlanId', code: 'silver', price_per_year: 228, name: 'silver plan' }, + { + id: 'thirdPlanId', + code: 'ramon', + price_per_year: 50, + name: 'ramon plan', + maximum_seat_limit: 2000, + }, ]; const firstGroup = { id: 132, name: 'My first group', full_path: 'my-first-group' }; const secondGroup = { id: 483, name: 'My second group', full_path: 'my-second-group' }; @@ -71,6 +78,7 @@ describe('Subscription Details', () => { const numberOfUsersInput = () => wrapper.findComponent({ ref: 'number-of-users' }); const companyLink = () => wrapper.findComponent({ ref: 'company-link' }); const findQsrOverageMessage = () => wrapper.findByTestId('qsr-overage-message'); + const findMaximumSeatMessage = () => wrapper.findByTestId('label-description'); const findNumberOfUsersFormGroup = () => wrapper.findByTestId('number-of-users-field'); describe('when rendering', () => { @@ -93,6 +101,43 @@ describe('Subscription Details', () => { QSR_RECONCILIATION_PATH, ); }); + + it('does not have a maximum seat limit alert', () => { + expect(findMaximumSeatMessage().exists()).toBe(false); + }); + }); + + describe('when rendering with a maximumSeatLimit', () => { + beforeEach(() => { + const mockApollo = createMockApolloProvider(STEPS, 1, {}, []); + const store = createStore( + createDefaultInitialStoreData({ + newUser: 'true', + setupForCompany: '', + planId: 'thirdPlanId', + }), + ); + + return createComponent({ apolloProvider: mockApollo, store }); + }); + + it('does not have a QSR alert', () => { + expect(findQsrOverageMessage().exists()).toBe(false); + }); + + it('has a maximum seat limit alert', () => { + expect(findMaximumSeatMessage().exists()).toBe(true); + }); + + it('maximum seat limit alert has correct content', () => { + expect(findMaximumSeatMessage().text()).toEqual( + 'The maximum amount of seats available to buy is 2000. Learn more.', + ); + }); + + it('maximum seat limit alert has a link', () => { + expect(findMaximumSeatMessage().findComponent(GlLink).exists()).toBe(true); + }); }); describe('A new user for which we do not have setupForCompany info', () => { diff --git a/ee/spec/frontend/subscriptions/new/store/getters_spec.js b/ee/spec/frontend/subscriptions/new/store/getters_spec.js index a4bc57cf15dcf5478bb4e7c69be3e2d974cd284e..f3e68994c7b9c9b10aab2c3b4c3a61c1906c6571 100644 --- a/ee/spec/frontend/subscriptions/new/store/getters_spec.js +++ b/ee/spec/frontend/subscriptions/new/store/getters_spec.js @@ -46,17 +46,11 @@ describe('Subscriptions Getters', () => { }); }); - describe('isUltimatePlan', () => { - it('returns true if plan code is ultimate', () => { - expect(getters.isUltimatePlan(state, { selectedPlanDetails: { code: 'ultimate' } })).toBe( - true, - ); - }); - - it('returns false if plan code is not ultimate', () => { - expect(getters.isUltimatePlan(state, { selectedPlanDetails: { code: 'not-ultimate' } })).toBe( - false, - ); + describe('maximumSeatLimit', () => { + it('returns the maximumSeatLimit for the selected plan', () => { + expect( + getters.maximumSeatLimit(state, { selectedPlanDetails: { maximumSeatLimit: 12345 } }), + ).toBe(12345); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6ccea64a38eaf72f612b5848200975c87e24db80..a24c03ffd0d10ad3025c6fb4bcb30aa8771bf97b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10496,6 +10496,9 @@ msgstr "" msgid "Checkout|Tax ID" msgstr "" +msgid "Checkout|The maximum amount of seats available to buy is %{maximumSeatLimit}. %{maxSeatLinkStart}Learn more.%{maxSeatLinkEnd}" +msgstr "" + msgid "Checkout|Total" msgstr ""