(Feature-GuidedTour)/ State and content structure (#12054)

* feat: added guided tour context, hook and provider to helper-plugin

* feat(guided-tour): using helpers in the admin + init reducer

Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com>

* feedback fixes

* created data structure for guided tour state and content

Co-authored-by: Vincent <vincentbpro@users.noreply.github.com>

* removed closed leaf key from initialState

* init guided tour home component

* feedback fix fontWeight prop

Co-authored-by: vincentbpro <89356961+vincentbpro@users.noreply.github.com>
Co-authored-by: ronronscelestes <ronronscelestes@users.noreply.github.com>
Co-authored-by: Vincent <vincentbpro@users.noreply.github.com>
This commit is contained in:
ronronscelestes 2022-01-03 16:21:51 +01:00 committed by GitHub
parent 1b276b6dc7
commit ccd92c08ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 279 additions and 12 deletions

View File

@ -0,0 +1,37 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { useGuidedTour } from '@strapi/helper-plugin';
import { Box } from '@strapi/design-system/Box';
import { Stack } from '@strapi/design-system/Stack';
import { Typography } from '@strapi/design-system/Typography';
import { LinkButton } from '@strapi/design-system/LinkButton';
import layout from '../layout';
const GuidedTourHomepage = () => {
const { formatMessage } = useIntl();
const { guidedTourState } = useGuidedTour();
const sections = Object.entries(layout).map(([key, val]) => ({ key, ...val.home }));
const enrichedSections = sections.map(section => ({
isDone: Object.entries(guidedTourState[section.key]).every(([, value]) => value),
...section,
}));
const activeSection = enrichedSections.find(section => !section.isDone).key;
return (
<Stack size={5}>
{enrichedSections.map(section => (
<Box hasRadius shadow="tableShadow" background="neutral0" padding={5} key={section.key}>
<Typography>{formatMessage(section.title)}</Typography>
{section.key === activeSection && (
<LinkButton to={section.cta.target}>{formatMessage(section.cta.title)}</LinkButton>
)}
</Box>
))}
</Stack>
);
};
export default GuidedTourHomepage;

View File

@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Stack } from '@strapi/design-system/Stack';
import { Typography } from '@strapi/design-system/Typography';
import { useIntl } from 'react-intl';
const Content = ({ id, defaultMessage }) => {
const { formatMessage } = useIntl();
return (
<Stack size={5}>
{formatMessage(
{ id, defaultMessage },
{
b: children => <Typography fontWeight="semiBold">{children}</Typography>,
p: children => <Typography>{children}</Typography>,
}
)}
</Stack>
);
};
Content.propTypes = {
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
};
export default Content;

View File

