mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 00:39:49 +00:00
(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:
parent
1b276b6dc7
commit
ccd92c08ad
@ -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;
|
@ -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;
|
@ -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>
|
||||
);
|
||||
|
162
packages/core/admin/admin/src/components/GuidedTour/layout.js
Normal file
162
packages/core/admin/admin/src/components/GuidedTour/layout.js
Normal 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;
|
@ -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) =>
|
||||
|
@ -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}",
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user