@selfcommunity/react-ui
Version:
React UI Components to integrate a Community created with SelfCommunity Platform.
332 lines (331 loc) • 23.5 kB
JavaScript
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;