Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

MM-35435 - add multiple SKU options to purchase modal #8119

Merged
merged 20 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion actions/cloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {getConfirmCardSetup} from 'components/payment_form/stripe';
import {StripeSetupIntent, BillingDetails} from 'types/cloud/sku';

// Returns true for success, and false for any error
export function completeStripeAddPaymentMethod(stripe: Stripe, billingDetails: BillingDetails, isDevMode: boolean) {
export function completeStripeAddPaymentMethod(
stripe: Stripe,
billingDetails: BillingDetails,
isDevMode: boolean,
) {
return async () => {
let paymentSetupIntent: StripeSetupIntent;
try {
Expand Down Expand Up @@ -69,3 +73,14 @@ export function completeStripeAddPaymentMethod(stripe: Stripe, billingDetails: B
return true;
};
}

export function subscribeCloudSubscription(productId: string) {
return async () => {
try {
await Client4.subscribeCloudProduct(productId);
} catch (error) {
return error;
}
return true;
};
}
62 changes: 62 additions & 0 deletions components/common/__snapshots__/radio_group.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`/components/common/RadioButtonGroup should match snapshot 1`] = `
<div
className="radio-list"
>
<div
className="radio"
key="value1"
>
<label
className=""
>
<input
checked={false}
disabled={false}
name="test-string"
onChange={[Function]}
type="radio"
value="value1"
/>
key1
</label>
</div>
<div
className="radio"
key="value2"
>
<label
className="selected"
>
<input
checked={true}
disabled={false}
name="test-string"
onChange={[Function]}
type="radio"
value="value2"
/>
key2
</label>
</div>
<div
className="radio"
key="value3"
>
<label
className=""
>
<input
checked={false}
disabled={false}
name="test-string"
onChange={[Function]}
type="radio"
value="value3"
/>
key3
</label>
</div>
</div>
`;
38 changes: 38 additions & 0 deletions components/common/radio_group.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {shallow} from 'enzyme';

import RadioButtonGroup from 'components/common/radio_group';

describe('/components/common/RadioButtonGroup', () => {
const onChange = jest.fn();
const baseProps = {
id: 'test-string',
value: 'value2',
values: [{key: 'key1', value: 'value1'}, {key: 'key2', value: 'value2'}, {key: 'key3', value: 'value3'}],
onChange,
};

test('should match snapshot', () => {
const wrapper = shallow(<RadioButtonGroup {...baseProps}/>);
expect(wrapper).toMatchSnapshot();
});

test('test radio button group input lenght is as expected', () => {
const wrapper = shallow(<RadioButtonGroup {...baseProps}/>);
const buttons = wrapper.find('input');

expect(buttons.length).toBe(3);
});

test('test radio button group onChange function', () => {
const wrapper = shallow(<RadioButtonGroup {...baseProps}/>);

const buttons = wrapper.find('input');
buttons.at(0).simulate('change');

expect(onChange).toHaveBeenCalledTimes(1);
});
});
54 changes: 54 additions & 0 deletions components/common/radio_group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';

type RadioGroupProps = {
id: string;
values: Array<{ key: string; value: string}>;
value: string;
isDisabled?: (id: string) => boolean | boolean;
onChange(e: React.ChangeEvent<HTMLInputElement>): void;
}
const RadioButtonGroup: React.FC<RadioGroupProps> = ({
marianunez marked this conversation as resolved.
Show resolved Hide resolved
id,
onChange,
isDisabled,
values,
value,
}: RadioGroupProps) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e);
};

const options = [];
for (const {value: val, key} of values) {
const disabled = isDisabled ? isDisabled(val) : false;
options.push(
<div
className='radio'
key={val}
>
<label className={val === value ? 'selected' : ''}>
<input
type='radio'
value={val}
name={id}
checked={val === value}
onChange={handleChange}
disabled={disabled}
/>
{key}
</label>
</div>,
);
}

return (
<div className='radio-list'>
{options}
</div>
);
};

