move self hosted purchase modals into one folder for shared resources, update banners to redirect to the self hosted purchase modal (in the case of air gapped, link to CWS. In the future, this will be a modal).
Этот коммит содержится в:
@@ -23,6 +23,8 @@ import useOpenPricingModal from 'components/common/hooks/useOpenPricingModal';
|
||||
import useCanSelfHostedExpand from 'components/common/hooks/useCanSelfHostedExpand';
|
||||
import {getExpandSeatsLink} from 'selectors/cloud';
|
||||
import useControlSelfHostedExpansionModal from 'components/common/hooks/useControlSelfHostedExpansionModal';
|
||||
import {useQuery} from 'utils/http_utils';
|
||||
import {STORAGE_KEY_EXPANSION_IN_PROGRESS} from 'components/self_hosted_purchases/constants';
|
||||
|
||||
const DAYS_UNTIL_EXPIRY_WARNING_DISPLAY_THRESHOLD = 30;
|
||||
const DAYS_UNTIL_EXPIRY_DANGER_DISPLAY_THRESHOLD = 5;
|
||||
@@ -58,6 +60,19 @@ const EnterpriseEditionLeftPanel = ({
|
||||
const canExpand = useCanSelfHostedExpand();
|
||||
const selfHostedExpansionModal = useControlSelfHostedExpansionModal({trackingLocation: 'license_settings_add_seats'});
|
||||
const expandableLink = useSelector(getExpandSeatsLink);
|
||||
const isSelfHostedExpansionEnabled = useSelector(getConfig)?.ServiceSettings?.SelfHostedExpansion;
|
||||
|
||||
const query = useQuery();
|
||||
const actionQueryParam = query.get('action');
|
||||
|
||||
useEffect(() => {
|
||||
console.log(actionQueryParam);
|
||||
if (actionQueryParam === 'show_expansion_modal' && canExpand && isSelfHostedExpansionEnabled) {
|
||||
console.log("Open modal!");
|
||||
selfHostedExpansionModal.open();
|
||||
query.set('action', '');
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchUnSanitizedLicense() {
|
||||
@@ -73,7 +88,6 @@ const EnterpriseEditionLeftPanel = ({
|
||||
|
||||
const skuName = getSkuDisplayName(unsanitizedLicense.SkuShortName, unsanitizedLicense.IsGovSku === 'true');
|
||||
const expirationDays = getRemainingDaysFromFutureTimestamp(parseInt(unsanitizedLicense.ExpiresAt, 10));
|
||||
const isSelfHostedExpansionEnabled = useSelector(getConfig)?.ServiceSettings?.SelfHostedExpansion;
|
||||
|
||||
const viewPlansButton = (
|
||||
<button
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import React, {useMemo} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {useHistory} from 'react-router-dom';
|
||||
|
||||
import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
|
||||
import {GlobalState} from 'types/store';
|
||||
@@ -16,9 +17,10 @@ import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {PreferenceType} from '@mattermost/types/preferences';
|
||||
import {useExpandOverageUsersCheck} from 'components/common/hooks/useExpandOverageUsersCheck';
|
||||
import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink';
|
||||
import {StatTypes, Preferences, AnnouncementBarTypes} from 'utils/constants';
|
||||
import {StatTypes, Preferences, AnnouncementBarTypes, ConsolePages} from 'utils/constants';
|
||||
|
||||
import './overage_users_banner.scss';
|
||||
import useCWSAvailabilityCheck from 'components/common/hooks/useCWSAvailabilityCheck';
|
||||
|
||||
type AdminHasDismissedItArgs = {
|
||||
preferenceName: string;
|
||||
@@ -56,6 +58,8 @@ const OverageUsersBanner = () => {
|
||||
const prefixPreferences = isOver10PercerntPurchasedSeats ? 'error' : 'warn';
|
||||
const prefixLicenseId = (license.Id || '').substring(0, 8);
|
||||
const preferenceName = `${prefixPreferences}_overage_seats_${prefixLicenseId}`;
|
||||
const history = useHistory();
|
||||
const isAirGapped = !useCWSAvailabilityCheck();
|
||||
|
||||
const overageByUsers = activeUsers - seatsPurchased;
|
||||
|
||||
@@ -86,7 +90,14 @@ const OverageUsersBanner = () => {
|
||||
const handleUpdateSeatsSelfServeClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
trackEventFn('Self Serve');
|
||||
window.open(expandableLink(license.Id), '_blank');
|
||||
|
||||
if (isAirGapped) {
|
||||
window.open(expandableLink(license.Id), '_blank');
|
||||
}
|
||||
|
||||
if (isExpandable) {
|
||||
history.push(`${ConsolePages.LICENSE}?action=show_expansion_modal`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContactSalesClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
|
||||
@@ -12,8 +12,8 @@ import {Client4} from 'mattermost-redux/client';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
|
||||
import {HostedCustomerTypes} from 'mattermost-redux/action_types';
|
||||
|
||||
import {STORAGE_KEY_EXPANSION_IN_PROGRESS} from 'components/self_hosted_expansion_modal/constants';
|
||||
import SelfHostedExpansionModal from 'components/self_hosted_expansion_modal';
|
||||
import {STORAGE_KEY_EXPANSION_IN_PROGRESS} from 'components/self_hosted_purchases/constants';
|
||||
import SelfHostedExpansionModal from 'components/self_hosted_purchases/self_hosted_expansion_modal';
|
||||
|
||||
import {useControlModal, ControlModal} from './useControlModal';
|
||||
import useCanSelfHostedExpand from './useCanSelfHostedExpand';
|
||||
|
||||
@@ -7,8 +7,8 @@ import {useDispatch, useSelector} from 'react-redux';
|
||||
import {trackEvent} from 'actions/telemetry_actions';
|
||||
import {closeModal, openModal} from 'actions/views/modals';
|
||||
import {ModalIdentifiers, TELEMETRY_CATEGORIES} from 'utils/constants';
|
||||
import SelfHostedPurchaseModal from 'components/self_hosted_purchase_modal';
|
||||
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from 'components/self_hosted_purchase_modal/constants';
|
||||
import SelfHostedPurchaseModal from 'components/self_hosted_purchases/self_hosted_purchase_modal';
|
||||
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from 'components/self_hosted_purchases/constants';
|
||||
import PurchaseInProgressModal from 'components/purchase_in_progress_modal';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
|
||||
|
||||
@@ -16,10 +16,13 @@ import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {PreferenceType} from '@mattermost/types/preferences';
|
||||
import {useExpandOverageUsersCheck} from 'components/common/hooks/useExpandOverageUsersCheck';
|
||||
import {LicenseLinks, StatTypes, Preferences} from 'utils/constants';
|
||||
import {LicenseLinks, StatTypes, Preferences, ConsolePages} from 'utils/constants';
|
||||
|
||||
import './overage_users_banner_notice.scss';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import useControlSelfHostedExpansionModal from 'components/common/hooks/useControlSelfHostedExpansionModal';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
import useCWSAvailabilityCheck from 'components/common/hooks/useCWSAvailabilityCheck';
|
||||
|
||||
type AdminHasDismissedArgs = {
|
||||
preferenceName: string;
|
||||
@@ -53,15 +56,16 @@ const OverageUsersBannerNotice = () => {
|
||||
const prefixLicenseId = (license.Id || '').substring(0, 8);
|
||||
const preferenceName = `${prefixPreferences}_overage_seats_${prefixLicenseId}`;
|
||||
|
||||
const isAirGapped = !useCWSAvailabilityCheck();
|
||||
const overageByUsers = activeUsers - seatsPurchased;
|
||||
const isOverageState = isBetween5PercerntAnd10PercentPurchasedSeats || isOver10PercerntPurchasedSeats;
|
||||
const hasPermission = isAdmin && isOverageState && !isCloud;
|
||||
const {
|
||||
cta,
|
||||
expandableLink,
|
||||
trackEventFn,
|
||||
getRequestState,
|
||||
isExpandable,
|
||||
expandableLink,
|
||||
} = useExpandOverageUsersCheck({
|
||||
shouldRequest: hasPermission && !adminHasDismissed({overagePreferences, preferenceName}),
|
||||
licenseId: license.Id,
|
||||
@@ -69,6 +73,8 @@ const OverageUsersBannerNotice = () => {
|
||||
banner: 'invite modal',
|
||||
});
|
||||
|
||||
const selfHostedExpansionModal = useControlSelfHostedExpansionModal({trackingLocation: 'overage_user_banner_notice'});
|
||||
|
||||
if (!hasPermission || adminHasDismissed({overagePreferences, preferenceName})) {
|
||||
return null;
|
||||
}
|
||||
@@ -96,12 +102,31 @@ const OverageUsersBannerNotice = () => {
|
||||
|
||||
const handleClick = () => {
|
||||
trackEventFn(isExpandable ? 'Self Serve' : 'Contact Sales');
|
||||
if (isExpandable) {
|
||||
selfHostedExpansionModal.open()
|
||||
}
|
||||
};
|
||||
|
||||
if (isAirGapped) {
|
||||
window.open(expandableLink(license.Id), '_blank');
|
||||
}
|
||||
|
||||
if (isExpandable) {
|
||||
return (
|
||||
<NavLink
|
||||
to={`${ConsolePages.LICENSE}?action=show_expansion_modal`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
{cta}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ExternalLink
|
||||
className='overage_users_banner__button'
|
||||
href={isExpandable ? expandableLink(license.Id) : LicenseLinks.CONTACT_SALES}
|
||||
href={LicenseLinks.CONTACT_SALES}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{cta}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {GlobalState} from 'types/store';
|
||||
import {TestHelper as TH} from 'utils/test_helper';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from 'components/self_hosted_purchase_modal/constants';
|
||||
import {STORAGE_KEY_PURCHASE_IN_PROGRESS} from 'components/self_hosted_purchases/constants';
|
||||
|
||||
import PurchaseInProgressModal from './';
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const STORAGE_KEY_EXPANSION_IN_PROGRESS = 'EXPANSION_IN_PROGRESS';
|
||||
@@ -2,3 +2,4 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const STORAGE_KEY_PURCHASE_IN_PROGRESS = 'PURCHASE_IN_PROGRESS';
|
||||
export const STORAGE_KEY_EXPANSION_IN_PROGRESS = 'EXPANSION_IN_PROGRESS';
|
||||
@@ -13,6 +13,7 @@ import './error_page.scss';
|
||||
|
||||
interface Props {
|
||||
canRetry: boolean;
|
||||
tryAgain: () => void;
|
||||
}
|
||||
|
||||
export default function SelfHostedExpansionErrorPage(props: Props) {
|
||||
@@ -70,9 +71,7 @@ export default function SelfHostedExpansionErrorPage(props: Props) {
|
||||
icon={icon}
|
||||
error={true}
|
||||
formattedButtonText={formattedButtonText}
|
||||
buttonHandler={() => {
|
||||
//TODO: Open self hosted expansion modal
|
||||
}}
|
||||
buttonHandler={props.tryAgain}
|
||||
formattedTertiaryButonText={tertiaryButtonText}
|
||||
tertiaryButtonHandler={() => window.open(contactSupportLink, '_blank', 'noreferrer')}
|
||||
/>
|
||||
@@ -11,10 +11,10 @@ import {StripeCardElementChangeEvent} from '@stripe/stripe-js';
|
||||
|
||||
import UpgradeSvg from 'components/common/svg_images_components/upgrade_svg';
|
||||
import RootPortal from 'components/root_portal';
|
||||
import ContactSalesLink from 'components/self_hosted_purchase_modal/contact_sales_link';
|
||||
import ErrorPage from 'components/self_hosted_expansion_modal/error_page';
|
||||
import SuccessPage from 'components/self_hosted_expansion_modal/success_page';
|
||||
import Submitting from 'components/self_hosted_purchase_modal/submitting';
|
||||
import ContactSalesLink from 'components/self_hosted_purchases/contact_sales_link';
|
||||
import ErrorPage from 'components/self_hosted_purchases/self_hosted_expansion_modal/error_page';
|
||||
import SuccessPage from 'components/self_hosted_purchases/self_hosted_expansion_modal/success_page';
|
||||
import Submitting from './submitting';
|
||||
|
||||
import useLoadStripe from 'components/common/hooks/useLoadStripe';
|
||||
import CardInput, {CardInputType} from 'components/payment_form/card_input';
|
||||
@@ -23,7 +23,7 @@ import Input from 'components/widgets/inputs/input/input';
|
||||
|
||||
import BackgroundSvg from 'components/common/svg_images_components/background_svg';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import StripeProvider from '../self_hosted_purchase_modal/stripe_provider';
|
||||
import StripeProvider from '../stripe_provider';
|
||||
|
||||
import {closeModal} from 'actions/views/modals';
|
||||
import {ModalIdentifiers, TELEMETRY_CATEGORIES} from 'utils/constants';
|
||||
@@ -46,11 +46,12 @@ import SelfHostedExpansionCard from './expansion_card';
|
||||
|
||||
import './self_hosted_expansion_modal.scss';
|
||||
|
||||
import {STORAGE_KEY_EXPANSION_IN_PROGRESS} from './constants';
|
||||
import Address from 'components/self_hosted_purchase_modal/address';
|
||||
import {STORAGE_KEY_EXPANSION_IN_PROGRESS} from '../constants';
|
||||
import Address from 'components/self_hosted_purchases/address';
|
||||
import ChooseDifferentShipping from 'components/choose_different_shipping';
|
||||
import Terms from 'components/self_hosted_purchase_modal/terms';
|
||||
import Terms from 'components/self_hosted_purchases/self_hosted_purchase_modal/terms';
|
||||
import useControlSelfHostedExpansionModal from 'components/common/hooks/useControlSelfHostedExpansionModal';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export interface FormState {
|
||||
cardName: string;
|
||||
@@ -189,6 +190,7 @@ export default function SelfHostedExpansionModal() {
|
||||
const [formState, setFormState] = useState<FormState>(initialState);
|
||||
const [show] = useState(true);
|
||||
const canRetry = formState.error !== '422';
|
||||
const showForm = progress !== SelfHostedSignupProgress.PAID && progress !== SelfHostedSignupProgress.CREATED_LICENSE && !formState.submitting && !formState.error && !formState.succeeded;
|
||||
|
||||
const title = intl.formatMessage({
|
||||
id: 'self_hosted_expansion.expansion_modal.title',
|
||||
@@ -200,6 +202,7 @@ export default function SelfHostedExpansionModal() {
|
||||
const submit = async () => {
|
||||
let submitProgress = progress;
|
||||
let signupCustomerResult: SelfHostedSignupCustomerResponse | null = null;
|
||||
setFormState({...formState, submitting: true});
|
||||
try {
|
||||
const [firstName, lastName] = inferNames(user, formState.cardName);
|
||||
|
||||
@@ -344,8 +347,7 @@ export default function SelfHostedExpansionModal() {
|
||||
}}
|
||||
>
|
||||
<div className='SelfHostedExpansionModal'>
|
||||
<div className='form-view'>
|
||||
<div className='lhs'>
|
||||
{<div className={classNames('form-view', {'form-view--hide': !showForm})}> <div className='lhs'>
|
||||
<h2 className='title'>{title}</h2>
|
||||
<UpgradeSvg
|
||||
width={267}
|
||||
@@ -503,21 +505,21 @@ export default function SelfHostedExpansionModal() {
|
||||
initialSeats={additionalSeats}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
{((formState.succeeded || progress === SelfHostedSignupProgress.CREATED_LICENSE)) && !formState.error && !formState.submitting && (
|
||||
<SuccessPage
|
||||
onClose={selfHostedExpansionModal.close}
|
||||
/>
|
||||
)}
|
||||
{formState.submitting && !formState.error && (
|
||||
{formState.submitting && (
|
||||
<Submitting
|
||||
desiredPlanName={currentPlan}
|
||||
progressBar={formState.progressBar}
|
||||
currentPlan={currentPlan}
|
||||
/>
|
||||
)}
|
||||
{formState.error && (
|
||||
<ErrorPage
|
||||
canRetry={canRetry}
|
||||
tryAgain={() => {setFormState({...formState, submitting: false, error: ''})}}
|
||||
/>
|
||||
)}
|
||||
<div className='background-svg'>
|
||||
@@ -83,6 +83,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&--hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
>.lhs {
|
||||
width: 25%;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getSelfHostedSignupProgress} from 'mattermost-redux/selectors/entities/hosted_customer';
|
||||
import {SelfHostedSignupProgress} from '@mattermost/types/hosted_customer';
|
||||
import {ValueOf} from '@mattermost/types/utilities';
|
||||
|
||||
import CreditCardSvg from 'components/common/svg_images_components/credit_card_svg';
|
||||
import IconMessage from 'components/purchase_modal/icon_message';
|
||||
|
||||
function useConvertProgressToWaitingExplanation(progress: ValueOf<typeof SelfHostedSignupProgress>, planName: string): React.ReactNode {
|
||||
const intl = useIntl();
|
||||
switch (progress) {
|
||||
case SelfHostedSignupProgress.START:
|
||||
case SelfHostedSignupProgress.CREATED_CUSTOMER:
|
||||
case SelfHostedSignupProgress.CREATED_INTENT:
|
||||
return intl.formatMessage({
|
||||
id: 'self_hosted_signup.progress_step.submitting_payment',
|
||||
defaultMessage: 'Submitting payment information',
|
||||
});
|
||||
case SelfHostedSignupProgress.CONFIRMED_INTENT:
|
||||
case SelfHostedSignupProgress.CREATED_SUBSCRIPTION:
|
||||
return intl.formatMessage({
|
||||
id: 'self_hosted_signup.progress_step.verifying_payment',
|
||||
defaultMessage: 'Verifying payment details',
|
||||
});
|
||||
case SelfHostedSignupProgress.PAID:
|
||||
case SelfHostedSignupProgress.CREATED_LICENSE:
|
||||
return intl.formatMessage({
|
||||
id: 'self_hosted_signup.progress_step.applying_license',
|
||||
defaultMessage: 'Applying your {planName} license to your Mattermost instance',
|
||||
}, {planName});
|
||||
default:
|
||||
return intl.formatMessage({
|
||||
id: 'self_hosted_signup.progress_step.submitting_payment',
|
||||
defaultMessage: 'Submitting payment information',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function convertProgressToBar(progress: ValueOf<typeof SelfHostedSignupProgress>): number {
|
||||
switch (progress) {
|
||||
case SelfHostedSignupProgress.START:
|
||||
return 15;
|
||||
case SelfHostedSignupProgress.CREATED_CUSTOMER:
|
||||
return 30;
|
||||
case SelfHostedSignupProgress.CREATED_INTENT:
|
||||
return 45;
|
||||
case SelfHostedSignupProgress.CONFIRMED_INTENT:
|
||||
return 60;
|
||||
case SelfHostedSignupProgress.CREATED_SUBSCRIPTION:
|
||||
return 75;
|
||||
case SelfHostedSignupProgress.PAID:
|
||||
return 85;
|
||||
case SelfHostedSignupProgress.CREATED_LICENSE:
|
||||
return 100;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
currentPlan: string;
|
||||
}
|
||||
|
||||
const maxProgressBar = 100;
|
||||
const maxFakeProgressIncrement = 5;
|
||||
const fakeProgressInterval = 600;
|
||||
|
||||
export default function Submitting(props: Props) {
|
||||
const [barProgress, setBarProgress] = useState(0);
|
||||
const signupProgress = useSelector(getSelfHostedSignupProgress);
|
||||
const waitingExplanation = useConvertProgressToWaitingExplanation(signupProgress, props.currentPlan);
|
||||
const footer = (
|
||||
<div className='ProcessPayment-progress'>
|
||||
<div
|
||||
className='ProcessPayment-progress-fill'
|
||||
style={{width: `${barProgress}%`}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const maxProgressForCurrentSignupProgress = convertProgressToBar(signupProgress);
|
||||
const interval = setInterval(() => {
|
||||
if (barProgress < maxProgressBar) {
|
||||
setBarProgress(Math.min(maxProgressForCurrentSignupProgress, barProgress + maxFakeProgressIncrement));
|
||||
}
|
||||
}, fakeProgressInterval);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [barProgress]);
|
||||
|
||||
return (
|
||||
|
||||
<div className='submitting'>
|
||||
<IconMessage
|
||||
formattedTitle={(
|
||||
<FormattedMessage
|
||||
id='admin.billing.subscription.verifyPaymentInformation'
|
||||
defaultMessage='Verifying your payment information'
|
||||
/>
|
||||
)}
|
||||
formattedSubtitle={waitingExplanation}
|
||||
icon={
|
||||
<CreditCardSvg
|
||||
width={444}
|
||||
height={313}
|
||||
/>
|
||||
}
|
||||
footer={footer}
|
||||
className={'processing'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {SelfHostedProducts, ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import {DeepPartial} from '@mattermost/types/utilities';
|
||||
|
||||
import SelfHostedPurchaseModal, {makeInitialState, canSubmit, State} from './';
|
||||
import SelfHostedPurchaseModal, {makeInitialState, canSubmit, State} from '.';
|
||||
|
||||
interface MockCardInputProps {
|
||||
onCardInputChange: (event: {complete: boolean}) => void;
|
||||
@@ -53,23 +53,23 @@ import {
|
||||
SelfHostedSignupCustomerResponse,
|
||||
} from '@mattermost/types/hosted_customer';
|
||||
|
||||
import {Seats, errorInvalidNumber} from '../seats_calculator';
|
||||
import {Seats, errorInvalidNumber} from '../../seats_calculator';
|
||||
|
||||
import ContactSalesLink from './contact_sales_link';
|
||||
import ContactSalesLink from '../contact_sales_link';
|
||||
import Submitting, {convertProgressToBar} from './submitting';
|
||||
import ErrorPage from './error';
|
||||
import SuccessPage from './success_page';
|
||||
import SelfHostedCard from './self_hosted_card';
|
||||
import StripeProvider from './stripe_provider';
|
||||
import StripeProvider from '../stripe_provider';
|
||||
import Terms from './terms';
|
||||
import Address from './address';
|
||||
import Address from '../address';
|
||||
import useNoEscape from './useNoEscape';
|
||||
|
||||
import {SetPrefix, UnionSetActions} from './types';
|
||||
|
||||
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 {
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
SelfHostedProducts,
|
||||
} from 'utils/constants';
|
||||
|
||||
import Consequences from '../seats_calculator/consequences';
|
||||
import SeatsCalculator, {Seats} from '../seats_calculator';
|
||||
import Consequences from '../../seats_calculator/consequences';
|
||||
import SeatsCalculator, {Seats} from '../../seats_calculator';
|
||||
|
||||
// Card has a bunch of props needed for monthly/yearly payments that
|
||||
// do not apply to self-hosted.
|
||||
Ссылка в новой задаче
Block a user