[MM-51854] Onboarding Role selection screen (#23121)

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Julien Tant
2023-05-08 13:57:41 -07:00
коммит произвёл GitHub
родитель 657c0024f9
Коммит d40689466d
101 изменённых файлов: 5022 добавлений и 285 удалений

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

@@ -10,3 +10,25 @@
border: 2px solid var(--button-bg);
}
}
@mixin sideIllustrationKeyframe($name, $rightPx) {
@keyframes #{$name}SlideInRight {
from {
right: -#{$rightPx}px;
}
to {
right: 0;
}
}
@keyframes #{$name}SlideOutRight {
from {
right: 0;
}
to {
right: -#{$rightPx}px;
}
}
}

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

@@ -1,5 +1,6 @@
@import 'utils/variables';
@import 'utils/mixins';
@import 'mixins';
.PreparingWorkspace {
width: 100vw;
@@ -93,34 +94,35 @@
animation-duration: 0.3s;
animation-fill-mode: forwards;
animation-timing-function: ease-in-out;
}
}
.enter {
animation-name: slideInRight;
}
@include sideIllustrationKeyframe(invitation, 651);
.exit {
animation-name: slideOutRight;
}
&.enter {
animation-name: invitationSlideInRight;
}
@keyframes slideInRight {
from {
right: -651px;
&.exit {
animation-name: invitationSlideOutRight;
}
}
to {
right: 0;
}
}
&__roles-illustration {
position: absolute;
right: -295px;
bottom: 0;
animation-duration: 0.3s;
animation-fill-mode: forwards;
animation-timing-function: ease-in-out;
@keyframes slideOutRight {
from {
right: 0;
}
@include sideIllustrationKeyframe(roles, 295);
to {
right: -651px;
&.enter {
animation-name: rolesSlideInRight;
}
&.exit {
animation-name: rolesSlideOutRight;
}
}
}
@@ -146,3 +148,10 @@
overflow-y: visible;
}
}
@media screen and (max-width: 1199px) {
.PreparingWorkspace__roles-illustration,
.PreparingWorkspace__invite-members-illustration {
display: none;
}
}

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

