Merge branch 'master' into MM-50966-in-product-expansion

Этот коммит содержится в:
Conor Macpherson
2023-04-04 09:22:58 -04:00
коммит произвёл GitHub
родитель 0ce1b6c12c ed36e8bd64
Коммит e127f68149
213 изменённых файлов: 3970 добавлений и 4558 удалений

Просмотреть файл

@@ -26,6 +26,7 @@ import {
CreateSubscriptionRequest,
Feedback,
WorkspaceDeletionRequest,
NewsletterRequestBody,
} from '@mattermost/types/cloud';
import {
SelfHostedSignupForm,
@@ -3894,6 +3895,7 @@ export default class Client4 {
);
};
confirmSelfHostedExpansion = (setupIntentId: string, expandRequest: SelfHostedExpansionRequest) => {
return this.doFetch<SelfHostedSignupSuccessResponse>(
`${this.getHostedCustomerRoute()}/confirm?expand=true`,
@@ -3901,6 +3903,13 @@ export default class Client4 {
);
}
subscribeToNewsletter = (newletterRequestBody: NewsletterRequestBody) => {
return this.doFetch<StatusOK>(
`${this.getHostedCustomerRoute()}/subscribe-newsletter`,
{method: 'post', body: JSON.stringify(newletterRequestBody)},
);
};
createPaymentMethod = async () => {
return this.doFetch(
`${this.getCloudRoute()}/payment`,

Просмотреть файл

@@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useEffect, useState} from 'react';
export const useFollowElementDimensions = (elementId: string): DOMRectReadOnly => {
const [dimensions, setDimensions] = useState(new DOMRect());
useEffect(() => {
const element = document.getElementById(elementId);
if (!element) {
return undefined;
}
const observer = new ResizeObserver((entries) => {
if (entries.length > 0) {
setDimensions(entries[0].contentRect);
}
});
observer.observe(element);
return () => {
observer.unobserve(element);
};
}, [elementId]);
return dimensions;
};

Просмотреть файл

@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/GenericModal/FooterPagination should render default 1`] = `
<div
className="footer-pagination"
>
<div
className="footer-pagination__legend"
/>
<div
className="footer-pagination__button-container"
>
<button
className="footer-pagination__button-container__button disabled"
disabled={true}
onClick={[MockFunction]}
type="button"
>
<ChevronLeftIcon
size={16}
/>
<span>
Previous
</span>
</button>
<button
className="footer-pagination__button-container__button disabled"
disabled={true}
onClick={[MockFunction]}
type="button"
>
<span>
Next
</span>
<ChevronRightIcon
size={16}
/>
</button>
</div>
</div>
`;

Просмотреть файл

@@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
@import '../../../../../channels/src/sass/utils/mixins';
.footer-pagination {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
&__legend {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-start;
color: rgba(var(--center-channel-color-rgb), 0.64);
font-size: 12px;
font-weight: 600;
line-height: 16px;
}
&__button-container {
display: flex;
align-items: center;
justify-content: center;
&__button {
@include tertiary-button;
@include button-small;
&:not(:first-child) {
margin-left: 8px;
}
> :not(:first-child) {
margin-left: 5px;
}
}
}
}

Просмотреть файл

@@ -0,0 +1,88 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import {FooterPagination} from './';
describe('components/GenericModal/FooterPagination', () => {
const baseProps = {
page: 0,
total: 0,
itemsPerPage: 0,
onNextPage: jest.fn(),
onPreviousPage: jest.fn(),
};
test('should render default', () => {
const wrapper = shallow(
<FooterPagination {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should render pagination legend', () => {
const wrapper = shallow(
<FooterPagination
{...baseProps}
page={0}
total={17}
itemsPerPage={10}
/>,
);
const legend = wrapper.find('.footer-pagination__legend');
expect(legend.length).toEqual(1);
expect(legend.at(0).text()).toEqual('Showing 1-10 of 17');
});
test('should render pagination buttons', () => {
const wrapper = shallow(
<FooterPagination
{...baseProps}
page={1}
total={30}
itemsPerPage={10}
/>,
);
const buttons = wrapper.find('.footer-pagination__button-container__button');
expect(buttons.length).toEqual(2);
expect(buttons.at(0).text()).toEqual('<ChevronLeftIcon />Previous');
expect(buttons.at(1).text()).toEqual('Next<ChevronRightIcon />');
});
test('should handle pagination buttons', async () => {
const onPreviousPage = jest.fn();
const onNextPage = jest.fn();
const wrapper = shallow(
<FooterPagination
page={1}
total={30}
itemsPerPage={10}
onPreviousPage={onPreviousPage}
onNextPage={onNextPage}
/>,
);
const buttons = wrapper.find('.footer-pagination__button-container__button');
const prevButton = buttons.at(0);
const nextButton = buttons.at(1);
expect(prevButton.hasClass('disabled')).toBeFalsy();
expect(nextButton.hasClass('disabled')).toBeFalsy();
nextButton.simulate('click');
expect(onNextPage).toHaveBeenCalledTimes(1);
prevButton.simulate('click');
expect(onPreviousPage).toHaveBeenCalledTimes(1);
});
});

Просмотреть файл

@@ -0,0 +1,93 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React from 'react';
import {useIntl} from 'react-intl';
import {ChevronLeftIcon, ChevronRightIcon} from '@mattermost/compass-icons/components';
import './footer_pagination.scss';
const BUTTON_ICON_SIZE = 16;
type FooterPaginationProps = {
page: number;
total: number;
itemsPerPage: number;
onNextPage: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onPreviousPage: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
};
export const FooterPagination = ({
page,
total,
itemsPerPage,
onNextPage,
onPreviousPage,
}: FooterPaginationProps) => {
const {formatMessage} = useIntl();
const startCount = page * itemsPerPage;
const endCount = Math.min(startCount + itemsPerPage, total);
const totalPages = Math.trunc((total - 1) / itemsPerPage);
const prevDisabled = page <= 0;
const nextDisabled = page >= totalPages;
return (
<div className='footer-pagination'>
<div className='footer-pagination__legend'>
{Boolean(total) && (
formatMessage(
{
id: 'footer_pagination.count',
defaultMessage: 'Showing {startCount, number}-{endCount, number} of {total, number}',
},
{
startCount: startCount + 1,
endCount,
total,
},
)
)}
</div>
<div className='footer-pagination__button-container'>
<button
type='button'
className={classNames(
'footer-pagination__button-container__button',
{disabled: prevDisabled},
)}
onClick={onPreviousPage}
disabled={prevDisabled}
>
<ChevronLeftIcon size={BUTTON_ICON_SIZE}/>
<span>
{formatMessage({
id: 'footer_pagination.prev',
defaultMessage: 'Previous',
})}
</span>
</button>
<button
type='button'
className={classNames(
'footer-pagination__button-container__button',
{disabled: nextDisabled},
)}
onClick={onNextPage}
disabled={nextDisabled}
>
<span>
{formatMessage({
id: 'footer_pagination.next',
defaultMessage: 'Next',
})}
</span>
<ChevronRightIcon size={BUTTON_ICON_SIZE}/>
</button>
</div>
</div>
);
};

Просмотреть файл

@@ -0,0 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export * from './footer_pagination';

Просмотреть файл

@@ -34,13 +34,17 @@ export type Props = {
enforceFocus?: boolean;
container?: React.ReactNode | React.ReactNodeArray;
ariaLabel?: string;
errorText?: string;
errorText?: string | React.ReactNode;
compassDesign?: boolean;
backdrop?: boolean;
backdropClassName?: string;
tabIndex?: number;
children: React.ReactNode;
keyboardEscape?: boolean;
headerInput?: React.ReactNode;
bodyPadding?: boolean;
footerContent?: React.ReactNode;
footerDivider?: boolean;
};
type State = {
@@ -56,6 +60,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
autoCloseOnConfirmButton: true,
enforceFocus: true,
keyboardEscape: true,
bodyPadding: true,
};
constructor(props: Props) {
@@ -195,7 +200,12 @@ export class GenericModal extends React.PureComponent<Props, State> {
className='GenericModal__wrapper-enter-key-press-catcher'
>
<Modal.Header closeButton={true}>
{this.props.compassDesign && headerText}
{this.props.compassDesign && (
<>
{headerText}
{this.props.headerInput}
</>
)}
</Modal.Header>
<Modal.Body>
{this.props.compassDesign ? (
@@ -208,14 +218,22 @@ export class GenericModal extends React.PureComponent<Props, State> {
) : (
headerText
)}
<div className='GenericModal__body'>
<div className={classNames('GenericModal__body', {padding: this.props.bodyPadding})}>
{this.props.children}
</div>
</Modal.Body>
{(cancelButton || confirmButton) && <Modal.Footer>
{cancelButton}
{confirmButton}
</Modal.Footer>}
{(cancelButton || confirmButton || this.props.footerContent) && (
<Modal.Footer className={classNames({divider: this.props.footerDivider})}>
{(cancelButton || confirmButton) ? (
<>
{cancelButton}
{confirmButton}
</>
) : (
this.props.footerContent
)}
</Modal.Footer>
)}
</div>
</FocusTrap>
</Modal>

Просмотреть файл

@@ -7,6 +7,7 @@ export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './sk
export type {Props as FocusTrapProps} from './focus_trap';
// components
export * from './generic_modal/footer_content';
export {GenericModal} from './generic_modal/generic_modal';
export {CircleSkeletonLoader, RectangleSkeletonLoader} from './skeleton_loader';
export * from './tour_tip';
@@ -16,3 +17,4 @@ export {FocusTrap} from './focus_trap';
// hooks
export * from './common/hooks/useMeasurePunchouts';
export {useElementAvailable} from './common/hooks/useElementAvailable';
export {useFollowElementDimensions} from './common/hooks/useFollowElementDimensions';

Просмотреть файл

@@ -363,7 +363,7 @@
&__backdrop {
position: absolute;
z-index: 999;
z-index: 100;
top: 0;
left: 0;
width: 100%;

Просмотреть файл

@@ -90,15 +90,10 @@ export const TourTip = ({
}: Props) => {
const FIRST_STEP_INDEX = 0;
const triggerRef = useRef(null);
const [useBackdrop, setUseBackdrop] = useState(!hideBackdrop);
const onJump = (event: React.MouseEvent, jumpToStep: number) => {
handleJump?.(event, jumpToStep);
};
useClickOutsideRef(triggerRef, () => {
setUseBackdrop(false);
});
// This needs to be changed if root-portal node isn't available to maybe body
const rootPortal = document.getElementById('root-portal');
@@ -214,7 +209,7 @@ export const TourTip = ({
>
<PulsatingDot/>
</div>
{useBackdrop && <TourTipBackdrop
<TourTipBackdrop
show={show}
onDismiss={handleDismiss}
onPunchOut={handlePunchOut}
@@ -222,7 +217,7 @@ export const TourTip = ({
overlayPunchOut={overlayPunchOut}
appendTo={rootPortal!}
transparent={hideBackdrop}
/>}
/>
{show && (
<Tippy
showOnCreate={show}

Просмотреть файл

@@ -81,4 +81,3 @@ export const TourTipBackdrop = ({
</TourTipRootPortal>
);
};

Просмотреть файл

@@ -220,6 +220,11 @@ export interface CreateSubscriptionRequest {
internal_purchase_order?: string;
}
export interface NewsletterRequestBody {
email: string;
subscribed_content: string;
}
export const areShippingDetailsValid = (address: Address | null | undefined): boolean => {
if (!address) {
return false;

Просмотреть файл

@@ -116,6 +116,7 @@ export type ClientConfig = {
ExperimentalViewArchivedChannels: string;
FileLevel: string;
FeatureFlagAppsEnabled: string;
FeatureFlagAppsSidebarCategory: string;
FeatureFlagBoardsProduct: string;
FeatureFlagCallsEnabled: string;
FeatureFlagGraphQL: string;