Mm 50735 (#22626)
* add shipping address to self hosted signup model * purchase modals allow scroll * fix z-index issues with payment modal dropdowns
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
379dbb1ca8
Коммит
9105077ee1
@@ -21,10 +21,11 @@ type BootstrapSelfHostedSignupResponseInternal struct {
|
|||||||
|
|
||||||
// email contained in token, so not in the request body.
|
// email contained in token, so not in the request body.
|
||||||
type SelfHostedCustomerForm struct {
|
type SelfHostedCustomerForm struct {
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
BillingAddress *Address `json:"billing_address"`
|
BillingAddress *Address `json:"billing_address"`
|
||||||
Organization string `json:"organization"`
|
ShippingAddress *Address `json:"shipping_address"`
|
||||||
|
Organization string `json:"organization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelfHostedConfirmPaymentMethodRequest struct {
|
type SelfHostedConfirmPaymentMethodRequest struct {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.shipping-address-section {
|
||||||
|
display: flex;
|
||||||
|
align-content: flex-start;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
button.no-style {
|
||||||
|
padding-left: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
outline: unset;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#address-same-than-billing-address {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form-checkbox-label {
|
||||||
|
padding-left: 12px;
|
||||||
|
cursor: default;
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.billing_address_btn_text {
|
||||||
|
color: var(--center-channel-color);
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
|
||||||
|
import './choose_different_shipping.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
shippingIsSame: boolean;
|
||||||
|
setShippingIsSame: (different: boolean) => void;
|
||||||
|
}
|
||||||
|
export default function ChooseDifferentShipping(props: Props) {
|
||||||
|
const intl = useIntl();
|
||||||
|
const toggle = () => props.setShippingIsSame(!props.shippingIsSame);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='shipping-address-section'>
|
||||||
|
<input
|
||||||
|
id='address-same-than-billing-address'
|
||||||
|
className='Form-checkbox-input'
|
||||||
|
name='terms'
|
||||||
|
type='checkbox'
|
||||||
|
checked={props.shippingIsSame}
|
||||||
|
onChange={toggle}
|
||||||
|
/>
|
||||||
|
<span className='Form-checkbox-label'>
|
||||||
|
<button
|
||||||
|
onClick={toggle}
|
||||||
|
type='button'
|
||||||
|
className='no-style'
|
||||||
|
>
|
||||||
|
<span className='billing_address_btn_text'>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'admin.billing.subscription.complianceScreenShippingSameAsBilling',
|
||||||
|
defaultMessage:
|
||||||
|
'My shipping address is the same as my billing address',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
$dropdown_input_index: 999999;
|
||||||
|
|
||||||
.DropdownInput {
|
.DropdownInput {
|
||||||
z-index: 999999;
|
z-index: $dropdown_input_index;
|
||||||
|
|
||||||
&.Input_container {
|
&.Input_container {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.DropdownInput__option > div {
|
.DropdownInput__option > div {
|
||||||
z-index: 999999;
|
z-index: $dropdown_input_index;
|
||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
@@ -51,3 +53,33 @@
|
|||||||
.DropdownInput__option.focused > div {
|
.DropdownInput__option.focused > div {
|
||||||
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.second-dropdown-sibling-wrapper {
|
||||||
|
.DropdownInput {
|
||||||
|
z-index: $dropdown_input_index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DropdownInput__option > div {
|
||||||
|
z-index: $dropdown_input_index - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.third-dropdown-sibling-wrapper {
|
||||||
|
.DropdownInput {
|
||||||
|
z-index: $dropdown_input_index - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DropdownInput__option > div {
|
||||||
|
z-index: $dropdown_input_index - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fourth-dropdown-sibling-wrapper {
|
||||||
|
.DropdownInput {
|
||||||
|
z-index: $dropdown_input_index - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DropdownInput__option > div {
|
||||||
|
z-index: $dropdown_input_index - 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,25 +61,27 @@ const AddressForm = (props: AddressFormProps) => {
|
|||||||
{...props.title}
|
{...props.title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DropdownInput
|
<div className='third-dropdown-sibling-wrapper'>
|
||||||
onChange={handleCountryChange}
|
<DropdownInput
|
||||||
value={
|
onChange={handleCountryChange}
|
||||||
props.address.country ? {value: props.address.country, label: props.address.country} : undefined
|
value={
|
||||||
}
|
props.address.country ? {value: props.address.country, label: props.address.country} : undefined
|
||||||
options={COUNTRIES.map((country) => ({
|
}
|
||||||
value: country.name,
|
options={COUNTRIES.map((country) => ({
|
||||||
label: country.name,
|
value: country.name,
|
||||||
}))}
|
label: country.name,
|
||||||
legend={formatMessage({
|
}))}
|
||||||
id: 'payment_form.country',
|
legend={formatMessage({
|
||||||
defaultMessage: 'Country',
|
id: 'payment_form.country',
|
||||||
})}
|
defaultMessage: 'Country',
|
||||||
placeholder={formatMessage({
|
})}
|
||||||
id: 'payment_form.country',
|
placeholder={formatMessage({
|
||||||
defaultMessage: 'Country',
|
id: 'payment_form.country',
|
||||||
})}
|
defaultMessage: 'Country',
|
||||||
name={'billing_dropdown'}
|
})}
|
||||||
/>
|
name={'billing_dropdown'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className='form-row'>
|
<div className='form-row'>
|
||||||
<Input
|
<Input
|
||||||
name='address'
|
name='address'
|
||||||
@@ -122,7 +124,7 @@ const AddressForm = (props: AddressFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='form-row'>
|
<div className='form-row'>
|
||||||
<div className='form-row-third-1 selector'>
|
<div className='form-row-third-1 selector fourth-dropdown-sibling-wrapper'>
|
||||||
<StateSelector
|
<StateSelector
|
||||||
country={props.address.country}
|
country={props.address.country}
|
||||||
state={props.address.state}
|
state={props.address.state}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
.form-row-third-1 {
|
.form-row-third-1 {
|
||||||
.DropdownInput {
|
.DropdownInput {
|
||||||
z-index: 99999;
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@
|
|||||||
|
|
||||||
.DropdownInput {
|
.DropdownInput {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 999999;
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ export default class PaymentForm extends React.PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='form-row'>
|
<div className='form-row'>
|
||||||
<div className='form-row-third-1 selector'>
|
<div className='form-row-third-1 selector second-dropdown-sibling-wrapper'>
|
||||||
<StateSelector
|
<StateSelector
|
||||||
country={this.state.country}
|
country={this.state.country}
|
||||||
state={this.state.state}
|
state={this.state.state}
|
||||||
|
|||||||
@@ -2,42 +2,8 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.shipping-address-section {
|
& &__purchase-body {
|
||||||
display: flex;
|
overflow-y: auto;
|
||||||
align-content: center;
|
|
||||||
padding: 0 96px;
|
|
||||||
padding-bottom: 28px;
|
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
button.no-style {
|
|
||||||
padding-left: 0;
|
|
||||||
background: transparent;
|
|
||||||
outline: unset;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#address-same-than-billing-address {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Form-checkbox-label {
|
|
||||||
padding-left: 12px;
|
|
||||||
cursor: default;
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.billing_address_btn_text {
|
|
||||||
color: var(--center-channel-color);
|
|
||||||
font-family: 'Open Sans', sans-serif;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
>div {
|
>div {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import React, {ReactNode} from 'react';
|
import React, {ReactNode} from 'react';
|
||||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
import {Stripe, StripeCardElementChangeEvent} from '@stripe/stripe-js';
|
import {Stripe, StripeCardElementChangeEvent} from '@stripe/stripe-js';
|
||||||
import {loadStripe} from '@stripe/stripe-js/pure'; // https://github.com/stripe/stripe-js#importing-loadstripe-without-side-effects
|
import {loadStripe} from '@stripe/stripe-js/pure'; // https://github.com/stripe/stripe-js#importing-loadstripe-without-side-effects
|
||||||
import {Elements} from '@stripe/react-stripe-js';
|
import {Elements} from '@stripe/react-stripe-js';
|
||||||
@@ -812,7 +813,7 @@ class PurchaseModal extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.state.processing ? 'processing' : ''}>
|
<div className={classnames('PurchaseModal__purchase-body', {processing: this.state.processing})}>
|
||||||
<div className='LHS'>
|
<div className='LHS'>
|
||||||
<h2 className='title'>{title}</h2>
|
<h2 className='title'>{title}</h2>
|
||||||
<UpgradeSvg
|
<UpgradeSvg
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {COUNTRIES} from 'utils/countries';
|
||||||
|
|
||||||
|
import DropdownInput from 'components/dropdown_input';
|
||||||
|
import Input from 'components/widgets/inputs/input/input';
|
||||||
|
import StateSelector from 'components/payment_form/state_selector';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'shipping' | 'billing';
|
||||||
|
testPrefix?: string;
|
||||||
|
|
||||||
|
country: string;
|
||||||
|
changeCountry: (option: {value: string}) => void;
|
||||||
|
|
||||||
|
address: string;
|
||||||
|
changeAddress: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
|
||||||
|
address2: string;
|
||||||
|
changeAddress2: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
|
||||||
|
city: string;
|
||||||
|
changeCity: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
|
||||||
|
state: string;
|
||||||
|
changeState: (postalCode: string) => void;
|
||||||
|
|
||||||
|
postalCode: string;
|
||||||
|
changePostalCode: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
}
|
||||||
|
export default function Address(props: Props) {
|
||||||
|
const testPrefix = props.testPrefix || 'selfHostedPurchase';
|
||||||
|
const intl = useIntl();
|
||||||
|
let countrySelectorId = `${testPrefix}CountrySelector`;
|
||||||
|
let stateSelectorId = `${testPrefix}StateSelector`;
|
||||||
|
if (props.type === 'shipping') {
|
||||||
|
countrySelectorId += '_Shipping';
|
||||||
|
stateSelectorId += '_Shipping';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={classNames({'third-dropdown-sibling-wrapper': props.type === 'shipping'})}>
|
||||||
|
<DropdownInput
|
||||||
|
testId={countrySelectorId}
|
||||||
|
onChange={props.changeCountry}
|
||||||
|
value={
|
||||||
|
props.country ? {value: props.country, label: props.country} : undefined
|
||||||
|
}
|
||||||
|
options={COUNTRIES.map((country) => ({
|
||||||
|
value: country.name,
|
||||||
|
label: country.name,
|
||||||
|
}))}
|
||||||
|
legend={intl.formatMessage({
|
||||||
|
id: 'payment_form.country',
|
||||||
|
defaultMessage: 'Country',
|
||||||
|
})}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'payment_form.country',
|
||||||
|
defaultMessage: 'Country',
|
||||||
|
})}
|
||||||
|
name={'billing_dropdown'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-row'>
|
||||||
|
<Input
|
||||||
|
name='address'
|
||||||
|
type='text'
|
||||||
|
value={props.address}
|
||||||
|
onChange={props.changeAddress}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'payment_form.address',
|
||||||
|
defaultMessage: 'Address',
|
||||||
|
})}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-row'>
|
||||||
|
<Input
|
||||||
|
name='address2'
|
||||||
|
type='text'
|
||||||
|
value={props.address2}
|
||||||
|
onChange={props.changeAddress2}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'payment_form.address_2',
|
||||||
|
defaultMessage: 'Address 2',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-row'>
|
||||||
|
<Input
|
||||||
|
name='city'
|
||||||
|
type='text'
|
||||||
|
value={props.city}
|
||||||
|
onChange={props.changeCity}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'payment_form.city',
|
||||||
|
defaultMessage: 'City',
|
||||||
|
})}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-row'>
|
||||||
|
<div className={classNames('form-row-third-1', {'second-dropdown-sibling-wrapper': props.type === 'billing', 'fourth-dropdown-sibling-wrapper': props.type === 'shipping'})}>
|
||||||
|
<StateSelector
|
||||||
|
testId={stateSelectorId}
|
||||||
|
country={props.country}
|
||||||
|
state={props.state}
|
||||||
|
onChange={props.changeState}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='form-row-third-2'>
|
||||||
|
<Input
|
||||||
|
name='postalCode'
|
||||||
|
type='text'
|
||||||
|
value={props.postalCode}
|
||||||
|
onChange={props.changePostalCode}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'payment_form.zipcode',
|
||||||
|
defaultMessage: 'Zip/Postal Code',
|
||||||
|
})}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -310,6 +310,15 @@ describe('SelfHostedPurchaseModal :: canSubmit', () => {
|
|||||||
state: 'string',
|
state: 'string',
|
||||||
country: 'string',
|
country: 'string',
|
||||||
postalCode: '12345',
|
postalCode: '12345',
|
||||||
|
|
||||||
|
shippingSame: true,
|
||||||
|
shippingAddress: '',
|
||||||
|
shippingAddress2: '',
|
||||||
|
shippingCity: '',
|
||||||
|
shippingState: '',
|
||||||
|
shippingCountry: '',
|
||||||
|
shippingPostalCode: '',
|
||||||
|
|
||||||
cardName: 'string',
|
cardName: 'string',
|
||||||
organization: 'string',
|
organization: 'string',
|
||||||
agreedTerms: true,
|
agreedTerms: true,
|
||||||
@@ -361,6 +370,21 @@ describe('SelfHostedPurchaseModal :: canSubmit', () => {
|
|||||||
expect(canSubmit(state, SelfHostedSignupProgress.CREATED_CUSTOMER)).toBe(false);
|
expect(canSubmit(state, SelfHostedSignupProgress.CREATED_CUSTOMER)).toBe(false);
|
||||||
expect(canSubmit(state, SelfHostedSignupProgress.CREATED_INTENT)).toBe(false);
|
expect(canSubmit(state, SelfHostedSignupProgress.CREATED_INTENT)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('if shipping address different and is not filled, can not submit', () => {
|
||||||
|
const state = makeHappyPathState();
|
||||||
|
state.shippingSame = false;
|
||||||
|
expect(canSubmit(state, SelfHostedSignupProgress.START)).toBe(false);
|
||||||
|
|
||||||
|
state.shippingAddress = 'more shipping info';
|
||||||
|
state.shippingAddress2 = 'more shipping info';
|
||||||
|
state.shippingCity = 'more shipping info';
|
||||||
|
state.shippingState = 'more shipping info';
|
||||||
|
state.shippingCountry = 'more shipping info';
|
||||||
|
state.shippingPostalCode = 'more shipping info';
|
||||||
|
expect(canSubmit(state, SelfHostedSignupProgress.START)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('if card number missing and card has not been confirmed, can not submit', () => {
|
it('if card number missing and card has not been confirmed, can not submit', () => {
|
||||||
const state = makeHappyPathState();
|
const state = makeHappyPathState();
|
||||||
state.cardFilled = false;
|
state.cardFilled = false;
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import {GlobalState} from 'types/store';
|
|||||||
import {isModalOpen} from 'selectors/views/modals';
|
import {isModalOpen} from 'selectors/views/modals';
|
||||||
import {isDevModeEnabled} from 'selectors/general';
|
import {isDevModeEnabled} from 'selectors/general';
|
||||||
|
|
||||||
import {COUNTRIES} from 'utils/countries';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ModalIdentifiers,
|
ModalIdentifiers,
|
||||||
StatTypes,
|
StatTypes,
|
||||||
@@ -35,8 +33,6 @@ import {
|
|||||||
} from 'utils/constants';
|
} from 'utils/constants';
|
||||||
|
|
||||||
import CardInput, {CardInputType} from 'components/payment_form/card_input';
|
import CardInput, {CardInputType} from 'components/payment_form/card_input';
|
||||||
import StateSelector from 'components/payment_form/state_selector';
|
|
||||||
import DropdownInput from 'components/dropdown_input';
|
|
||||||
import BackgroundSvg from 'components/common/svg_images_components/background_svg';
|
import BackgroundSvg from 'components/common/svg_images_components/background_svg';
|
||||||
import UpgradeSvg from 'components/common/svg_images_components/upgrade_svg';
|
import UpgradeSvg from 'components/common/svg_images_components/upgrade_svg';
|
||||||
|
|
||||||
@@ -47,6 +43,7 @@ import RootPortal from 'components/root_portal';
|
|||||||
import useLoadStripe from 'components/common/hooks/useLoadStripe';
|
import useLoadStripe from 'components/common/hooks/useLoadStripe';
|
||||||
import useControlSelfHostedPurchaseModal from 'components/common/hooks/useControlSelfHostedPurchaseModal';
|
import useControlSelfHostedPurchaseModal from 'components/common/hooks/useControlSelfHostedPurchaseModal';
|
||||||
import useFetchStandardAnalytics from 'components/common/hooks/useFetchStandardAnalytics';
|
import useFetchStandardAnalytics from 'components/common/hooks/useFetchStandardAnalytics';
|
||||||
|
import ChooseDifferentShipping from 'components/choose_different_shipping';
|
||||||
|
|
||||||
import {ValueOf} from '@mattermost/types/utilities';
|
import {ValueOf} from '@mattermost/types/utilities';
|
||||||
import {UserProfile} from '@mattermost/types/users';
|
import {UserProfile} from '@mattermost/types/users';
|
||||||
@@ -64,6 +61,7 @@ import SuccessPage from './success_page';
|
|||||||
import SelfHostedCard from './self_hosted_card';
|
import SelfHostedCard from './self_hosted_card';
|
||||||
import StripeProvider from './stripe_provider';
|
import StripeProvider from './stripe_provider';
|
||||||
import Terms from './terms';
|
import Terms from './terms';
|
||||||
|
import Address from './address';
|
||||||
import useNoEscape from './useNoEscape';
|
import useNoEscape from './useNoEscape';
|
||||||
|
|
||||||
import {SetPrefix, UnionSetActions} from './types';
|
import {SetPrefix, UnionSetActions} from './types';
|
||||||
@@ -73,12 +71,24 @@ import './self_hosted_purchase_modal.scss';
|
|||||||
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from './constants';
|
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from './constants';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
|
|
||||||
|
// billing address
|
||||||
address: string;
|
address: string;
|
||||||
address2: string;
|
address2: string;
|
||||||
city: string;
|
city: string;
|
||||||
state: string;
|
state: string;
|
||||||
country: string;
|
country: string;
|
||||||
postalCode: string;
|
postalCode: string;
|
||||||
|
|
||||||
|
// shipping address
|
||||||
|
shippingSame: boolean;
|
||||||
|
shippingAddress: string;
|
||||||
|
shippingAddress2: string;
|
||||||
|
shippingCity: string;
|
||||||
|
shippingState: string;
|
||||||
|
shippingCountry: string;
|
||||||
|
shippingPostalCode: string;
|
||||||
|
|
||||||
cardName: string;
|
cardName: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
agreedTerms: boolean;
|
agreedTerms: boolean;
|
||||||
@@ -113,6 +123,15 @@ export function makeInitialState(): State {
|
|||||||
state: '',
|
state: '',
|
||||||
country: '',
|
country: '',
|
||||||
postalCode: '',
|
postalCode: '',
|
||||||
|
|
||||||
|
shippingSame: true,
|
||||||
|
shippingAddress: '',
|
||||||
|
shippingAddress2: '',
|
||||||
|
shippingCity: '',
|
||||||
|
shippingState: '',
|
||||||
|
shippingCountry: '',
|
||||||
|
shippingPostalCode: '',
|
||||||
|
|
||||||
cardName: '',
|
cardName: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
agreedTerms: false,
|
agreedTerms: false,
|
||||||
@@ -170,8 +189,18 @@ const simpleSetters: Array<Extract<keyof State, string>> = [
|
|||||||
'address2',
|
'address2',
|
||||||
'city',
|
'city',
|
||||||
'country',
|
'country',
|
||||||
'postalCode',
|
|
||||||
'state',
|
'state',
|
||||||
|
'postalCode',
|
||||||
|
|
||||||
|
// shipping address
|
||||||
|
'shippingSame',
|
||||||
|
'shippingAddress',
|
||||||
|
'shippingAddress2',
|
||||||
|
'shippingCity',
|
||||||
|
'shippingState',
|
||||||
|
'shippingCountry',
|
||||||
|
'shippingPostalCode',
|
||||||
|
|
||||||
'agreedTerms',
|
'agreedTerms',
|
||||||
'cardFilled',
|
'cardFilled',
|
||||||
'cardName',
|
'cardName',
|
||||||
@@ -220,7 +249,7 @@ export function canSubmit(state: State, progress: ValueOf<typeof SelfHostedSignu
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validAddress = Boolean(
|
let validAddress = Boolean(
|
||||||
state.organization &&
|
state.organization &&
|
||||||
state.address &&
|
state.address &&
|
||||||
state.city &&
|
state.city &&
|
||||||
@@ -228,6 +257,16 @@ export function canSubmit(state: State, progress: ValueOf<typeof SelfHostedSignu
|
|||||||
state.postalCode &&
|
state.postalCode &&
|
||||||
state.country,
|
state.country,
|
||||||
);
|
);
|
||||||
|
if (!state.shippingSame) {
|
||||||
|
validAddress = validAddress && Boolean(
|
||||||
|
state.shippingAddress &&
|
||||||
|
state.shippingCity &&
|
||||||
|
state.shippingState &&
|
||||||
|
state.shippingPostalCode &&
|
||||||
|
state.shippingCountry,
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
const validCard = Boolean(
|
const validCard = Boolean(
|
||||||
state.cardName &&
|
state.cardName &&
|
||||||
state.cardFilled,
|
state.cardFilled,
|
||||||
@@ -366,16 +405,25 @@ export default function SelfHostedPurchaseModal(props: Props) {
|
|||||||
try {
|
try {
|
||||||
const [firstName, lastName] = inferNames(user, state.cardName);
|
const [firstName, lastName] = inferNames(user, state.cardName);
|
||||||
|
|
||||||
|
const billingAddress = {
|
||||||
|
city: state.city,
|
||||||
|
country: state.country,
|
||||||
|
line1: state.address,
|
||||||
|
line2: state.address2,
|
||||||
|
postal_code: state.postalCode,
|
||||||
|
state: state.state,
|
||||||
|
};
|
||||||
signupCustomerResult = await Client4.createCustomerSelfHostedSignup({
|
signupCustomerResult = await Client4.createCustomerSelfHostedSignup({
|
||||||
first_name: firstName,
|
first_name: firstName,
|
||||||
last_name: lastName,
|
last_name: lastName,
|
||||||
billing_address: {
|
billing_address: billingAddress,
|
||||||
city: state.city,
|
shipping_address: state.shippingSame ? billingAddress : {
|
||||||
country: state.country,
|
city: state.shippingCity,
|
||||||
line1: state.address,
|
country: state.shippingCountry,
|
||||||
line2: state.address2,
|
line1: state.shippingAddress,
|
||||||
postal_code: state.postalCode,
|
line2: state.shippingAddress2,
|
||||||
state: state.state,
|
postal_code: state.shippingPostalCode,
|
||||||
|
state: state.shippingState,
|
||||||
},
|
},
|
||||||
organization: state.organization,
|
organization: state.organization,
|
||||||
});
|
});
|
||||||
@@ -586,99 +634,76 @@ export default function SelfHostedPurchaseModal(props: Props) {
|
|||||||
defaultMessage='Billing address'
|
defaultMessage='Billing address'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DropdownInput
|
<Address
|
||||||
testId='selfHostedPurchaseCountrySelector'
|
type='billing'
|
||||||
onChange={(option: {value: string}) => {
|
country={state.country}
|
||||||
|
changeCountry={(option) => {
|
||||||
dispatch({type: 'set_country', data: option.value});
|
dispatch({type: 'set_country', data: option.value});
|
||||||
}}
|
}}
|
||||||
value={
|
address={state.address}
|
||||||
state.country ? {value: state.country, label: state.country} : undefined
|
changeAddress={(e) => {
|
||||||
}
|
dispatch({type: 'set_address', data: e.target.value});
|
||||||
options={COUNTRIES.map((country) => ({
|
}}
|
||||||
value: country.name,
|
address2={state.address2}
|
||||||
label: country.name,
|
changeAddress2={(e) => {
|
||||||
}))}
|
dispatch({type: 'set_address2', data: e.target.value});
|
||||||
legend={intl.formatMessage({
|
}}
|
||||||
id: 'payment_form.country',
|
city={state.city}
|
||||||
defaultMessage: 'Country',
|
changeCity={(e) => {
|
||||||
})}
|
dispatch({type: 'set_city', data: e.target.value});
|
||||||
placeholder={intl.formatMessage({
|
}}
|
||||||
id: 'payment_form.country',
|
state={state.state}
|
||||||
defaultMessage: 'Country',
|
changeState={(state: string) => {
|
||||||
})}
|
dispatch({type: 'set_state', data: state});
|
||||||
name={'billing_dropdown'}
|
}}
|
||||||
|
postalCode={state.postalCode}
|
||||||
|
changePostalCode={(e) => {
|
||||||
|
dispatch({type: 'set_postalCode', data: e.target.value});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className='form-row'>
|
<ChooseDifferentShipping
|
||||||
<Input
|
shippingIsSame={state.shippingSame}
|
||||||
name='address'
|
setShippingIsSame={(val: boolean) => {
|
||||||
type='text'
|
dispatch({type: 'set_shippingSame', data: val});
|
||||||
value={state.address}
|
}}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
/>
|
||||||
dispatch({type: 'set_address', data: e.target.value});
|
{!state.shippingSame && (
|
||||||
}}
|
<>
|
||||||
placeholder={intl.formatMessage({
|
<div className='section-title'>
|
||||||
id: 'payment_form.address',
|
<FormattedMessage
|
||||||
defaultMessage: 'Address',
|
id='payment_form.shipping_address'
|
||||||
})}
|
defaultMessage='Shipping Address'
|
||||||
required={true}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<Address
|
||||||
<div className='form-row'>
|
type='shipping'
|
||||||
<Input
|
country={state.shippingCountry}
|
||||||
name='address2'
|
changeCountry={(option) => {
|
||||||
type='text'
|
dispatch({type: 'set_shippingCountry', data: option.value});
|
||||||
value={state.address2}
|
}}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
address={state.shippingAddress}
|
||||||
dispatch({type: 'set_address2', data: e.target.value});
|
changeAddress={(e) => {
|
||||||
}}
|
dispatch({type: 'set_shippingAddress', data: e.target.value});
|
||||||
placeholder={intl.formatMessage({
|
}}
|
||||||
id: 'payment_form.address_2',
|
address2={state.shippingAddress2}
|
||||||
defaultMessage: 'Address 2',
|
changeAddress2={(e) => {
|
||||||
})}
|
dispatch({type: 'set_shippingAddress2', data: e.target.value});
|
||||||
/>
|
}}
|
||||||
</div>
|
city={state.shippingCity}
|
||||||
<div className='form-row'>
|
changeCity={(e) => {
|
||||||
<Input
|
dispatch({type: 'set_shippingCity', data: e.target.value});
|
||||||
name='city'
|
}}
|
||||||
type='text'
|
state={state.shippingState}
|
||||||
value={state.city}
|
changeState={(state: string) => {
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
dispatch({type: 'set_shippingState', data: state});
|
||||||
dispatch({type: 'set_city', data: e.target.value});
|
}}
|
||||||
}}
|
postalCode={state.shippingPostalCode}
|
||||||
placeholder={intl.formatMessage({
|
changePostalCode={(e) => {
|
||||||
id: 'payment_form.city',
|
dispatch({type: 'set_shippingPostalCode', data: e.target.value});
|
||||||
defaultMessage: 'City',
|
|
||||||
})}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='form-row'>
|
|
||||||
<div className='form-row-third-1'>
|
|
||||||
<StateSelector
|
|
||||||
testId='selfHostedPurchaseStateSelector'
|
|
||||||
country={state.country}
|
|
||||||
state={state.state}
|
|
||||||
onChange={(state: string) => {
|
|
||||||
dispatch({type: 'set_state', data: state});
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
<div className='form-row-third-2'>
|
)}
|
||||||
<Input
|
|
||||||
name='postalCode'
|
|
||||||
type='text'
|
|
||||||
value={state.postalCode}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
dispatch({type: 'set_postalCode', data: e.target.value});
|
|
||||||
}}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: 'payment_form.zipcode',
|
|
||||||
defaultMessage: 'Zip/Postal Code',
|
|
||||||
})}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Terms
|
<Terms
|
||||||
agreed={state.agreedTerms}
|
agreed={state.agreedTerms}
|
||||||
setAgreed={(data: boolean) => {
|
setAgreed={(data: boolean) => {
|
||||||
|
|||||||
@@ -4,19 +4,20 @@
|
|||||||
|
|
||||||
.form-view {
|
.form-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: top;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 77px 107px;
|
padding: 77px 107px;
|
||||||
color: var(--center-channel-color);
|
color: var(--center-channel-color);
|
||||||
font-family: "Open Sans";
|
font-family: "Open Sans";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
@@ -39,14 +40,12 @@
|
|||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
|
||||||
.DropdownInput {
|
.DropdownInput {
|
||||||
z-index: 99999;
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.DropdownInput {
|
.DropdownInput {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 999999;
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
|
||||||
@@ -517,6 +516,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
flex-shrink: 0;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4378,6 +4378,7 @@
|
|||||||
"payment_form.no_billing_address": "No billing address added",
|
"payment_form.no_billing_address": "No billing address added",
|
||||||
"payment_form.no_credit_card": "No credit card added",
|
"payment_form.no_credit_card": "No credit card added",
|
||||||
"payment_form.saved_payment_method": "Saved Payment Method",
|
"payment_form.saved_payment_method": "Saved Payment Method",
|
||||||
|
"payment_form.shipping_address": "Shipping Address",
|
||||||
"payment_form.zipcode": "Zip/Postal Code",
|
"payment_form.zipcode": "Zip/Postal Code",
|
||||||
"payment.card_number": "Card Number",
|
"payment.card_number": "Card Number",
|
||||||
"payment.field_required": "This field is required",
|
"payment.field_required": "This field is required",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface SelfHostedSignupForm {
|
|||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
billing_address: Address;
|
billing_address: Address;
|
||||||
|
shipping_address: Address;
|
||||||
organization: string;
|
organization: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user