@ -1,18 +1,10 @@
/*
*
* GuidedTour
*
* this component connects the redux state language locale to the
* IntlProvider component and i18n messages (loaded from `app/translations`)
*/
import React, { useReducer } from 'react';
import PropTypes from 'prop-types';
import { GuidedTourProvider } from '@strapi/helper-plugin';
import reducer, { initialState } from './reducer';
const GuidedTour = ({ children }) => {
const [{ currentStep }, dispatch] = useReducer(reducer, initialState);
const [{ currentStep, guidedTourState }, dispatch] = useReducer(reducer, initialState);
const setStep = step => {
dispatch({
@ -22,7 +14,11 @@ const GuidedTour = ({ children }) => {
};
return (
<GuidedTourProvider currentStep={currentStep} setStep={setStep}>
<GuidedTourProvider
guidedTourState={guidedTourState}
currentStep={currentStep}
setStep={setStep}
>
{children}
</GuidedTourProvider>
);

View File

@ -0,0 +1,162 @@
// TODO: create the modal to consume create and success content
const layout = {
contentTypeBuilder: {
home: {
title: {
id: 'app.components.GuidedTour.home.CTB.title',
defaultMessage: 'Build the content structure',
},
cta: {
title: {
id: 'app.components.GuidedTour.home.CTB.cta.title',
defaultMessage: 'Go to the Content-type Builder',
},
type: 'REDIRECT',
target: '/plugins/content-type-builder',
},
},
create: {
title: {
id: 'app.components.GuidedTour.CTB.create.title',
defaultMessage: 'Create a first Collection-type',
},
content: {
id: 'app.components.GuidedTour.CTB.create.content',
defaultMessage:
'<p>Collection-types help you manage several entries, Single-types are suitable to manage only one entry.</p> <p>Ex: For a website, articles would be a Collection type and homepage would be a Single type.</p>',
},
cta: {
title: {
id: 'app.components.GuidedTour.CTB.create.cta.title',
defaultMessage: 'Build a Collection-type',
},
type: 'CLOSE',
},
},
success: {
title: {
id: 'app.components.GuidedTour.CTB.success.title',
defaultMessage: 'Step 1: Completed ✅',
},
content: {
id: 'app.components.GuidedTour.CTB.success.content',
defaultMessage: '<p>Good going!</p><b>What would you like to share with the world? ⚡️</b>',
},
cta: {
title: {
id: 'app.components.GuidedTour.CTB.success.cta.title',
defaultMessage: 'Create sample content',
},
type: 'REDIRECT',
target: '/content-manager',
},
},
},
contentManager: {
home: {
title: {
id: 'app.components.GuidedTour.home.CM.title',
defaultMessage: '⚡️ What would you like to share with the world?',
},
cta: {
title: {
id: 'app.components.GuidedTour.home.CM.cta.title',
defaultMessage: 'Create sample data',
},
type: 'REDIRECT',
target: '/content-manager',
},
},
create: {
title: {
id: 'app.components.GuidedTour.CM.create.title',
defaultMessage: 'Create sample content',
},
content: {
id: 'app.components.GuidedTour.CM.create.content',
defaultMessage:
'<p>Create and manage all the content here in the Content Manager.</p><p>Ex: Taking the blog website example further, one can write an article, save and publish it as they like.</p>',
},
cta: {
title: {
id: 'app.components.GuidedTour.CM.create.ctaTitle',
defaultMessage: 'Create sample content',
},
type: 'CLOSE',
},
},
success: {
title: {
id: 'app.components.GuidedTour.CM.success.title',
defaultMessage: 'Step 2: Completed ✅',
},
content: {
id: 'app.components.GuidedTour.CM.success.content',
defaultMessage: '<p>Awesome, one last step to go!</p><b>🚀 See content in action</b>',
},
cta: {
title: {
id: 'app.components.GuidedTour.CM.success.cta.title',
defaultMessage: 'Test the API',
},
type: 'REDIRECT',
target: '/settings/api-tokens',
},
},
},
apiTokens: {
home: {
title: {
id: 'app.components.GuidedTour.home.apiTokens.title',
defaultMessage: '🚀 See content in action',
},
cta: {
title: {
id: 'app.components.GuidedTour.home.apiTokens.cta.title',
defaultMessage: 'Test the API',
},
type: 'REDIRECT',
target: '/content-manager',
},
},
create: {
title: {
id: 'app.components.GuidedTour.apiTokens.create.title',
defaultMessage: 'See content in action',
},
content: {
id: 'app.components.GuidedTour.apiTokens.create.content',
defaultMessage:
'Generate an authentication token here and retrieve the content you just created',
},
cta: {
title: {
id: 'app.components.GuidedTour.apiTokens.create.cta.title',
defaultMessage: 'Create sample content',
},
type: 'CLOSE',
},
},
success: {
title: {
id: 'app.components.GuidedTour.apiTokens.success.title',
defaultMessage: 'Step 3: Completed ✅',
},
content: {
id: 'app.components.GuidedTour.apiTokens.success.content',
defaultMessage: 'You successfully finished the guided tour.',
},
cta: {
title: {
id: 'app.components.GuidedTour.apiTokens.success.cta.title',
defaultMessage: 'Go back to homepage',
},
type: 'REDIRECT',
target: '/',
},
},
},
};
export default layout;

View File

@ -3,6 +3,20 @@ import produce from 'immer';
export const initialState = {
currentStep: null,
guidedTourState: {
contentTypeBuilder: {
create: false,
success: false,
},
contentManager: {
create: false,
success: false,
},
apiTokens: {
create: false,
success: false,
},
},
};
const reducer = (state = initialState, action) =>

View File

@ -232,6 +232,30 @@
"Users.components.List.empty.withFilters": "There is no users with the applied filters...",
"Users.components.List.empty.withSearch": "There is no users corresponding to the search ({search})...",
"anErrorOccurred": "Woops! Something went wrong. Please, try again.",
"app.components.GuidedTour.home.CTB.title": "Build the content structure",
"app.components.GuidedTour.home.CTB.cta.title": "Go to the Content-type Builder",
"app.components.GuidedTour.CTB.create.title": "Create a first Collection-type",
"app.components.GuidedTour.CTB.create.content": "<p>Collection-types help you manage several entries, Single-types are suitable to manage only one entry.</p> <p>Ex: For a website, articles would be a Collection type and homepage would be a Single type.</p>",
"app.components.GuidedTour.CTB.create.cta.title": "Build a Collection-type",
"app.components.GuidedTour.CTB.success.title": "Step 1: Completed ✅",
"app.components.GuidedTour.CTB.success.content": "<p>Good going!</p><b>What would you like to share with the world? ⚡️</b>",
"app.components.GuidedTour.CTB.success.cta.title": "Create sample content",
"app.components.GuidedTour.home.CM.title": "⚡️ What would you like to share with the world?",
"app.components.GuidedTour.home.CM.cta.title": "Create sample data",
"app.components.GuidedTour.CM.create.title": "Create sample content",
"app.components.GuidedTour.CM.create.content": "<p>Create and manage all the content here in the Content Manager.</p><p>Ex: Taking the blog website example further, one can write an article, save and publish it as they like.</p>",
"app.components.GuidedTour.CM.create.ctaTitle": "Create sample content",
"app.components.GuidedTour.CM.success.title": "Step 2: Completed ✅",
"app.components.GuidedTour.CM.success.content": "<p>Awesome, one last step to go!</p><b>🚀 See content in action</b>",
"app.components.GuidedTour.CM.success.cta.title": "Test the API",
"app.components.GuidedTour.home.apiTokens.title": "🚀 See content in action",
"app.components.GuidedTour.home.apiTokens.cta.title": "Test the API",
"app.components.GuidedTour.apiTokens.create.title": "See content in action",
"app.components.GuidedTour.apiTokens.create.content": "Generate an authentication token here and retrieve the content you just created",
"app.components.GuidedTour.apiTokens.create.cta.title": "Create sample content",
"app.components.GuidedTour.apiTokens.success.title": "Step 3: Completed ✅",
"app.components.GuidedTour.apiTokens.success.content": "You successfully finished the guided tour.",
"app.components.GuidedTour.apiTokens.success.cta.title": "Go back to homepage",
"app.component.CopyToClipboard.label": "Copy to clipboard",
"app.component.search.label": "Search for {target}",
"app.component.table.delete": "Delete {target}",

View File

@ -7,9 +7,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import GuidedTourContext from '../../contexts/GuidedTourContext';
const GuidedTourProvider = ({ children, currentStep, setStep }) => {
const GuidedTourProvider = ({ children, currentStep, setStep, guidedTourState }) => {
return (
<GuidedTourContext.Provider value={{ currentStep, setStep }}>
<GuidedTourContext.Provider value={{ currentStep, guidedTourState, setStep }}>
{children}
</GuidedTourContext.Provider>
);
@ -22,6 +22,12 @@ GuidedTourProvider.defaultProps = {
GuidedTourProvider.propTypes = {
children: PropTypes.node.isRequired,
currentStep: PropTypes.string,
guidedTourState: PropTypes.objectOf(
PropTypes.shape({
create: PropTypes.bool,
success: PropTypes.bool,
})
).isRequired,
setStep: PropTypes.func.isRequired,
};