UNPKG

@selfcommunity/react-ui

Version:

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

254 lines (253 loc) • 15.3 kB
import { __rest } from "tslib"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { LoadingButton } from '@mui/lab'; import { Button, CardActions, CardContent, CardHeader, Divider, Icon, Stack, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material'; import { styled } from '@mui/material/styles'; import { Box, useThemeProps } from '@mui/system'; import { Endpoints, EventService, http } from '@selfcommunity/api-services'; import { SCCache, useSCFetchEvent, useSCUser } from '@selfcommunity/react-core'; import { CacheStrategies, Logger } from '@selfcommunity/utils'; import { Fragment, useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { SCOPE_SC_UI } from '../../constants/Errors'; import { DEFAULT_PAGINATION_LIMIT, DEFAULT_PAGINATION_OFFSET } from '../../constants/Pagination'; import BaseDialog from '../../shared/BaseDialog'; import ConfirmDialog from '../../shared/ConfirmDialog/ConfirmDialog'; import HiddenPlaceholder from '../../shared/HiddenPlaceholder'; import InfiniteScroll from '../../shared/InfiniteScroll'; import { Lightbox } from '../../shared/Lightbox'; import { actionWidgetTypes, dataWidgetReducer, stateWidgetInitializer } from '../../utils/widget'; import Widget from '../Widget'; import { PREFIX } from './constants'; import SkeletonComponent, { EventMediaSkeleton } from './Skeleton'; import TriggerButton from './TriggerButton'; const messages = defineMessages({ title: { id: 'ui.eventMediaWidget.title', defaultMessage: 'ui.eventMediaWidget.title' } }); const MEDIAS_TO_SHOW = 9; const classes = { root: `${PREFIX}-root`, header: `${PREFIX}-header`, grid: `${PREFIX}-grid`, media: `${PREFIX}-media`, mediaLayer: `${PREFIX}-media-layer`, countHiddenMediaWrapper: `${PREFIX}-count-hidden-media-wrapper`, countHiddenMedia: `${PREFIX}-count-hidden-media`, content: `${PREFIX}-content`, actions: `${PREFIX}-actions`, dialogRoot: `${PREFIX}-dialog-root`, dialogInfiniteScroll: `${PREFIX}-dialog-infinite-scroll`, dialogMediaWrapper: `${PREFIX}-dialog-media-wrapper`, dialogButtonWrapper: `${PREFIX}-dialog-button-wrapper`, dialogLoadingButton: `${PREFIX}-dialog-loading-button`, endMessage: `${PREFIX}-end-message` }; const Root = styled(Widget, { name: PREFIX, slot: 'Root', overridesResolver: (_props, styles) => styles.root, shouldForwardProp: (prop) => prop !== 'showPadding' })(() => ({})); const DialogRoot = styled(BaseDialog, { name: PREFIX, slot: 'DialogRoot', overridesResolver: (_props, styles) => styles.dialogRoot, shouldForwardProp: (prop) => prop !== 'loading' })(() => ({})); export default function EventMediaWidget(inProps) { // PROPS const props = useThemeProps({ props: inProps, name: PREFIX }); // CONST const { event, eventId, limit = DEFAULT_PAGINATION_LIMIT, endpointQueryParams = { limit, offset: DEFAULT_PAGINATION_OFFSET }, cacheStrategy = CacheStrategies.CACHE_FIRST, dialogProps } = props, rest = __rest(props, ["event", "eventId", "limit", "endpointQueryParams", "cacheStrategy", "dialogProps"]); // HOOKS const { scEvent } = useSCFetchEvent({ id: eventId, event }); const intl = useIntl(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); // STATE const [state, dispatch] = useReducer(dataWidgetReducer, { isLoadingNext: false, next: null, cacheKey: SCCache.getWidgetStateCacheKey(SCCache.EVENT_MEDIA_STATE_CACHE_PREFIX_KEY, event ? event.id : eventId), cacheStrategy, visibleItems: limit }, stateWidgetInitializer); const [medias, setMedias] = useState(state.results); const [mediasCount, setMediasCount] = useState(state.count); const [openDialog, setOpenDialog] = useState(false); const [openDialogConfirm, setOpenDialogConfirm] = useState(false); const [mediaId, setMediaId] = useState(null); const [preview, setPreview] = useState(-1); const [loading, setLoading] = useState(false); const [showSkeleton, setShowSkeleton] = useState(null); // CONTEXT const scUserContext = useSCUser(); // CONSTS const hasAllow = useMemo(() => { var _a; return ((_a = scUserContext.user) === null || _a === void 0 ? void 0 : _a.id) === (scEvent === null || scEvent === void 0 ? void 0 : scEvent.managed_by.id); }, [scUserContext, scEvent]); const countHiddenMedia = useMemo(() => mediasCount - MEDIAS_TO_SHOW, [mediasCount]); const _fetchNext = useCallback((index) => { if (mediasCount > medias.length && index >= 6 && !state.isLoadingNext && state.next) { setPreview(index); dispatch({ type: actionWidgetTypes.LOADING_NEXT }); http .request({ url: state.next, method: Endpoints.GetEventPhotoGallery.method }) .then((res) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: res.data }); setMedias((prev) => [...prev, ...res.data.results]); setMediasCount(res.data.count); }) .catch((error) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } }); Logger.error(SCOPE_SC_UI, error); }); } }, [state.next, state.isLoadingNext, medias, mediasCount, dispatch, setPreview]); const handleOpenLightbox = useCallback((index) => { setPreview(index); }, [setPreview]); const handleCloseLightbox = useCallback(() => { setPreview(-1); }, [setPreview]); const handleToggleDialogOpen = useCallback(() => { setOpenDialog((prev) => !prev); }, [setOpenDialog]); const handleNext = useCallback(() => { setShowSkeleton('dialog'); dispatch({ type: actionWidgetTypes.LOADING_NEXT }); http .request({ url: state.next, method: Endpoints.GetEventPhotoGallery.method }) .then((res) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: res.data }); setMedias((prev) => [...prev, ...res.data.results]); setMediasCount(res.data.count); setShowSkeleton(null); }) .catch((error) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } }); Logger.error(SCOPE_SC_UI, error); }); }, [state.next, state.isLoadingNext, state.initialized, dispatch, setMedias, setMediasCount, setShowSkeleton]); const handleRemoveMedia = useCallback((id) => { if (hasAllow) { setMediaId(id); setOpenDialogConfirm(true); } }, [setMediaId, setOpenDialogConfirm]); const handleConfirmAction = useCallback(() => { setLoading(true); http .request({ url: Endpoints.RemoveMediasFromEventPhotoGallery.url({ id: scEvent.id }), method: Endpoints.RemoveMediasFromEventPhotoGallery.method, data: { medias: [mediaId] } }) .then(() => { setMedias((prev) => prev.filter((media) => media.id !== mediaId)); setMediasCount((prev) => prev - 1); setMediaId(null); setLoading(false); setOpenDialogConfirm(false); }) .catch((error) => { Logger.error(SCOPE_SC_UI, error); }); }, [scEvent, mediaId, setMedias, setLoading, setOpenDialogConfirm, dispatch, setMediasCount]); const handleCloseAction = useCallback(() => { setMediaId(null); setOpenDialogConfirm(false); }, [setMediaId, setOpenDialogConfirm]); const handleAddMedia = useCallback((media) => { setShowSkeleton('widget'); http .request({ url: Endpoints.AddMediaToEventPhotoGallery.url({ id: scEvent.id }), method: Endpoints.AddMediaToEventPhotoGallery.method, data: { media: media.id } }) .then((res) => { setMedias((prev) => [res.data, ...prev]); setMediasCount((prev) => prev + 1); setShowSkeleton(null); }) .catch((error) => { Logger.error(SCOPE_SC_UI, error); }); }, [scEvent, setMedias, setMediasCount, setShowSkeleton]); // EFFECTS useEffect(() => { let _t; if (scUserContext.user && scEvent && !state.initialized && Boolean((eventId !== undefined && scEvent.id === eventId) || (event && scEvent.id === event.id))) { _t = setTimeout(_initComponent); return () => { clearTimeout(_t); }; } }, [scUserContext.user, scEvent, state.initialized]); useEffect(() => { if (state.initialized && scEvent && Boolean((eventId !== undefined && scEvent.id !== eventId) || (event && scEvent.id !== event.id))) { dispatch({ type: actionWidgetTypes.RESET, payload: {} }); } }, [state.initialized, scEvent, eventId, event]); /** * Initialize component * Fetch data only if the component is not initialized and it is not loading data */ const _initComponent = useCallback(() => { if (!state.isLoadingNext) { dispatch({ type: actionWidgetTypes.LOADING_NEXT }); EventService.getEventPhotoGallery(scEvent.id, Object.assign({}, endpointQueryParams)) .then((payload) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_SUCCESS, payload: Object.assign(Object.assign({}, payload), { initialized: true }) }); setMedias(payload.results); setMediasCount(payload.count); }) .catch((error) => { dispatch({ type: actionWidgetTypes.LOAD_NEXT_FAILURE, payload: { errorLoadNext: error } }); Logger.error(SCOPE_SC_UI, error); }); } }, [state.isLoadingNext, scEvent, eventId, dispatch, setMedias, setMediasCount]); useEffect(() => { if (isMobile && openDialog && state.next) { handleNext(); } }, [isMobile, openDialog, state.next]); if (!scUserContext.user) { return _jsx(HiddenPlaceholder, {}); } // RENDER if (!scEvent || !state.initialized || (scEvent && ((eventId !== undefined && scEvent.id !== eventId) || (event && scEvent.id !== event.id))) || (state.isLoadingNext && !state.initialized)) { return _jsx(SkeletonComponent, {}); } if (state.initialized && state.count === 0 && !hasAllow) { return _jsx(HiddenPlaceholder, {}); } return (_jsxs(Root, Object.assign({ className: classes.root }, rest, { showPadding: hasAllow }, { children: [_jsx(CardHeader, { title: _jsxs(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center" }, { children: [_jsx(Typography, Object.assign({ variant: "h5" }, { children: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.title", defaultMessage: "ui.eventMediaWidget.title" }) })), hasAllow && mediasCount > 0 && _jsx(TriggerButton, { size: "small", onAdd: handleAddMedia })] })), className: classes.header }), _jsx(Divider, {}), _jsxs(CardContent, Object.assign({ className: classes.content }, { children: [_jsxs(Box, Object.assign({ className: classes.grid }, { children: [showSkeleton === 'widget' && _jsx(EventMediaSkeleton, {}), medias.slice(0, MEDIAS_TO_SHOW).map((media, i, array) => (_jsx(Box, Object.assign({ onClick: () => handleOpenLightbox(i), sx: { background: `url(${media.image}) no-repeat center` }, className: classes.media }, { children: medias.length > array.length && i === array.length - 1 && (_jsxs(Fragment, { children: [_jsx(Box, { className: classes.mediaLayer }), _jsx(Box, Object.assign({ className: classes.countHiddenMediaWrapper }, { children: _jsxs(Typography, Object.assign({ className: classes.countHiddenMedia }, { children: ["+", countHiddenMedia] })) }))] })) }), media.id))), hasAllow && mediasCount === 0 && (_jsx(Tooltip, Object.assign({ title: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.add", defaultMessage: "ui.eventMediaWidget.add" }) }, { children: _jsx(TriggerButton, { size: "large", onAdd: handleAddMedia, isSquare: true }) })))] })), preview !== -1 && _jsx(Lightbox, { onClose: handleCloseLightbox, index: preview, medias: medias, onIndexChange: _fetchNext })] })), hasAllow && mediasCount > 0 && (_jsx(CardActions, Object.assign({ className: classes.actions }, { children: _jsx(Button, Object.assign({ onClick: handleToggleDialogOpen }, { children: _jsx(Typography, Object.assign({ variant: "caption" }, { children: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.showAll", defaultMessage: "ui.eventMediaWidget.showAll" }) })) })) }))), openDialog && (_jsx(DialogRoot, Object.assign({ className: classes.dialogRoot, title: intl.formatMessage(messages.title, { user: scEvent.managed_by.username }), onClose: handleToggleDialogOpen, open: true }, dialogProps, { children: _jsx(InfiniteScroll, Object.assign({ dataLength: medias.length, height: isMobile ? '100%' : '515px', next: handleNext, hasMoreNext: Boolean(state.next), className: classes.dialogInfiniteScroll, endMessage: _jsx(Typography, Object.assign({ className: classes.endMessage }, { children: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.noMoreResults", defaultMessage: "ui.eventMediaWidget.noMoreResults" }) })) }, { children: _jsxs(Box, Object.assign({ className: classes.grid }, { children: [medias.map((media) => (_jsx(Box, Object.assign({ sx: { background: `url(${media.image}) no-repeat center` }, className: classes.dialogMediaWrapper }, { children: _jsx(Stack, Object.assign({ className: classes.dialogButtonWrapper }, { children: _jsx(LoadingButton, Object.assign({ className: classes.dialogLoadingButton, loading: mediaId === media.id, size: "large", onClick: () => handleRemoveMedia(media.id), sx: { color: (theme) => (mediaId === media.id ? 'transparent' : theme.palette.common.white) } }, { children: _jsx(Icon, Object.assign({ fontSize: "inherit" }, { children: "delete" })) })) })) }), media.id))), showSkeleton === 'dialog' && Array.from(Array(countHiddenMedia)).map((_, i) => _jsx(EventMediaSkeleton, {}, i))] })) })) }))), openDialogConfirm && (_jsx(ConfirmDialog, { open: true, title: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.dialog.title", defaultMessage: "ui.eventMediaWidget.dialog.title" }), content: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.dialog.msg", defaultMessage: "ui.eventMediaWidget.dialog.msg" }), btnConfirm: _jsx(FormattedMessage, { id: "ui.eventMediaWidget.dialog.confirm", defaultMessage: "ui.eventMediaWidget.dialog.confirm" }), isUpdating: loading, onConfirm: handleConfirmAction, onClose: handleCloseAction }))] }))); }