UNPKG

@selfcommunity/react-ui

Version:

React UI Components to integrate a Community created with SelfCommunity Platform.

332 lines (331 loc) • 23.5 kB
import { __awaiter, __rest } from "tslib"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useMemo, useState } from 'react'; import { Accordion, AccordionDetails, AccordionSummary, Box, Button, CardContent, CardMedia, Checkbox, Chip, Fade, Icon, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery, useTheme } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { styled } from '@mui/material/styles'; import classNames from 'classnames'; import { useThemeProps } from '@mui/system'; import Category from './Steps/Category'; import { PREFIX } from './constants'; import { getTheme, SCPreferences, usePreviousValue, UserUtils, useSCContext, useSCFetchCategories, useSCPreferences, useSCTheme, useSCUser } from '@selfcommunity/react-core'; import Appearance from './Steps/Appearance'; import Profile from './Steps/Profile'; import Invite from './Steps/Invite'; import App from './Steps/App'; import HiddenPlaceholder from '../../shared/HiddenPlaceholder'; import Widget from '../Widget'; import Content from './Steps/Content'; import { SCOPE_SC_UI } from '../../constants/Errors'; import { Endpoints, http, OnBoardingService, PreferenceService } from '@selfcommunity/api-services'; import { Logger } from '@selfcommunity/utils'; import { SCOnBoardingStepStatusType, SCOnBoardingStepType, SCOnBoardingStepIdType } from '@selfcommunity/types'; import OnBoardingWidgetSkeleton from './Skeleton'; import { closeSnackbar, useSnackbar } from 'notistack'; import HeaderPlaceholder from '../../assets/onBoarding/header'; import BaseDialog from '../../shared/BaseDialog'; import PubSub from 'pubsub-js'; import { SCCategoryEventType, SCTopicType } from '../../constants/PubSub'; import OnBoardingActionsButton from './ActionsButton'; const classes = { root: `${PREFIX}-root`, content: `${PREFIX}-content`, accordionRoot: `${PREFIX}-accordion-root`, logo: `${PREFIX}-logo`, intro: `${PREFIX}-intro`, steps: `${PREFIX}-steps`, stepsMobile: `${PREFIX}-steps-mobile`, stepContent: `${PREFIX}-step-content`, dialogRoot: `${PREFIX}-dialog-root`, dialogContent: `${PREFIX}-dialog-content` }; const Root = styled(Widget, { name: PREFIX, slot: 'Root', overridesResolver: (props, styles) => styles.root })(() => ({})); const AccordionRoot = styled(Accordion, { name: PREFIX, slot: 'AccordionRoot', overridesResolver: (props, styles) => styles.accordionRoot })(() => ({})); const DialogRoot = styled(BaseDialog, { name: PREFIX, slot: 'Root', overridesResolver: (props, styles) => styles.dialogRoot })(({ theme }) => ({})); const OnBoardingWidget = (inProps) => { // PROPS const props = useThemeProps({ props: inProps, name: PREFIX }); const { className, GenerateContentsParams = {}, onGeneratedContent = null, onHeightChange, onStateChange, forceExpanded = false, initialStep } = props, rest = __rest(props, ["className", "GenerateContentsParams", "onGeneratedContent", "onHeightChange", "onStateChange", "forceExpanded", "initialStep"]); // STATE const [loading, setLoading] = useState(true); const [initialized, setInitialized] = useState(false); const [steps, setSteps] = useState([]); const nextStep = useMemo(() => { const step = steps === null || steps === void 0 ? void 0 : steps.find((step) => (initialStep ? step.step === initialStep : step.status === 'in_progress' || step.status === 'not_started')); return step || (steps === null || steps === void 0 ? void 0 : steps[0]); }, [steps]); const allStepsDone = useMemo(() => { return steps === null || steps === void 0 ? void 0 : steps.every((step) => step.status === SCOnBoardingStepStatusType.COMPLETED); }, [steps]); const [expanded, setExpanded] = useState(!allStepsDone || forceExpanded); const [_step, setStep] = useState(nextStep); const currentContentsStep = steps === null || steps === void 0 ? void 0 : steps.find((s) => s.step === SCOnBoardingStepType.CONTENTS); const prevContentsStep = usePreviousValue(currentContentsStep); const currentCategoriesStep = steps === null || steps === void 0 ? void 0 : steps.find((s) => s.step === SCOnBoardingStepType.CATEGORIES); const prevCategoriesStep = usePreviousValue(currentCategoriesStep); const [showNoCategoriesModal, setShowNoCategoriesModal] = useState(false); const [showCategoriesWarningModal, setShowWarningCategoriesModal] = useState(false); const [isGenerating, setIsGenerating] = useState(false); // CONTEXT const scUserContext = useSCUser(); const isAdmin = useMemo(() => UserUtils.isCommunityCreator(scUserContext.user), [scUserContext.user]); const scContext = useSCContext(); const scPreferencesContext = useSCPreferences(); const scThemeContext = useSCTheme(); const { enqueueSnackbar } = useSnackbar(); const showOnBoarding = useMemo(() => scPreferencesContext.preferences && SCPreferences.CONFIGURATIONS_ONBOARDING_ENABLED in scPreferencesContext.preferences && SCPreferences.CONFIGURATIONS_ONBOARDING_HIDDEN in scPreferencesContext.preferences && scPreferencesContext.preferences[SCPreferences.CONFIGURATIONS_ONBOARDING_ENABLED].value && !scPreferencesContext.preferences[SCPreferences.CONFIGURATIONS_ONBOARDING_HIDDEN].value, [scPreferencesContext.preferences]); // HOOKS const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const { categories, isLoading } = useSCFetchCategories(); // HANDLERS /** * Notify changes to Feed if the Widget is contained */ const notifyLayoutChanges = useMemo(() => (state) => { if (onStateChange && state) { onStateChange(state); } onHeightChange && onHeightChange(); }, [onStateChange, onHeightChange]); const completeStep = (s) => __awaiter(void 0, void 0, void 0, function* () { if (s.status !== SCOnBoardingStepStatusType.COMPLETED) { yield OnBoardingService.completeAStep(s.id) .then(() => { setSteps((prev) => prev.map((item) => { if (item.id === s.id) { return Object.assign(Object.assign({}, item), { status: SCOnBoardingStepStatusType.COMPLETED, completion_percentage: 100 }); } return item; })); }) .catch((error) => { Logger.error(SCOPE_SC_UI, error); }); } s.step === SCOnBoardingStepType.APPEARANCE && handlePreferencesUpdate(); }); /** * Fetches platform url */ function fetchPlatform(query) { http .request({ url: Endpoints.Platform.url(), method: Endpoints.Platform.method, params: { next: query } }) .then((res) => { const platformUrl = res.data.platform_url; window.open(platformUrl, '_blank').focus(); }) .catch((error) => { console.log(error); }); } const showSuccessAlert = (step) => { setIsGenerating(false); enqueueSnackbar(_jsx(FormattedMessage, { id: `ui.onBoardingWidget.step.${step.step}.success`, defaultMessage: `ui.onBoardingWidget.step.${step.step}.success` }), { action: (snackbarId) => (_jsxs(_Fragment, { children: [step.step === SCOnBoardingStepType.CATEGORIES && (_jsx(Button, Object.assign({ sx: { textTransform: 'uppercase', color: 'white' }, size: "small", variant: "text", onClick: () => fetchPlatform('/contents/interests/') }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.step.categories.success.link", defaultMessage: "ui.onBoardingWidget.step.categories.success.link" }) }))), _jsx(IconButton, Object.assign({ sx: { color: 'white' }, onClick: () => closeSnackbar(snackbarId) }, { children: _jsx(Icon, { children: "close" }) }))] })), variant: 'success', autoHideDuration: 7000 }); }; const getSteps = () => __awaiter(void 0, void 0, void 0, function* () { yield OnBoardingService.getAllSteps() .then((res) => { const contentStep = res.results.find((step) => step.step === SCOnBoardingStepType.CONTENTS); setIsGenerating(res.results.some((step) => step.status === 'in_progress')); setSteps(res.results); setLoading(false); if (contentStep.status === SCOnBoardingStepStatusType.IN_PROGRESS && contentStep.results.length !== 0 && onGeneratedContent) { onGeneratedContent(contentStep.results); } }) .catch((error) => { Logger.error(SCOPE_SC_UI, error); setLoading(false); }); }); const handleChange = (newStep) => { setStep(newStep); }; const handleExpand = () => { const _expanded = !expanded; setExpanded(_expanded); notifyLayoutChanges({ forceExpanded: _expanded }); }; const generateContent = (stepId) => __awaiter(void 0, void 0, void 0, function* () { if (!isLoading && !categories.length) { setShowNoCategoriesModal(true); } else if (stepId === SCOnBoardingStepIdType.CATEGORIES) { setShowWarningCategoriesModal(true); } else { yield startStep(stepId); } }); const startStep = (stepId) => __awaiter(void 0, void 0, void 0, function* () { showCategoriesWarningModal && setShowWarningCategoriesModal(false); try { yield OnBoardingService.startAStep(stepId, GenerateContentsParams); setIsGenerating(true); } catch (error) { Logger.error(SCOPE_SC_UI, error); enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.common.error.action", defaultMessage: "ui.common.error.action" }), { variant: 'error', autoHideDuration: 3000 }); } }); const handlePreferencesUpdate = () => { PreferenceService.getAllPreferences().then((preferences) => { const prefs = preferences['results'].reduce((obj, p) => (Object.assign(Object.assign({}, obj), { [`${p.section}.${p.name}`]: p })), {}); scPreferencesContext.setPreferences(prefs); scThemeContext.setTheme(getTheme(scContext.settings.theme, prefs)); }); }; const handleCategoriesClick = () => { fetchPlatform('/contents/interests/'); setShowNoCategoriesModal(false); }; /** * Notify when a category info changes * @param data */ function notifyCategoryChanges(data) { data && PubSub.publish(`${SCTopicType.CATEGORY}.${SCCategoryEventType.EDIT}`, data); } // EFFECTS useEffect(() => { if (prevContentsStep && prevContentsStep.status === SCOnBoardingStepStatusType.IN_PROGRESS && (currentContentsStep === null || currentContentsStep === void 0 ? void 0 : currentContentsStep.status) === SCOnBoardingStepStatusType.COMPLETED) { showSuccessAlert(currentContentsStep); } }, [currentContentsStep, prevContentsStep]); useEffect(() => { if (prevCategoriesStep && prevCategoriesStep.status === SCOnBoardingStepStatusType.IN_PROGRESS && (currentCategoriesStep === null || currentCategoriesStep === void 0 ? void 0 : currentCategoriesStep.status) === SCOnBoardingStepStatusType.COMPLETED) { showSuccessAlert(currentCategoriesStep); } }, [currentCategoriesStep, prevCategoriesStep]); useEffect(() => { if (!initialized && nextStep) { setStep(nextStep); setInitialized(true); notifyLayoutChanges({ forceExpanded: expanded }); } }, [initialized, nextStep]); useEffect(() => { const _expanded = !allStepsDone; setExpanded(_expanded); notifyLayoutChanges({ forceExpanded: _expanded }); }, [allStepsDone]); useEffect(() => { if (isAdmin && showOnBoarding) { getSteps(); // eslint-disable-next-line @typescript-eslint/no-misused-promises const intervalId = setInterval(getSteps, isGenerating ? 6000 : 3 * 60 * 1000); return () => clearInterval(intervalId); } }, [scUserContext === null || scUserContext === void 0 ? void 0 : scUserContext.user, isGenerating, isAdmin, showOnBoarding]); /** * updates categories info when generating category content */ useEffect(() => { const categoryStep = steps.find((step) => step.step === SCOnBoardingStepType.CATEGORIES); if ((categoryStep === null || categoryStep === void 0 ? void 0 : categoryStep.status) === SCOnBoardingStepStatusType.IN_PROGRESS && categoryStep.results.length !== 0) { categoryStep.results.forEach((c) => { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore const isAlreadyNotified = prevCategoriesStep === null || prevCategoriesStep === void 0 ? void 0 : prevCategoriesStep.results.some((result) => result.id === c.id); if (!isAlreadyNotified) { notifyCategoryChanges(c); } }); } }, [steps, prevCategoriesStep]); /** * Render _step content section */ const getStepContent = () => { const stepObj = steps === null || steps === void 0 ? void 0 : steps.find((obj) => obj.step === (_step === null || _step === void 0 ? void 0 : _step.step)); let content; switch (stepObj === null || stepObj === void 0 ? void 0 : stepObj.step) { case SCOnBoardingStepType.CONTENTS: content = _jsx(Content, { step: stepObj, handleContentCreation: () => generateContent(stepObj.id) }); break; case SCOnBoardingStepType.CATEGORIES: content = _jsx(Category, { step: stepObj, handleCategoriesCreation: () => generateContent(stepObj.id) }); break; case SCOnBoardingStepType.APPEARANCE: content = _jsx(Appearance, { onCompleteAction: () => completeStep(stepObj) }); break; case SCOnBoardingStepType.PROFILE: content = _jsx(Profile, { onCompleteAction: () => completeStep(stepObj) }); break; case SCOnBoardingStepType.INVITE: content = _jsx(Invite, { onCompleteAction: () => completeStep(stepObj) }); break; case SCOnBoardingStepType.APP: content = _jsx(App, { step: stepObj, onCompleteAction: () => completeStep(stepObj) }); break; default: break; } return content; }; if (!isAdmin || !showOnBoarding) { return _jsx(HiddenPlaceholder, {}); } return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(AccordionRoot, Object.assign({ defaultExpanded: true, className: classes.accordionRoot, expanded: expanded }, { children: [_jsx(AccordionSummary, Object.assign({ expandIcon: _jsx(OnBoardingActionsButton, { isExpanded: expanded, onExpandChange: handleExpand, onHideOnBoarding: handlePreferencesUpdate }), "aria-controls": "accordion", id: "onBoarding-accordion" }, { children: _jsx(_Fragment, { children: expanded ? (_jsxs(_Fragment, { children: [!isMobile ? (_jsx(CardMedia, { className: classes.logo, component: "img", src: HeaderPlaceholder })) : (_jsxs(Typography, Object.assign({ variant: "h4" }, { children: [_jsx(Icon, Object.assign({ color: "secondary", fontSize: "medium" }, { children: "ai_stars" })), _jsx(FormattedMessage, { id: "ui.onBoardingWidget.accordion.expanded.title.mobile", defaultMessage: "ui.onBoardingWidget.accordion.expanded.title.mobile", values: { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore b: (chunks) => _jsx("strong", { children: chunks }) } })] }))), _jsxs(Box, Object.assign({ className: classes.intro }, { children: [!isMobile && (_jsx(Typography, Object.assign({ variant: "h4" }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.accordion.expanded.title", defaultMessage: "ui.onBoardingWidget.accordion.expanded.title" }) }))), _jsx(Typography, Object.assign({ variant: "h5" }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.accordion.expanded.subtitle", defaultMessage: "ui.onBoardingWidget.accordion.expanded.subtitle" }) })), _jsx(Typography, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.accordion.expanded.summary", defaultMessage: "ui.onBoardingWidget.accordion.expanded.summary", values: { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore b: (chunks) => _jsx("strong", { children: chunks }), // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore icon: (...chunks) => _jsx(Icon, { children: chunks }) } }) })] }))] })) : (_jsx(Typography, Object.assign({ variant: "body1" }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.accordion.collapsed", defaultMessage: "ui.onBoardingWidget.accordion.collapsed", values: { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore b: (chunks) => _jsx("strong", { children: chunks }), // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore // eslint-disable-next-line prettier/prettier icon: (...chunks) => _jsx(Icon, Object.assign({ color: "secondary", fontSize: "medium" }, { children: chunks })) } }) }))) }) })), _jsx(AccordionDetails, { children: _jsx(Widget, Object.assign({ className: classes.content, elevation: 0 }, { children: loading ? (_jsx(OnBoardingWidgetSkeleton, {})) : (_jsxs(CardContent, { children: [_jsx(List, Object.assign({ className: isMobile ? classes.stepsMobile : classes.steps }, { children: steps === null || steps === void 0 ? void 0 : steps.map((step) => (_jsx(ListItem, { children: isMobile ? (_jsx(Chip, { size: "small", label: _jsxs(_Fragment, { children: [_jsx(FormattedMessage, { id: `ui.onBoardingWidget.${step.step}`, defaultMessage: `ui.onBoardingWidget.${step.step}` }), ' ', step.status === SCOnBoardingStepStatusType.COMPLETED && (_jsx(Icon, Object.assign({ color: (step === null || step === void 0 ? void 0 : step.status) === SCOnBoardingStepStatusType.COMPLETED && (step === null || step === void 0 ? void 0 : step.step) !== (_step === null || _step === void 0 ? void 0 : _step.step) ? 'success' : 'inherit' }, { children: "check" })))] }), onClick: () => handleChange(step), variant: step.step === (_step === null || _step === void 0 ? void 0 : _step.step) ? 'filled' : 'outlined', color: step.status === SCOnBoardingStepStatusType.COMPLETED ? 'success' : 'default' })) : (_jsxs(ListItemButton, Object.assign({ onClick: () => handleChange(step), selected: (step === null || step === void 0 ? void 0 : step.step) === (_step === null || _step === void 0 ? void 0 : _step.step) }, { children: [_jsx(ListItemIcon, { children: _jsx(Checkbox, { edge: "start", checked: step.status === SCOnBoardingStepStatusType.COMPLETED, tabIndex: -1, disableRipple: true, inputProps: { 'aria-labelledby': step.step }, size: 'small' }) }), _jsx(ListItemText, { primary: _jsx(FormattedMessage, { id: `ui.onBoardingWidget.${step.step}`, defaultMessage: `ui.onBoardingWidget.${step.step}` }) })] }))) }, step.id))) })), _jsxs(Box, Object.assign({ className: classes.stepContent }, { children: [_jsx(Fade, Object.assign({ in: true, timeout: 2400 }, { children: _jsx(Box, { children: getStepContent() }) })), showNoCategoriesModal && (_jsx(BaseDialog, Object.assign({ title: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.no.categories", defaultMessage: "ui.onBoardingWidget.ai.no.categories" }), DialogContentProps: { dividers: false }, open: showNoCategoriesModal, onClose: () => setShowNoCategoriesModal(false), actions: _jsx(Button, Object.assign({ color: "secondary", onClick: () => setShowNoCategoriesModal(false) }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.no.categories.cancel", defaultMessage: "ui.onBoardingWidget.ai.no.categories.cancel" }) })) }, { children: _jsx(Button, Object.assign({ color: "primary", onClick: handleCategoriesClick, startIcon: _jsx(Icon, Object.assign({ fontSize: "small" }, { children: "edit" })) }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.no.categories.link", defaultMessage: "ui.onBoardingWidget.ai.no.categories.link" }) })) }))), showCategoriesWarningModal && (_jsx(DialogRoot, Object.assign({ className: classes.dialogRoot, title: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.categories.warning.title", defaultMessage: "ui.onBoardingWidget.ai.categories.warning.title" }), DialogContentProps: { dividers: false }, open: showCategoriesWarningModal, onClose: () => setShowWarningCategoriesModal(false), actions: _jsxs(_Fragment, { children: [_jsx(Button, Object.assign({ size: "small", variant: "outlined", color: "primary", onClick: () => setShowWarningCategoriesModal(false) }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.categories.warning.button.close", defaultMessage: "ui.onBoardingWidget.ai.categories.warning.button.close" }) })), _jsx(Button, Object.assign({ size: "small", variant: "contained", color: "secondary", onClick: () => startStep(SCOnBoardingStepIdType.CATEGORIES), endIcon: _jsx(Icon, Object.assign({ fontSize: "small" }, { children: "magic_wand" })) }, { children: _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.categories.warning.button.generate", defaultMessage: "ui.onBoardingWidget.ai.categories.warning.button.generate" }) }))] }) }, { children: _jsxs(Typography, Object.assign({ className: classes.dialogContent }, { children: [_jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.categories.warning.info", defaultMessage: "ui.onBoardingWidget.ai.categories.warning.info" }), _jsx(FormattedMessage, { id: "ui.onBoardingWidget.ai.categories.warning.confirm", defaultMessage: "ui.onBoardingWidget.ai.categories.warning.confirm", values: { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore // @ts-ignore b: (chunks) => _jsx("b", { children: chunks }) } })] })) })))] }))] })) })) })] })) }))); }; export default OnBoardingWidget;