@@ -18,6 +18,8 @@ import {getCurrentTeam, getMyTeams} from 'mattermost-redux/selectors/entities/te
import {getFirstAdminSetupComplete, getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {Client4} from 'mattermost-redux/client';
import {CategoryOther} from '@mattermost/types/work_templates';
import Constants from 'utils/constants';
import {getSiteURL, teamNameToUrl} from 'utils/url';
import {makeNewTeam} from 'utils/team_utils';
@@ -42,6 +44,8 @@ import {
} from './steps';
import Organization from './organization';
import Roles from './roles';
import RolesIllustration from './roles_illustration';
import Plugins from './plugins';
import Progress from './progress';
import InviteMembers from './invite_members';
@@ -90,6 +94,7 @@ function makeSubmitFail(step: WizardStep) {
const trackSubmitFail = {
[WizardSteps.Organization]: makeSubmitFail(WizardSteps.Organization),
[WizardSteps.Roles]: makeSubmitFail(WizardSteps.Roles),
[WizardSteps.Plugins]: makeSubmitFail(WizardSteps.Plugins),
[WizardSteps.InviteMembers]: makeSubmitFail(WizardSteps.InviteMembers),
[WizardSteps.LaunchingWorkspace]: makeSubmitFail(WizardSteps.LaunchingWorkspace),
@@ -97,6 +102,7 @@ const trackSubmitFail = {
const onPageViews = {
[WizardSteps.Organization]: makeOnPageView(WizardSteps.Organization),
[WizardSteps.Roles]: makeOnPageView(WizardSteps.Roles),
[WizardSteps.Plugins]: makeOnPageView(WizardSteps.Plugins),
[WizardSteps.InviteMembers]: makeOnPageView(WizardSteps.InviteMembers),
[WizardSteps.LaunchingWorkspace]: makeOnPageView(WizardSteps.LaunchingWorkspace),
@@ -128,6 +134,7 @@ const PreparingWorkspace = (props: Props) => {
const stepOrder = [
isSelfHosted && WizardSteps.Organization,
WizardSteps.Roles,
pluginsEnabled && WizardSteps.Plugins,
WizardSteps.InviteMembers,
WizardSteps.LaunchingWorkspace,
@@ -256,6 +263,7 @@ const PreparingWorkspace = (props: Props) => {
// even if admin skipped submitting plugins.
const completeSetupRequest = {
organization: form.organization,
role: form.role === CategoryOther ? form.roleOther : form.role,
install_plugins: pluginsToSetup,
};
@@ -365,6 +373,15 @@ const PreparingWorkspace = (props: Props) => {
return '';
}, [currentStep]);
const getRolesAnimationClass = useCallback(() => {
if (currentStep === WizardSteps.Roles) {
return 'enter';
} else if (mostRecentStep === WizardSteps.Roles) {
return 'exit';
}
return '';
}, [currentStep]);
let previous: React.ReactNode = (
<div
onClick={goPrevious}
@@ -431,6 +448,48 @@ const PreparingWorkspace = (props: Props) => {
updateTeam={updateTeam}
/>
<Roles
onPageView={onPageViews[WizardSteps.Roles]}
previous={previous}
next={() => {
makeNext(WizardSteps.Roles)({
role: form.role,
roleOther: form.roleOther,
});
}}
quickNext={(role: string) => {
setForm({
...form,
role,
roleOther: '',
});
makeNext(WizardSteps.Roles)({
role,
roleOther: '',
});
}}
skip={() => {
setForm({
...form,
role: '',
roleOther: '',
});
makeNext(WizardSteps.Roles, true)();
}}
transitionDirection={getTransitionDirection(WizardSteps.Roles)}
show={shouldShowPage(WizardSteps.Roles)}
role={form.role}
roleOther={form.roleOther}
setRole={(role: Form['role'], roleOther: Form['roleOther']) => {
setForm({
...form,
role,
roleOther,
});
}}
className='child-page'
/>
<Plugins
isSelfHosted={isSelfHosted}
onPageView={onPageViews[WizardSteps.Plugins]}
@@ -508,6 +567,9 @@ const PreparingWorkspace = (props: Props) => {
<div className={`PreparingWorkspace__invite-members-illustration ${getInviteMembersAnimationClass()}`}>
<InviteMembersIllustration/>
</div>
<div className={`PreparingWorkspace__roles-illustration ${getRolesAnimationClass()}`}>
<RolesIllustration/>
</div>
</div>
);
};

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

@@ -0,0 +1,47 @@
@import 'utils/mixins';
$btn-margin-right: 24px;
.plugins-skip-btn {
margin-left: 8px;
}
.Roles__input {
width: calc(100% - $btn-margin-right);
padding: 10px 16px;
border: 2px solid rgba(var(--center-channel-color-rgb), 0.16);
border-radius: 4px;
font-size: 16px;
&:active,
&:focus {
border: 2px solid var(--button-bg);
}
}
.Roles-body {
.PreparingWorkspacePageBody {
max-width: 600px;
}
.Roles-list {
display: flex;
flex-wrap: wrap;
}
.role-button {
@include secondary-button;
@include button-large;
border-color: rgba(63, 67, 80, 0.16);
margin-right: $btn-margin-right;
margin-bottom: 24px;
color: #3f4350;
&.active {
background: rgba(var(--denim-button-bg-rgb), 0.16);
}
}
}
@include simple-in-and-out("Roles");

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

@@ -0,0 +1,158 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {CSSTransition} from 'react-transition-group';
import {Animations, mapAnimationReasonToClass, Form, PreparingWorkspacePageProps} from './steps';
import Title from './title';
import Description from './description';
import PageBody from './page_body';
import SingleColumnLayout from './single_column_layout';
import PageLine from './page_line';
import './roles.scss';
import {useDispatch, useSelector} from 'react-redux';
import {getWorkTemplateCategories as fetchCategories} from 'mattermost-redux/actions/work_templates';
import {getWorkTemplateCategories} from 'selectors/work_template';
import classNames from 'classnames';
import QuickInput from 'components/quick_input';
import {CategoryOther} from '@mattermost/types/work_templates';
type Props = PreparingWorkspacePageProps & {
role: Form['role'];
roleOther: Form['roleOther'];
setRole: (role: string, roleOther: string) => void;
className?: string;
quickNext(role: string): void;
}
const Roles = ({role, next, ...props}: Props) => {
const {formatMessage} = useIntl();
const dispatch = useDispatch();
const categories = useSelector(getWorkTemplateCategories);
let className = 'Roles-body';
useEffect(() => {
dispatch(fetchCategories());
}, []);
useEffect(() => {
if (props.show) {
props.onPageView();
}
}, [props.show]);
if (props.className) {
className += ' ' + props.className;
}
const title = (
<FormattedMessage
id={'onboarding_wizard.roles.title'}
defaultMessage='What is your primary function?'
/>
);
const description = (
<FormattedMessage
id={'onboarding_wizard.roles.description'}
defaultMessage={'Well use this to suggest templates to help get you started.'}
/>
);
const selectRole = (role: string) => {
if (role !== CategoryOther) {
props.quickNext(role);
}
props.setRole(role, '');
};
const roleIsSet = Boolean(role);
const roleOtherIsSet = Boolean(props.roleOther);
const canContinue = (roleIsSet && role !== CategoryOther) || (role === CategoryOther && roleOtherIsSet);
return (
<CSSTransition
in={props.show}
timeout={Animations.PAGE_SLIDE}
classNames={mapAnimationReasonToClass('Roles', props.transitionDirection)}
mountOnEnter={true}
unmountOnExit={true}
>
<div className={className}>
<SingleColumnLayout>
<PageLine
style={{
marginBottom: '50px',
marginLeft: '50px',
height: 'calc(25vh)',
}}
noLeft={true}
/>
{props.previous}
<Title>
{title}
</Title>
<Description>{description}</Description>
<PageBody>
<div className='Roles-list'>
{categories.map((category) => (
<button
key={category.id}
className={classNames('role-button', {active: category.id === role})}
onClick={() => selectRole(category.id)}
>
{category.name}
</button>
))}
</div>
{role === CategoryOther && (
<QuickInput
value={props.roleOther || ''}
className='Roles__input'
onChange={(e) => props.setRole(CategoryOther, e.target.value)}
placeholder={formatMessage({
id: 'onboarding_wizard.roles.other_input_placeholder',
defaultMessage: 'Please share your primary function',
})}
/>
)}
</PageBody>
<div>
<button
className='primary-button'
onClick={next}
disabled={!canContinue}
>
<FormattedMessage
id={'onboarding_wizard.next'}
defaultMessage='Continue'
/>
</button>
<button
className='link-style plugins-skip-btn'
onClick={props.skip}
>
<FormattedMessage
id={'onboarding_wizard.skip-button'}
defaultMessage='Skip'
/>
</button>
</div>
<PageLine
style={{
marginTop: '50px',
marginLeft: '50px',
height: 'calc(30vh)',
}}
noLeft={true}
/>
</SingleColumnLayout>
</div>
</CSSTransition>
);
};
export default Roles;

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@@ -5,6 +5,7 @@ import deepFreeze from 'mattermost-redux/utils/deep_freeze';
export const WizardSteps = {
Organization: 'Organization',
Roles: 'Roles',
Plugins: 'Plugins',
InviteMembers: 'InviteMembers',
LaunchingWorkspace: 'LaunchingWorkspace',
@@ -24,6 +25,8 @@ export function mapStepToNextName(step: WizardStep): string {
switch (step) {
case WizardSteps.Organization:
return 'admin_onboarding_next_organization';
case WizardSteps.Roles:
return 'admin_onboarding_next_roles';
case WizardSteps.Plugins:
return 'admin_onboarding_next_plugins';
case WizardSteps.InviteMembers:
@@ -39,6 +42,8 @@ export function mapStepToPrevious(step: WizardStep): string {
switch (step) {
case WizardSteps.Organization:
return 'admin_onboarding_previous_organization';
case WizardSteps.Roles:
return 'admin_onboarding_previous_roles';
case WizardSteps.Plugins:
return 'admin_onboarding_previous_plugins';
case WizardSteps.InviteMembers:
@@ -54,6 +59,8 @@ export function mapStepToPageView(step: WizardStep): string {
switch (step) {
case WizardSteps.Organization:
return 'pageview_admin_onboarding_organization';
case WizardSteps.Roles:
return 'pageview_admin_onboarding_roles';
case WizardSteps.Plugins:
return 'pageview_admin_onboarding_plugins';
case WizardSteps.InviteMembers:
@@ -69,6 +76,8 @@ export function mapStepToSubmitFail(step: WizardStep): string {
switch (step) {
case WizardSteps.Organization:
return 'admin_onboarding_organization_submit_fail';
case WizardSteps.Roles:
return 'admin_onboarding_roles_submit_fail';
case WizardSteps.Plugins:
return 'admin_onboarding_plugins_submit_fail';
case WizardSteps.InviteMembers:
@@ -84,6 +93,8 @@ export function mapStepToSkipName(step: WizardStep): string {
switch (step) {
case WizardSteps.Organization:
return 'admin_onboarding_skip_organization';
case WizardSteps.Roles:
return 'admin_onboarding_skip_roles';
case WizardSteps.Plugins:
return 'admin_onboarding_skip_plugins';
case WizardSteps.InviteMembers:
@@ -127,6 +138,8 @@ export const PLUGIN_NAME_TO_ID_MAP: PluginNameMap = {
export type Form = {
organization?: string;
role?: string;
roleOther?: string;
url?: string;
urlSkipped: boolean;
inferredProtocol: 'http' | 'https' | null;
@@ -184,6 +197,8 @@ export const emptyForm = deepFreeze({
invites: [],
skipped: false,
},
role: '',
roleOther: '',
});
export type PreparingWorkspacePageProps = {

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

@@ -37,6 +37,11 @@ const CategoryButton = styled.button`
background: none;
text-align: left;
&:hover {
background: rgba(var(--denim-button-bg-rgb), 0.04);
cursor: pointer;
}
&.selected {
background: rgba(var(--denim-button-bg-rgb), 0.04);
font-weight: 600;

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

@@ -4236,6 +4236,9 @@
"onboarding_wizard.plugins.zoom": "Zoom",
"onboarding_wizard.plugins.zoom.tooltip": "Start Zoom audio and video conferencing calls in Mattermost with a single click",
"onboarding_wizard.previous": "Previous",
"onboarding_wizard.roles.description": "Well use this to suggest templates to help get you started.",
"onboarding_wizard.roles.other_input_placeholder": "Please share your primary function",
"onboarding_wizard.roles.title": "What is your primary function?",
"onboarding_wizard.self_hosted_plugins.description": "Choose the tools you work with, and we'll add them to your workspace. Additional set up may be needed later.",
"onboarding_wizard.self_hosted_plugins.title": "What tools do you use?",
"onboarding_wizard.skip-button": "Skip",

Двоичные данные
webapp/channels/src/images/worktemplates/boards/content_calendar.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 53 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/content_calendar/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/create_project/create_project.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/feature_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/feature_release/feature_release.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/product_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/product_release/product_release.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/sprint_planning/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/design/sprint_planning/sprint_planning.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/bug_bash/bug_bash.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/bug_bash/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 565 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/create_project/create_project.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/sprint_planning/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/devops/sprint_planning/sprint_planning.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/bug_bash/bug_bash.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/bug_bash/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 565 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/feature_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/goals_and_okrs/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/engineering/sprint_planning/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/leadership/content_calendar/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/leadership/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/leadership/incident_resolution/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 88 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/content_calendar/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 80 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/create_project/create_project.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/goals_and_okrs/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/goals_and_okrs/goals_and_okrs.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/marketing/product_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/create_project/create_project.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/feature_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/feature_release/feature_release.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/goals_and_okrs/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/goals_and_okrs/goals_and_okrs.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/incident_resolution/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 88 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/product_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/other/product_release/product_release.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 98 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 182 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 112 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/bug_bash/bug_bash.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/bug_bash/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 565 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/create_project/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 568 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/create_project/create_project.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/incident_resolution/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 88 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/product_release/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/product_release/product_release.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 113 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/sprint_planning/channel.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 567 KiB

Двоичные данные
webapp/channels/src/images/worktemplates/qa/sprint_planning/sprint_planning.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 68 KiB

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

@@ -23,3 +23,4 @@ export const areWorkTemplatesEnabled = createSelector(
},
);
export const getWorkTemplateCategories = (state: GlobalState) => state.entities.worktemplates.categories;