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