export default RadioButtonGroup;
5 changes: 4 additions & 1 deletion components/purchase_modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {getCloudContactUsLink, InquiryType} from 'selectors/cloud';
import {ModalIdentifiers} from 'utils/constants';

import {closeModal} from 'actions/views/modals';
import {completeStripeAddPaymentMethod} from 'actions/cloud';
import {completeStripeAddPaymentMethod, subscribeCloudSubscription} from 'actions/cloud';

import PurchaseModal from './purchase_modal';

Expand All @@ -32,12 +32,14 @@ function mapStateToProps(state: GlobalState) {
contactSupportLink: getCloudContactUsLink(state, InquiryType.Technical),
isFreeTrial: subscription?.is_free_trial === 'true',
contactSalesLink: getCloudContactUsLink(state, InquiryType.Sales),
productId: subscription?.product_id,
};
}
type Actions = {
closeModal: () => void;
getCloudProducts: () => void;
completeStripeAddPaymentMethod: (stripe: Stripe, billingDetails: BillingDetails, isDevMode: boolean) => Promise<boolean | null>;
subscribeCloudSubscription: (productId: string) => Promise<boolean | null>;
getClientConfig: () => void;
getCloudSubscription: () => void;
}
Expand All @@ -49,6 +51,7 @@ function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
closeModal: () => closeModal(ModalIdentifiers.CLOUD_PURCHASE),
getCloudProducts,
completeStripeAddPaymentMethod,
subscribeCloudSubscription,
getClientConfig,
getCloudSubscription,
},
Expand Down
23 changes: 22 additions & 1 deletion components/purchase_modal/process_payment_setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import processSvg from 'images/cloud/processing_payment.svg';

import './process_payment.css';

import {Product} from 'mattermost-redux/types/cloud';

import IconMessage from './icon_message';

type Props = {
Expand All @@ -25,8 +27,10 @@ type Props = {
isDevMode: boolean;
contactSupportLink: string;
addPaymentMethod: (stripe: Stripe, billingDetails: BillingDetails, isDevMode: boolean) => Promise<boolean | null>;
subscribeCloudSubscription: ((productId: string) => Promise<boolean | null>) | null;
onBack: () => void;
onClose: () => void;
selectedProduct?: Product | null | undefined;
}

type State = {
Expand Down Expand Up @@ -83,7 +87,13 @@ export default class ProcessPaymentSetup extends React.PureComponent<Props, Stat

private savePaymentMethod = async () => {
const start = new Date();
const {stripe, addPaymentMethod, billingDetails, isDevMode} = this.props;
const {
stripe,
addPaymentMethod,
billingDetails,
isDevMode,
subscribeCloudSubscription,
} = this.props;
const success = await addPaymentMethod((await stripe)!, billingDetails!, isDevMode);

if (!success) {
Expand All @@ -93,6 +103,17 @@ export default class ProcessPaymentSetup extends React.PureComponent<Props, Stat
return;
}

if (subscribeCloudSubscription) {
const productUpdated = await subscribeCloudSubscription(this.props.selectedProduct?.id as string);

if (!productUpdated) {
this.setState({
error: true,
state: ProcessState.FAILED});
return;
}
}

const end = new Date();
const millisecondsElapsed = end.valueOf() - start.valueOf();
if (millisecondsElapsed < MIN_PROCESSING_MILLISECONDS) {
Expand Down
35 changes: 35 additions & 0 deletions components/purchase_modal/purchase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,41 @@
margin-bottom: 24px;
min-width: 270px;

.select-plan {
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
box-sizing: border-box;
margin-bottom: 20px;

.title {
display: flex;
font-weight: 600;
font-size: 14px;
justify-content: space-between;
line-height: 20px;

a {
font-weight: normal;
font-size: 11px;
line-height: 16px;
}
}

.plans-list {
label {
color: rgba(var(--sys-center-channel-color-rgb), 0.64);
font-family: Open Sans;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 16px;
}

.selected {
color: var(--sys-center-channel-color);
}
}
}

.footer-text {
font-size: 14px;
margin-bottom: 24px;
Expand Down
Loading