UNPKG

@selfcommunity/react-ui

Version:

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

238 lines (237 loc) • 13.1 kB
import { __rest } from "tslib"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useMemo, useState } from 'react'; import { styled } from '@mui/material/styles'; import { Box, CircularProgress, IconButton, InputAdornment, MenuItem, TextField, useMediaQuery, useTheme } from '@mui/material'; import Icon from '@mui/material/Icon'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Endpoints, formatHttpErrorCode, http } from '@selfcommunity/api-services'; import { camelCase, Logger } from '@selfcommunity/utils'; import { SCPreferences, useSCContext, useSCPreferences, useSCUser } from '@selfcommunity/react-core'; import { DEFAULT_FIELDS } from '../../../constants/UserProfile'; import classNames from 'classnames'; import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import UsernameTextField from '../../../shared/UsernameTextField'; import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'; import { SCUserProfileFields } from '../../../types'; import MetadataField from '../../../shared/MetadataField'; import { SCOPE_SC_UI } from '../../../constants/Errors'; import { format, isBefore, isValid, parseISO, startOfHour } from 'date-fns'; import itLocale from 'date-fns/locale/it'; import enLocale from 'date-fns/locale/en-US'; import { LoadingButton } from '@mui/lab'; import { useSnackbar } from 'notistack'; import { PREFIX } from '../constants'; const messages = defineMessages({ genderMale: { id: 'ui.userProfileEditPublicInfo.genderMale', defaultMessage: 'ui.userProfileEditPublicInfo.genderMale' }, genderFemale: { id: 'ui.userProfileEditPublicInfo.genderFemale', defaultMessage: 'ui.userProfileEditPublicInfo.genderFemale' }, genderUnspecified: { id: 'ui.userProfileEditPublicInfo.genderUnspecified', defaultMessage: 'ui.userProfileEditPublicInfo.genderUnspecified' } }); const classes = { root: `${PREFIX}-public-info-root`, field: `${PREFIX}-field`, btnSave: `${PREFIX}-btn-save` }; const Root = styled(Box, { name: PREFIX, slot: 'PublicInfoRoot' })(() => ({})); const GENDERS = ['Male', 'Female', 'Unspecified']; const DATEPICKER_MINDATE = new Date(1000, 1, 1); export default function PublicInfo(props) { // PROPS const { id = null, className = null, fields = [...DEFAULT_FIELDS], onEditSuccess = null, onEditFailure = null, startActions = null, endActions = null } = props, rest = __rest(props, ["id", "className", "fields", "onEditSuccess", "onEditFailure", "startActions", "endActions"]); // CONTEXT const scContext = useSCContext(); const scUserContext = useSCUser(); const { enqueueSnackbar } = useSnackbar(); // PREFERENCES const scPreferences = useSCPreferences(); const metadataDefinitions = useMemo(() => { if (scPreferences.preferences && SCPreferences.CONFIGURATIONS_USER_METADATA_DEFINITIONS in scPreferences.preferences) { try { return JSON.parse(scPreferences.preferences[SCPreferences.CONFIGURATIONS_USER_METADATA_DEFINITIONS].value); } catch (e) { Logger.error(SCOPE_SC_UI, 'Error on parse user metadata.'); return {}; } } return null; }, [scPreferences.preferences]); // STATE const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [user, setUser] = useState(); const [error, setError] = useState({}); const [editing, setEditing] = useState([]); const [saving, setSaving] = useState([]); // INTL const intl = useIntl(); // EFFECTS useDeepCompareEffectNoCheck(() => { if (scUserContext.user) { setUser(Object.assign({}, scUserContext.user)); // eslint-disable-next-line @typescript-eslint/no-empty-function return () => { }; } }, [scUserContext.user]); // HANDLERS const handleSave = () => { setSaving([...editing]); setError({}); const data = {}; editing.map((f) => { data[f] = f === SCUserProfileFields.DATE_OF_BIRTH && user[f] ? format(user[f], 'yyyy-MM-dd') : user[f]; }); http .request({ url: Endpoints.UserPatch.url({ id: user.id }), method: Endpoints.UserPatch.method, data }) .then((res) => { scUserContext.updateUser(res.data); setEditing([]); setSaving([]); enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.userInfo.save.success", defaultMessage: "ui.userInfo.save.success" }), { variant: 'success', autoHideDuration: 3000 }); onEditSuccess && onEditSuccess(); }) .catch((e) => { setError(Object.assign(Object.assign({}, error), formatHttpErrorCode(e))); setSaving([]); enqueueSnackbar(_jsx(FormattedMessage, { id: "ui.common.error.action", defaultMessage: "ui.common.error.action" }), { variant: 'error', autoHideDuration: 3000 }); onEditFailure && onEditFailure(); }); }; const handleChange = (field) => { return (event) => { setUser(Object.assign(Object.assign({}, user), { [event.target.name]: event.target.value })); setEditing([...editing, field]); if (!event.target.value && event.target.name in metadataDefinitions && metadataDefinitions[event.target.name].mandatory) { setError(Object.assign(Object.assign({}, error), { [`${camelCase(event.target.name)}Error`]: { error: 'invalid' } })); } else if (error[`${camelCase(event.target.name)}Error`]) { delete error[`${camelCase(event.target.name)}Error`]; setError(error); } }; }; // RENDER const renderField = (field) => { const isEditing = editing.includes(field); const isSaving = saving.includes(field); const camelField = camelCase(field); const _error = error !== null && error[`${camelField}Error`] && error[`${camelField}Error`].error; const component = { element: TextField }; let label = metadataDefinitions[field] ? metadataDefinitions[field].label : intl.formatMessage({ id: `ui.userInfo.${camelField}`, defaultMessage: `ui.userInfo.${camelField}` }); let props = { InputProps: { endAdornment: _jsx(InputAdornment, Object.assign({ position: "end" }, { children: isSaving && _jsx(CircularProgress, { size: 10 }) })) } }; let content = null; switch (field) { case SCUserProfileFields.USERNAME: component.element = UsernameTextField; break; case SCUserProfileFields.DATE_JOINED: return null; case SCUserProfileFields.DATE_OF_BIRTH: return (_jsx(LocalizationProvider, Object.assign({ dateAdapter: AdapterDateFns, adapterLocale: scContext.settings.locale.default === 'it' ? itLocale : enLocale }, { children: _jsx(DatePicker, { label: intl.formatMessage({ id: `ui.userInfo.${camelCase(field)}`, defaultMessage: `ui.userInfo.${field}` }), defaultValue: user[field] ? parseISO(user[field]) : null, minDate: DATEPICKER_MINDATE, onChange: (newValue) => { const u = user; const field = SCUserProfileFields.DATE_OF_BIRTH; const camelField = camelCase(field); if ((isValid(newValue) && isBefore(startOfHour(DATEPICKER_MINDATE), newValue) && isBefore(newValue, new Date())) || !newValue) { if (error[`${camelField}Error`]) { const _error = Object.assign({}, error); delete _error[`${camelField}Error`]; setError(_error); } u[field] = newValue; } else { u[field] = null; setError({ [`${camelField}Error`]: { error: intl.formatMessage({ id: 'ui.publicInfo.dateOfBirth.error', defaultMessage: 'ui.publicInfo.dateOfBirth.error' }) } }); } setUser(u); setEditing([...editing, field]); }, disableFuture: true, disabled: isSaving, slots: { inputAdornment: (params) => { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore,@typescript-eslint/ban-ts-comment // @ts-ignore const _a = params.children.props, { children } = _a, rest = __rest(_a, ["children"]); return (_jsxs(InputAdornment, Object.assign({ position: 'end' }, { children: [_jsx(IconButton, Object.assign({}, rest, { children: children })), isSaving && _jsx(CircularProgress, { size: 10 })] }))); } }, // onAccept={isMobile ? handleSave(SCUserProfileFields.DATE_OF_BIRTH) : null} slotProps: { textField: { className: classes.field, fullWidth: true, variant: 'outlined', helperText: _error || null, InputProps: { endAdornment: isMobile && (_jsxs(_Fragment, { children: [_jsx(IconButton, Object.assign({ disabled: !isEditing }, { children: _jsx(Icon, { children: "CalendarIcon" }) })), isSaving ? _jsx(CircularProgress, { size: 10 }) : null] })) } } } }) }), field)); case SCUserProfileFields.BIO: props.multiline = true; break; case SCUserProfileFields.WEBSITE: props.type = 'url'; props.pattern = 'https://.*'; props.size = '30'; break; case SCUserProfileFields.GENDER: props.select = true; content = GENDERS.map((gender) => (_jsx(MenuItem, Object.assign({ value: gender }, { children: intl.formatMessage(messages[`gender${gender}`]) }), gender))); break; case SCUserProfileFields.TAGS: return null; default: if (metadataDefinitions && metadataDefinitions[field]) { return (_jsx(MetadataField, Object.assign({ id: field, className: classes.field, name: field, fullWidth: true, label: label, value: user[field] || '', onChange: handleChange(field), disabled: isSaving || (user[field] && 'editable' in metadataDefinitions[field] && !metadataDefinitions[field].editable && !isEditing), error: Boolean(_error), helperText: _error && _jsx(FormattedMessage, { id: `ui.userInfo.metadata.error.${_error}`, defaultMessage: `ui.userInfo.metadata.error.${_error}` }), metadata: metadataDefinitions[field] }, props), field)); } break; } return (_jsx(component.element, Object.assign({ id: field }, props, { className: classes.field, name: field, fullWidth: true, label: label, value: user[field] || '', onChange: handleChange(field), disabled: isSaving, error: Boolean(_error), helperText: _error && _jsx(FormattedMessage, { id: `ui.userInfo.${camelField}.error.${_error}`, defaultMessage: `ui.userInfo.${camelField}.error.${_error}` }) }, { children: content }), field)); }; // FIELDS const _fields = [...fields, ...Object.keys(metadataDefinitions)]; if (_fields.length === 0 || !user) { return null; } return (_jsxs(Root, Object.assign({ id: id, className: classNames(classes.root, className) }, rest, { children: [startActions, _fields.map((field) => { return renderField(field); }), _jsx(LoadingButton, Object.assign({ className: classes.btnSave, fullWidth: true, variant: "contained", color: "secondary", onClick: handleSave, loading: saving.length > 0, disabled: saving.length > 0 || !editing.length || Object.keys(error).length > 0 }, { children: _jsx(FormattedMessage, { id: 'ui.userInfo.button.save', defaultMessage: 'ui.userInfo.button.save' }) })), endActions] }))); }