1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: demo ui-ux improvements (#3634)

https://linear.app/unleash/issue/2-914/improve-demo-guide-uiux

Includes a big batch of UI/UX improvements, including but not limited
to:

- Updating steps text;
- Improve behavior of intro step dialogs (use normal dialogs);
- Improve overall design;
- Improve escape key and backdrop click behaviors;
- Add plans dialog;
- Add sticky demo banner;
- Assume `demo-app` project and `dev` environment to better fit our demo
instance;
- Misc fixes and refactors;

Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item:
#3537


![image](https://user-images.githubusercontent.com/14320932/234637210-1936fd48-ce40-4980-81ae-f1fe64e65545.png)
This commit is contained in:
Nuno Góis 2023-04-27 08:15:17 +01:00 committed by GitHub
parent 21640481ab
commit 3599e7478c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 575 additions and 201 deletions

View File

@ -0,0 +1,9 @@
<svg width="45" height="61" viewBox="0 0 45 61" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.789" d="M29.701 38.852C25.591 37.941 24.679 37.007 23.764 32.915C23.664 32.469 23.764 32 23.306 32C22.848 32 22.937 32.469 22.848 32.915C21.937 37.044 21.022 37.94 16.911 38.852C16.465 38.952 16 38.852 16 39.31C16 39.768 16.465 39.679 16.911 39.768C21.021 40.679 21.936 41.613 22.848 45.705C22.948 46.151 22.848 46.62 23.306 46.62C23.764 46.62 23.675 46.151 23.764 45.705C24.679 41.595 25.609 40.68 29.701 39.768C30.147 39.668 30.616 39.768 30.616 39.31C30.616 38.852 30.148 38.952 29.701 38.852Z" fill="white"/>
<path opacity="0.4" d="M10.686 52.1838C7.46503 51.4728 6.75403 50.7558 6.06003 47.5578C5.97903 47.2048 6.06003 46.8408 5.70103 46.8408C5.34203 46.8408 5.41803 47.2048 5.34203 47.5578C4.62503 50.7788 3.90803 51.4958 0.716026 52.1838C0.369026 52.2648 -0.000976562 52.1838 -0.000976562 52.5428C-0.000976562 52.9018 0.369026 52.8258 0.716026 52.9018C3.93703 53.6188 4.65403 54.3358 5.34203 57.5568C5.41703 57.9038 5.34203 58.2738 5.70103 58.2738C6.06003 58.2738 5.97903 57.9038 6.06003 57.5568C6.77703 54.3358 7.48803 53.6188 10.686 52.9018C11.039 52.8268 11.403 52.9018 11.403 52.5428C11.403 52.1838 11.039 52.2648 10.686 52.1838Z" fill="white"/>
<path opacity="0.739" d="M34.19 12.595C31.434 11.981 30.825 11.37 30.209 8.612C30.142 8.312 30.209 8 29.902 8C29.595 8 29.662 8.313 29.595 8.612C28.983 11.37 28.37 11.977 25.614 12.595C25.314 12.66 25 12.595 25 12.902C25 13.209 25.315 13.142 25.614 13.202C28.37 13.816 28.979 14.427 29.595 17.185C29.662 17.485 29.595 17.797 29.902 17.797C30.209 17.797 30.142 17.484 30.209 17.185C30.821 14.427 31.434 13.82 34.19 13.202C34.49 13.137 34.804 13.202 34.804 12.902C34.804 12.602 34.49 12.66 34.19 12.595Z" fill="white"/>
<path opacity="0.7" d="M38.666 55.832C36.366 55.32 35.859 54.81 35.345 52.51C35.289 52.261 35.345 52 35.089 52C34.833 52 34.889 52.261 34.833 52.51C34.323 54.81 33.811 55.317 31.512 55.832C31.263 55.886 31 55.832 31 56.088C31 56.344 31.263 56.288 31.512 56.342C33.812 56.854 34.319 57.364 34.833 59.664C34.889 59.913 34.833 60.174 35.089 60.174C35.345 60.174 35.289 59.913 35.345 59.664C35.855 57.364 36.367 56.857 38.666 56.342C38.917 56.287 39.178 56.342 39.178 56.088C39.178 55.834 38.917 55.886 38.666 55.832Z" fill="white"/>
<path opacity="0.855" d="M19.336 2.668C17.736 2.312 17.382 1.95598 17.024 0.35498C16.985 0.18098 17.024 0 16.846 0C16.668 0 16.707 0.18198 16.668 0.35498C16.313 1.95498 15.956 2.309 14.356 2.668C14.182 2.706 14 2.66798 14 2.84598C14 3.02398 14.183 2.98498 14.356 3.02298C15.956 3.37898 16.31 3.735 16.668 5.336C16.707 5.51 16.668 5.69098 16.846 5.69098C17.024 5.69098 16.985 5.509 17.024 5.336C17.379 3.736 17.736 3.38198 19.336 3.02298C19.511 2.98498 19.692 3.02298 19.692 2.84598C19.692 2.66898 19.511 2.706 19.336 2.668Z" fill="white"/>
<path opacity="0.855" d="M11.336 25.668C9.736 25.312 9.382 24.956 9.024 23.355C8.985 23.181 9.024 23 8.846 23C8.668 23 8.707 23.182 8.668 23.355C8.313 24.955 7.956 25.309 6.356 25.668C6.182 25.706 6 25.668 6 25.846C6 26.024 6.183 25.985 6.356 26.023C7.956 26.379 8.31 26.735 8.668 28.336C8.707 28.51 8.668 28.691 8.846 28.691C9.024 28.691 8.985 28.509 9.024 28.336C9.379 26.736 9.736 26.382 11.336 26.023C11.511 25.985 11.692 26.023 11.692 25.846C11.692 25.669 11.511 25.706 11.336 25.668Z" fill="white"/>
<path opacity="0.5" d="M44.336 28.668C42.736 28.312 42.382 27.956 42.024 26.355C41.985 26.181 42.024 26 41.846 26C41.668 26 41.707 26.182 41.668 26.355C41.313 27.955 40.956 28.309 39.356 28.668C39.182 28.706 39 28.668 39 28.846C39 29.024 39.183 28.985 39.356 29.023C40.956 29.379 41.31 29.735 41.668 31.336C41.707 31.51 41.668 31.691 41.846 31.691C42.024 31.691 41.985 31.509 42.024 31.336C42.379 29.736 42.736 29.382 44.336 29.023C44.511 28.985 44.692 29.023 44.692 28.846C44.692 28.669 44.511 28.706 44.336 28.668Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -20,7 +20,6 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import MaintenanceBanner from './maintenance/MaintenanceBanner';
import { styled } from '@mui/material';
import { InitialRedirect } from './InitialRedirect';
import { Demo } from './demo/Demo';
const StyledContainer = styled('div')(() => ({
'& ul': {
@ -101,13 +100,6 @@ export const App = () => {
<FeedbackNPS openUrl="http://feedback.unleash.run" />
<ConditionallyRender
condition={Boolean(
uiConfig.flags.demo
)}
show={<Demo />}
/>
<SplashPageRedirect />
</StyledContainer>
</>

View File

@ -5,6 +5,9 @@ import { createLocalStorage } from 'utils/createLocalStorage';
import { TOPICS } from './demo-topics';
import { DemoDialogWelcome } from './DemoDialog/DemoDialogWelcome/DemoDialogWelcome';
import { DemoDialogFinish } from './DemoDialog/DemoDialogFinish/DemoDialogFinish';
import { DemoDialogPlans } from './DemoDialog/DemoDialogPlans/DemoDialogPlans';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { DemoBanner } from './DemoBanner/DemoBanner';
const defaultProgress = {
welcomeOpen: true,
@ -13,14 +16,21 @@ const defaultProgress = {
steps: [0],
};
const { value: storedProgress, setValue: setStoredProgress } =
createLocalStorage('Tutorial:v1', defaultProgress);
interface IDemoProps {
children: JSX.Element;
}
export const Demo = ({ children }: IDemoProps): JSX.Element => {
const { uiConfig } = useUiConfig();
const { value: storedProgress, setValue: setStoredProgress } =
createLocalStorage('Tutorial:v1', defaultProgress);
export const Demo = () => {
const [welcomeOpen, setWelcomeOpen] = useState(
storedProgress.welcomeOpen ?? defaultProgress.welcomeOpen
);
const [finishOpen, setFinishOpen] = useState(false);
const [plansOpen, setPlansOpen] = useState(false);
const [expanded, setExpanded] = useState(
storedProgress.expanded ?? defaultProgress.expanded
@ -59,8 +69,16 @@ export const Demo = () => {
}
};
if (!uiConfig.flags.demo) return children;
return (
<>
<DemoBanner
onPlans={() => {
setPlansOpen(true);
}}
/>
{children}
<DemoDialogWelcome
open={welcomeOpen}
onClose={() => {
@ -76,12 +94,17 @@ export const Demo = () => {
open={finishOpen}
onClose={() => {
setFinishOpen(false);
setPlansOpen(true);
}}
onRestart={() => {
setFinishOpen(false);
onStart();
}}
/>
<DemoDialogPlans
open={plansOpen}
onClose={() => setPlansOpen(false)}
/>
<DemoTopics
expanded={expanded}
setExpanded={setExpanded}

View File

@ -0,0 +1,51 @@
import { Button, styled } from '@mui/material';
const StyledBanner = styled('div')(({ theme }) => ({
position: 'sticky',
top: 0,
zIndex: theme.zIndex.appBar,
display: 'flex',
gap: theme.spacing(1),
justifyContent: 'center',
alignItems: 'center',
backgroundColor: theme.palette.web.main,
color: theme.palette.web.contrastText,
padding: theme.spacing(1),
}));
const StyledButton = styled(Button)(({ theme }) => ({
whiteSpace: 'nowrap',
flexShrink: 0,
'&&&': {
fontSize: theme.fontSizes.smallBody,
},
}));
const StyledQuestionsButton = styled(StyledButton)(({ theme }) => ({
color: theme.palette.web.contrastText,
border: `1px solid rgba(255, 255, 255, 0.5)`,
})) as typeof Button;
interface IDemoBannerProps {
onPlans: () => void;
}
export const DemoBanner = ({ onPlans }: IDemoBannerProps) => (
<StyledBanner>
<span>
This is a <strong>demo of Unleash</strong>. Play around as much as
you want. Reach out when you're ready.
</span>
<StyledQuestionsButton
variant="outlined"
sx={{ ml: 1 }}
href="https://slack.unleash.run/"
target="_blank"
>
Ask questions
</StyledQuestionsButton>
<StyledButton variant="contained" color="primary" onClick={onPlans}>
Get Unleash
</StyledButton>
</StyledBanner>
);

View File

@ -1,4 +1,10 @@
import { Dialog, IconButton, Typography, styled } from '@mui/material';
import {
Dialog,
DialogProps,
IconButton,
Typography,
styled,
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
const StyledDialog = styled(Dialog)(({ theme }) => ({
@ -22,14 +28,19 @@ const StyledHeader = styled(Typography)(({ theme }) => ({
fontWeight: theme.fontWeight.bold,
}));
interface IDemoDialogProps {
interface IDemoDialogProps extends DialogProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
}
export const DemoDialog = ({ open, onClose, children }: IDemoDialogProps) => (
<StyledDialog open={open} onClose={onClose}>
export const DemoDialog = ({
open,
onClose,
children,
...props
}: IDemoDialogProps) => (
<StyledDialog open={open} onClose={onClose} {...props}>
<StyledCloseButton aria-label="close" onClick={onClose}>
<CloseIcon />
</StyledCloseButton>

View File

@ -39,11 +39,11 @@ export const DemoDialogFinish = ({
}
/>
<DemoDialog open={open} onClose={onClose}>
<DemoDialog.Header>You finished the tutorial</DemoDialog.Header>
<DemoDialog.Header>You finished the demo</DemoDialog.Header>
<Typography color="textSecondary" sx={{ mt: 4 }}>
Great job! Keep exploring Unleash, as this was just a small
example of its full potential. You can do the tutorial again at
any moment.
example of its full potential. You can do the demo again at any
moment.
</Typography>
<StyledActions>
<StyledButton
@ -51,14 +51,14 @@ export const DemoDialogFinish = ({
color="primary"
onClick={onRestart}
>
Restart tutorial
Restart demo
</StyledButton>
<StyledButton
variant="contained"
color="primary"
onClick={onClose}
>
Close
Continue
</StyledButton>
</StyledActions>
</DemoDialog>

View File

@ -0,0 +1,130 @@
import { Button, Typography, styled } from '@mui/material';
import { DemoDialog } from '../DemoDialog';
import { GitHub } from '@mui/icons-material';
import { Launch } from '@mui/icons-material';
const StyledDemoDialog = styled(DemoDialog)(({ theme }) => ({
'& .MuiDialog-paper': {
maxWidth: theme.spacing(120),
},
}));
const StyledPlans = styled('div')(({ theme }) => ({
display: 'grid',
gridTemplateColumns: 'auto auto auto',
gap: theme.spacing(1),
marginTop: theme.spacing(6),
}));
const StyledPlan = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
backgroundColor: theme.palette.background.elevation1,
borderRadius: theme.shape.borderRadiusLarge,
padding: theme.spacing(4, 3),
'& > a': {
whiteSpace: 'nowrap',
},
height: theme.spacing(34),
width: theme.spacing(34),
}));
const StyledCompareLink = styled('a')(({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
margin: 'auto',
marginTop: theme.spacing(4),
display: 'inline-flex',
alignItems: 'center',
gap: theme.spacing(1),
'& > svg': {
fontSize: theme.fontSizes.mainHeader,
},
}));
interface IDemoDialogPlansProps {
open: boolean;
onClose: () => void;
}
export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => (
<StyledDemoDialog open={open} onClose={onClose}>
<DemoDialog.Header>Want to keep going with Unleash?</DemoDialog.Header>
<StyledPlans>
<StyledPlan>
<Typography variant="h5" fontWeight="bold">
Open Source
</Typography>
<Typography variant="body2" color="textSecondary">
Self-hosted basic feature management solution
</Typography>
<Typography variant="h6" fontWeight="normal">
Free
</Typography>
<Button
variant="outlined"
color="primary"
startIcon={<GitHub />}
href="https://github.com/unleash/unleash"
target="_blank"
>
View project on GitHub
</Button>
</StyledPlan>
<StyledPlan>
<Typography variant="h5" fontWeight="bold">
Pro
</Typography>
<Typography variant="body2" color="textSecondary">
Free your team to collaborate. We'll do the heavy lifting.
</Typography>
<div>
<Typography variant="h6" fontWeight="normal">
$80/month
</Typography>
<Typography variant="body2">includes 5 seats</Typography>
</div>
<Button
variant="contained"
color="primary"
href="https://www.getunleash.io/plans/pro"
target="_blank"
>
Start 14-day free trial
</Button>
</StyledPlan>
<StyledPlan>
<Typography variant="h5" fontWeight="bold">
Enterprise
</Typography>
<Typography variant="body2" color="textSecondary">
Security, compliance, and development controls for scale.
</Typography>
<div>
<Typography variant="h6" fontWeight="normal">
Custom
</Typography>
<Typography variant="body2">unlimited seats</Typography>
</div>
<Button
variant="contained"
color="web"
href="https://www.getunleash.io/plans/enterprise"
target="_blank"
>
Contact sales
</Button>
</StyledPlan>
</StyledPlans>
<StyledCompareLink
href="https://www.getunleash.io/plans"
target="_blank"
>
Compare plans <Launch />
</StyledCompareLink>
</StyledDemoDialog>
);

View File

@ -60,8 +60,8 @@ export const DemoDialogWelcome = ({
<DemoDialog.Header>Explore Unleash</DemoDialog.Header>
<Typography color="textSecondary" sx={{ mt: 2 }}>
You can explore Unleash on your own, however for the best experience
it's recommended you follow our interactive tutorial. To get
started, you will need to open the demo website below.
it's recommended you follow our interactive demo. To get started,
you will need to open the demo website below.
</Typography>
<StyledDemoPane>
<StyledScanMessage>
@ -87,7 +87,7 @@ export const DemoDialogWelcome = ({
color="primary"
onClick={onStart}
>
Start Unleash tutorial
Try Unleash demo
</StyledStartButton>
</DemoDialog>
);

View File

@ -0,0 +1,203 @@
import {
Button,
Dialog,
IconButton,
Typography,
alpha,
styled,
} from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ITutorialTopic, ITutorialTopicStep } from 'component/demo/demo-topics';
import { TooltipRenderProps } from 'react-joyride';
import CloseIcon from '@mui/icons-material/Close';
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: theme.shape.borderRadiusMedium,
width: '100%',
maxWidth: theme.spacing(45),
padding: theme.spacing(3),
},
}));
const StyledTooltip = styled('div')(({ theme }) => ({
'@keyframes pulse': {
'0%': {
boxShadow: `0 0 0 0 ${alpha(theme.palette.primary.main, 0.7)}`,
},
'70%': {
boxShadow: `0 0 0 10px ${alpha(theme.palette.primary.main, 0)}`,
},
'100%': {
boxShadow: `0 0 0 0 ${alpha(theme.palette.primary.main, 0)}`,
},
},
position: 'relative',
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
borderRadius: theme.shape.borderRadiusMedium,
width: '100%',
maxWidth: theme.spacing(45),
padding: theme.spacing(3),
}));
const StyledCloseButton = styled(IconButton)(({ theme }) => ({
position: 'absolute',
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.neutral.main,
}));
const StyledTooltipTitle = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
marginBottom: theme.spacing(1),
flexWrap: 'wrap',
paddingRight: theme.spacing(4),
}));
const StyledTooltipActions = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginTop: theme.spacing(3),
'&&& button': {
fontSize: theme.fontSizes.smallBody,
},
}));
export interface IDemoStepTooltipProps extends TooltipRenderProps {
step: ITutorialTopicStep;
topic: number;
topics: ITutorialTopic[];
steps: number[];
onClose: () => void;
onBack: (step: ITutorialTopicStep) => void;
onNext: (step: number) => void;
}
export const DemoStepTooltip = ({
tooltipProps,
step,
topic,
topics,
steps,
onClose,
onBack,
onNext,
}: IDemoStepTooltipProps) => {
if (step.target === 'body') {
return (
<div {...tooltipProps}>
<StyledDialog
open
onClose={(_, r) => {
if (r !== 'backdropClick') onClose();
}}
transitionDuration={0}
>
<StyledCloseButton aria-label="close" onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
<StyledTooltipTitle>
<ConditionallyRender
condition={Boolean(step.title)}
show={step.title}
elseShow={
<Typography fontWeight="bold">
{topics[topic].title}
</Typography>
}
/>
</StyledTooltipTitle>
{step.content}
<StyledTooltipActions>
<div>
<ConditionallyRender
condition={topic > 0 || steps[topic] > 0}
show={
<Button
variant="outlined"
onClick={() => onBack(step)}
>
Back
</Button>
}
/>
</div>
<div>
<ConditionallyRender
condition={Boolean(step.nextButton)}
show={
<Button
onClick={() => onNext(steps[topic])}
variant="contained"
sx={{ alignSelf: 'flex-end' }}
>
{topic === topics.length - 1 &&
steps[topic] ===
topics[topic].steps.length - 1
? 'Finish'
: 'Next'}
</Button>
}
/>
</div>
</StyledTooltipActions>
</StyledDialog>
</div>
);
}
return (
<StyledTooltip {...tooltipProps}>
<StyledCloseButton aria-label="close" onClick={onClose}>
<CloseIcon />
</StyledCloseButton>
<StyledTooltipTitle>
<ConditionallyRender
condition={Boolean(step.title)}
show={step.title}
elseShow={
<Typography fontWeight="bold">
{topics[topic].title}
</Typography>
}
/>
</StyledTooltipTitle>
{step.content}
<StyledTooltipActions>
<div>
<ConditionallyRender
condition={topic > 0 || steps[topic] > 0}
show={
<Button
variant="outlined"
onClick={() => onBack(step)}
>
Back
</Button>
}
/>
</div>
<div>
<ConditionallyRender
condition={Boolean(step.nextButton)}
show={
<Button
onClick={() => onNext(steps[topic])}
variant="contained"
sx={{ alignSelf: 'flex-end' }}
>
{topic === topics.length - 1 &&
steps[topic] === topics[topic].steps.length - 1
? 'Finish'
: 'Next'}
</Button>
}
/>
</div>
</StyledTooltipActions>
</StyledTooltip>
);
};

View File

@ -3,45 +3,11 @@ import Joyride, {
CallBackProps,
TooltipRenderProps,
} from 'react-joyride';
import { Button, Typography, styled, useTheme } from '@mui/material';
import { useTheme } from '@mui/material';
import { ITutorialTopic, ITutorialTopicStep } from '../demo-topics';
import { useEffect, useState } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useLocation, useNavigate } from 'react-router-dom';
const StyledTooltip = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
borderRadius: theme.shape.borderRadiusMedium,
width: '100%',
maxWidth: theme.spacing(45),
padding: theme.spacing(3),
}));
const StyledTooltipTitle = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
marginBottom: theme.spacing(1),
flexWrap: 'wrap',
}));
const StyledTooltipActions = styled('div')(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginTop: theme.spacing(3),
'&&& button': {
'&:first-of-type': {
marginLeft: theme.spacing(-2),
},
fontSize: theme.fontSizes.smallBody,
},
}));
const StyledTooltipPrimaryActions = styled('div')(({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
}));
import { DemoStepTooltip } from './DemoStepTooltip/DemoStepTooltip';
interface IDemoStepsProps {
setExpanded: React.Dispatch<React.SetStateAction<boolean>>;
@ -82,10 +48,9 @@ export const DemoSteps = ({
}
};
const skip = () => {
const close = () => {
abortController.abort();
setTopicStep(-1);
setExpanded(false);
};
const back = () => {
@ -124,6 +89,10 @@ export const DemoSteps = ({
) => {
const { action, index, step } = data;
if (action === ACTIONS.CLOSE) {
close();
}
if (action === ACTIONS.UPDATE) {
const el = document.querySelector(step.target as string);
if (el) {
@ -237,78 +206,27 @@ export const DemoSteps = ({
border: `2px solid ${theme.palette.primary.main}`,
outline: `2px solid ${theme.palette.secondary.border}`,
backgroundColor: 'transparent',
animation: 'pulse 2s infinite',
},
overlay: {
backgroundColor: 'transparent',
mixBlendMode: 'unset',
},
}}
tooltipComponent={({
step,
tooltipProps,
}: TooltipRenderProps & {
step: ITutorialTopicStep;
}) => (
<StyledTooltip {...tooltipProps}>
<StyledTooltipTitle>
<ConditionallyRender
condition={Boolean(step.title)}
show={step.title}
elseShow={
<Typography fontWeight="bold">
{topics[topic].title}
</Typography>
}
/>
<ConditionallyRender
condition={topics[topic].steps.length > 1}
show={
<Typography
variant="body2"
color="text.secondary"
flexShrink={0}
>
(step {steps[topic] + 1} of{' '}
{topics[topic].steps.length})
</Typography>
}
/>
</StyledTooltipTitle>
{step.content}
<StyledTooltipActions>
<Button variant="text" onClick={skip}>
Skip
</Button>
<StyledTooltipPrimaryActions>
<ConditionallyRender
condition={topic > 0 || steps[topic] > 0}
show={
<Button
variant="outlined"
onClick={() => onBack(step)}
>
Back
</Button>
}
/>
<ConditionallyRender
condition={Boolean(step.nextButton)}
show={
<Button
onClick={() => next(steps[topic])}
variant="contained"
>
{topic === topics.length - 1 &&
steps[topic] ===
topics[topic].steps.length - 1
? 'Finish'
: 'Next'}
</Button>
}
/>
</StyledTooltipPrimaryActions>
</StyledTooltipActions>
</StyledTooltip>
tooltipComponent={(
props: TooltipRenderProps & {
step: ITutorialTopicStep;
}
) => (
<DemoStepTooltip
{...props}
topic={topic}
topics={topics}
steps={steps}
onClose={close}
onBack={onBack}
onNext={next}
/>
)}
/>
);

View File

@ -11,6 +11,7 @@ import {
import { CheckCircle, CircleOutlined, ExpandMore } from '@mui/icons-material';
import { ITutorialTopic } from '../demo-topics';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ReactComponent as StarsIcon } from 'assets/img/stars.svg';
const StyledAccordion = styled(Accordion)(({ theme }) => ({
position: 'fixed',
@ -47,8 +48,15 @@ const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
color: theme.palette.primary.contrastText,
borderTopLeftRadius: theme.shape.borderRadiusLarge,
borderTopRightRadius: theme.shape.borderRadiusLarge,
height: 91,
}));
const StyledStars = styled(StarsIcon)({
position: 'absolute',
left: 6,
top: -24,
});
const StyledExpandMoreIcon = styled(ExpandMore)(({ theme }) => ({
color: theme.palette.primary.contrastText,
}));
@ -99,7 +107,7 @@ const StyledStep = styled('li', {
...(selected && {
backgroundColor: theme.palette.secondary.light,
fontWeight: theme.typography.fontWeightBold,
border: `1px solid ${theme.palette.primary.main}`,
outline: `1px solid ${theme.palette.primary.main}`,
}),
...(completed && {
backgroundColor: theme.palette.background.elevation1,
@ -160,12 +168,13 @@ export const DemoTopics = ({
onChange={() => setExpanded(expanded => !expanded)}
>
<StyledAccordionSummary>
<StyledStars />
<StyledTitle>
<Typography fontWeight="bold">Unleash tutorial</Typography>
<Typography fontWeight="bold">Unleash demo</Typography>
<StyledExpandMoreIcon className="expand-icon" />
</StyledTitle>
<StyledSubtitle>
Complete all steps to finish tutorial
Complete all steps to finish demo
</StyledSubtitle>
<StyledProgress>
<Typography variant="body2">
@ -204,7 +213,7 @@ export const DemoTopics = ({
);
})}
<StyledButton variant="outlined" onClick={onWelcome}>
View demo link again
View demo page
</StyledButton>
</AccordionDetails>
</StyledAccordion>

View File

@ -1,25 +1,25 @@
import { IFeatureToggle } from 'interfaces/featureToggle';
import { formatApiPath } from 'utils/formatPath';
const PROJECT = 'demo-app';
const ENVIRONMENT = 'dev';
export const gradualRollout = async () => {
const projectId = 'default';
const featureId = 'demoApp.step3';
const environmentId = 'default';
const { environments }: IFeatureToggle = await fetch(
formatApiPath(
`api/admin/projects/${projectId}/features/${featureId}?variantEnvironments=true`
`api/admin/projects/${PROJECT}/features/${featureId}?variantEnvironments=true`
)
).then(res => res.json());
const strategies =
environments.find(({ name }) => name === environmentId)?.strategies ||
[];
environments.find(({ name }) => name === ENVIRONMENT)?.strategies || [];
if (!strategies.find(({ name }) => name === 'flexibleRollout')) {
await fetch(
formatApiPath(
`api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/strategies`
`api/admin/projects/${PROJECT}/features/${featureId}/environments/${ENVIRONMENT}/strategies`
),
{
method: 'POST',
@ -41,20 +41,18 @@ export const gradualRollout = async () => {
};
export const variants = async () => {
const projectId = 'default';
const featureId = 'demoApp.step4';
const environmentId = 'default';
const { variants }: IFeatureToggle = await fetch(
formatApiPath(
`api/admin/projects/${projectId}/features/${featureId}?variantEnvironments=true`
`api/admin/projects/${PROJECT}/features/${featureId}?variantEnvironments=true`
)
).then(res => res.json());
if (!variants.length) {
await fetch(
formatApiPath(
`api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/variants`
`api/admin/projects/${PROJECT}/features/${featureId}/environments/${ENVIRONMENT}/variants`
),
{
method: 'PATCH',

View File

@ -3,6 +3,7 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { Badge } from 'component/common/Badge/Badge';
import { Step } from 'react-joyride';
import { gradualRollout, variants } from './demo-setup';
import { basePath } from 'utils/formatPath';
export interface ITutorialTopicStep extends Step {
href?: string;
@ -24,12 +25,15 @@ const Description = (props: TypographyProps) => (
<Typography variant="body2" color="text.secondary" {...props} />
);
const PROJECT = 'demo-app';
const ENVIRONMENT = 'dev';
export const TOPICS: ITutorialTopic[] = [
{
title: 'Enable/disable a feature toggle',
steps: [
{
href: '/projects/default',
href: `/projects/${PROJECT}`,
target: 'body',
placement: 'center',
content: (
@ -58,8 +62,8 @@ export const TOPICS: ITutorialTopic[] = [
nextButton: true,
},
{
href: '/projects/default',
target: 'div[data-testid="TOGGLE-demoApp.step1-default"]',
href: `/projects/${PROJECT}`,
target: `div[data-testid="TOGGLE-demoApp.step1-${ENVIRONMENT}"]`,
content: (
<>
<Description>
@ -82,7 +86,7 @@ export const TOPICS: ITutorialTopic[] = [
title: 'Enable for a specific user',
steps: [
{
href: '/projects/default',
href: `/projects/${PROJECT}`,
target: 'body',
placement: 'center',
content: (
@ -106,19 +110,19 @@ export const TOPICS: ITutorialTopic[] = [
nextButton: true,
},
{
href: '/projects/default',
target: 'a[href="/projects/default/features/demoApp.step2"]',
href: `/projects/${PROJECT}`,
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step2"]`,
content: (
<Description>
First, let's open the feature toggle configuration for{' '}
<Badge as="span">demoApp.step2</Badge>.
<Badge as="span">demoApp.step2</Badge>
</Description>
),
preventDefault: true,
},
{
href: '/projects/default/features/demoApp.step2',
target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"] button',
href: `/projects/${PROJECT}/features/demoApp.step2`,
target: `div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_${ENVIRONMENT}"] button`,
content: (
<Description>
Add a new strategy to this environment by clicking this
@ -127,7 +131,7 @@ export const TOPICS: ITutorialTopic[] = [
),
},
{
target: 'a[href="/projects/default/features/demoApp.step2/strategies/create?environmentId=default&strategyName=default"]',
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step2/strategies/create?environmentId=${ENVIRONMENT}&strategyName=default"]`,
content: (
<Description>
Select the <Badge as="span">Standard</Badge> strategy
@ -197,7 +201,7 @@ export const TOPICS: ITutorialTopic[] = [
content: (
<>
<Description>
Enter your <Badge as="span">userId</Badge>.
Enter your <Badge as="span">userId</Badge>
</Description>
<Badge
sx={{ marginTop: 2 }}
@ -207,7 +211,6 @@ export const TOPICS: ITutorialTopic[] = [
</Badge>
</>
),
backCloseModal: true,
nextButton: true,
},
{
@ -217,7 +220,6 @@ export const TOPICS: ITutorialTopic[] = [
{
target: 'button[data-testid="CONSTRAINT_SAVE_BUTTON"]',
content: <Description>Save the constraint.</Description>,
backCloseModal: true,
},
{
target: 'button[data-testid="STRATEGY_FORM_SUBMIT_ID"]',
@ -259,7 +261,7 @@ export const TOPICS: ITutorialTopic[] = [
setup: gradualRollout,
steps: [
{
href: '/projects/default',
href: `/projects/${PROJECT}`,
target: 'body',
placement: 'center',
content: (
@ -289,19 +291,19 @@ export const TOPICS: ITutorialTopic[] = [
nextButton: true,
},
{
href: '/projects/default',
target: 'a[href="/projects/default/features/demoApp.step3"]',
href: `/projects/${PROJECT}`,
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step3"]`,
content: (
<Description>
First, let's open the feature toggle configuration for{' '}
<Badge as="span">demoApp.step3</Badge>.
<Badge as="span">demoApp.step3</Badge>
</Description>
),
preventDefault: true,
},
{
href: '/projects/default/features/demoApp.step3',
target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"] .MuiAccordionSummary-expandIconWrapper',
href: `/projects/${PROJECT}/features/demoApp.step3`,
target: `div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_${ENVIRONMENT}"] .MuiAccordionSummary-expandIconWrapper`,
content: (
<Description>
Expand the environment card to see all the defined
@ -310,7 +312,7 @@ export const TOPICS: ITutorialTopic[] = [
),
},
{
target: 'div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_default"].Mui-expanded a[data-testid="STRATEGY_EDIT-flexibleRollout"]',
target: `div[data-testid="FEATURE_ENVIRONMENT_ACCORDION_${ENVIRONMENT}"].Mui-expanded a[data-testid="STRATEGY_EDIT-flexibleRollout"]`,
content: (
<Description>
Edit the existing gradual rollout strategy.
@ -365,7 +367,7 @@ export const TOPICS: ITutorialTopic[] = [
setup: variants,
steps: [
{
href: '/projects/default',
href: `/projects/${PROJECT}`,
target: 'body',
placement: 'center',
content: (
@ -390,18 +392,18 @@ export const TOPICS: ITutorialTopic[] = [
nextButton: true,
},
{
href: '/projects/default',
target: 'a[href="/projects/default/features/demoApp.step4"]',
href: `/projects/${PROJECT}`,
target: `a[href="${basePath}/projects/${PROJECT}/features/demoApp.step4"]`,
content: (
<Description>
First, let's open the feature toggle configuration for{' '}
<Badge as="span">demoApp.step4</Badge>.
<Badge as="span">demoApp.step4</Badge>
</Description>
),
preventDefault: true,
},
{
href: '/projects/default/features/demoApp.step4',
href: `/projects/${PROJECT}/features/demoApp.step4`,
target: 'button[data-testid="TAB-Variants"]',
content: <Description>Select the variants tab.</Description>,
},
@ -432,7 +434,7 @@ export const TOPICS: ITutorialTopic[] = [
.
</Description>
<Description>
Example: <Badge as="span">aqua</Badge>.
Example: <Badge as="span">aqua</Badge>
</Description>
</>
),
@ -485,7 +487,7 @@ export const TOPICS: ITutorialTopic[] = [
content: (
<>
<Description>
Enter your <Badge as="span">userId</Badge>.
Enter your <Badge as="span">userId</Badge>
</Description>
<Badge
sx={{ marginTop: 2 }}

View File

@ -14,6 +14,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { DraftBanner } from './DraftBanner/DraftBanner';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
import { Demo } from 'component/demo/Demo';
interface IMainLayoutProps {
children: ReactNode;
@ -84,41 +85,50 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
return (
<>
<SkipNavLink />
<Header />
<SkipNavTarget />
<MainLayoutContainer>
<MainLayoutContentWrapper>
<ConditionallyRender
condition={Boolean(
projectId && isChangeRequestConfiguredInAnyEnv()
)}
show={<DraftBanner project={projectId || ''} />}
/>
<MainLayoutContent item xs={12} sm={12} my={2}>
<MainLayoutContentContainer ref={ref}>
<BreadcrumbNav />
<Proclamation toast={uiConfig.toast} />
{children}
</MainLayoutContentContainer>
</MainLayoutContent>
<ThemeMode
darkmode={
<StyledImg
style={{ opacity: 0.06 }}
src={formatAssetPath(textureImage)}
alt=""
<Demo>
<>
<Header />
<SkipNavTarget />
<MainLayoutContainer>
<MainLayoutContentWrapper>
<ConditionallyRender
condition={Boolean(
projectId &&
isChangeRequestConfiguredInAnyEnv()
)}
show={
<DraftBanner
project={projectId || ''}
/>
}
/>
}
lightmode={
<StyledImg
src={formatAssetPath(textureImage)}
alt=""
<MainLayoutContent item xs={12} sm={12} my={2}>
<MainLayoutContentContainer ref={ref}>
<BreadcrumbNav />
<Proclamation toast={uiConfig.toast} />
{children}
</MainLayoutContentContainer>
</MainLayoutContent>
<ThemeMode
darkmode={
<StyledImg
style={{ opacity: 0.06 }}
src={formatAssetPath(textureImage)}
alt=""
/>
}
lightmode={
<StyledImg
src={formatAssetPath(textureImage)}
alt=""
/>
}
/>
}
/>
</MainLayoutContentWrapper>
<Footer />
</MainLayoutContainer>
</MainLayoutContentWrapper>
<Footer />
</MainLayoutContainer>
</>
</Demo>
</>
);
}

View File

@ -131,6 +131,10 @@ const theme = {
border: '#8A3E45',
contrastText: '#EEEEFC', // Color used for content when error.main is used as a background
},
web: {
main: '#1A4049', // used on sales-related elements
contrastText: '#EEEEFC', // Color used for inner text
},
/**
* Used for grey badges, hover elements, and grey light elements

View File

@ -118,6 +118,10 @@ const theme = {
border: colors.red[300],
contrastText: colors.red[800], // Color used for text inside alert
},
web: {
main: '#1A4049', // used on sales-related elements
contrastText: colors.grey[50], // Color used for inner text
},
/**
* Used for grey badges, hover elements, and grey light elements

View File

@ -38,6 +38,11 @@ declare module '@mui/material/styles' {
*/
neutral: PaletteColorOptions;
/**
* Sales-related palette color
*/
web: PaletteColorOptions;
/**
* Table colors
*/
@ -148,5 +153,10 @@ declare module '@mui/material/styles/zIndex' {
sticky: number;
}
}
declare module '@mui/material' {
interface ButtonPropsColorOverrides {
web: true;
}
}
export {};