Skip to content
Commits on Source (10)
......@@ -18,6 +18,25 @@ AppSettings.deny({
},
});
const SettingsType = (type) =>
new SimpleSchema({
external: {
type: Boolean,
label: getLabel(`api.appsettings.labels.external_${type}`), // TODO i18n
optional: true,
},
link: {
type: String,
label: getLabel(`api.appsettings.labels.link_${type}`), // TODO i18n
optional: true,
},
content: {
type: String,
label: getLabel(`api.appsettings.labels.content_${type}`), // TODO i18n
optional: true,
},
});
AppSettings.schema = new SimpleSchema(
{
maintenance: {
......@@ -30,6 +49,22 @@ AppSettings.schema = new SimpleSchema(
defaultValue: ' ',
label: getLabel('api.appsettings.labels.textMaintenance'),
},
legal: {
type: SettingsType('legal'),
label: getLabel('api.appsettings.labels.legal'),
},
accessibility: {
type: SettingsType('accessibility'),
label: getLabel('api.appsettings.labels.accessibility'),
},
gcu: {
type: SettingsType('gcu'),
label: getLabel('api.appsettings.labels.gcu'),
},
personalData: {
type: SettingsType('personal_data'),
label: getLabel('api.appsettings.labels.personal_data'),
},
},
{ clean: { removeEmptyStrings: false }, tracker: Tracker },
);
......@@ -37,6 +72,42 @@ AppSettings.schema = new SimpleSchema(
AppSettings.publicFields = {
maintenance: 1,
textMaintenance: 1,
legal: 1,
accessibility: 1,
gcu: 1,
personalData: 1,
};
AppSettings.links = {
'legal.link': 1,
'legal.external': 1,
'accessibility.link': 1,
'accessibility.external': 1,
'gcu.link': 1,
'gcu.external': 1,
'personalData.link': 1,
'personalData.external': 1,
};
AppSettings.legal = {
'legal.link': 1,
'legal.external': 1,
'legal.content': 1,
};
AppSettings.accessibility = {
'accessibility.link': 1,
'accessibility.external': 1,
'accessibility.content': 1,
};
AppSettings.gcu = {
'gcu.link': 1,
'gcu.external': 1,
'gcu.content': 1,
};
AppSettings.personalData = {
'personalData.link': 1,
'personalData.external': 1,
'personalData.content': 1,
};
AppSettings.attachSchema(AppSettings.schema);
......
import { Meteor } from 'meteor/meteor';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { _ } from 'meteor/underscore';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import AppSettings from './appsettings';
// eslint-disable-next-line import/prefer-default-export
export const getAppSettingsLinks = new ValidatedMethod({
name: 'appSettings.getAppSettingsLinks',
validate: null,
run() {
try {
return AppSettings.findOne({ _id: 'settings' }, { fields: AppSettings.links });
} catch (error) {
throw new Meteor.Error(error, error);
}
},
});
// Get list of all method names on User
const LISTS_METHODS = _.pluck([getAppSettingsLinks], 'name');
if (Meteor.isServer) {
// Only allow 5 list operations per connection per second
DDPRateLimiter.addRule(
{
name(name) {
return _.contains(LISTS_METHODS, name);
},
// Rate limit per connection ID
connectionId() {
return true;
},
},
5,
1000,
);
}
......@@ -26,6 +26,9 @@ const settingsGroup = new SimpleSchema({
name: {
type: String,
},
type: {
type: SimpleSchema.Integer,
},
});
const settingsParticipant = new SimpleSchema({
......
......@@ -95,4 +95,10 @@ export const validateEmail = (email) => {
return re.test(String(email).toLowerCase());
};
export const getGroupName = (group) => {
if (group.type !== 15) return group.name;
return `[STRUC] ${group.name.slice(group.name.indexOf('_') + 1, group.name.length)}`;
};
export default logServer;
......@@ -12,3 +12,4 @@ import '../../api/groups/groups';
import '../../api/appsettings/server/publications';
import '../../api/appsettings/appsettings';
import '../../api/appsettings/methods';
import React from 'react';
import React, { useEffect } from 'react';
import i18n from 'meteor/universe:i18n';
import PropTypes from 'prop-types';
import { useTracker } from 'meteor/react-meteor-data';
......@@ -12,6 +12,7 @@ import FormHelperText from '@material-ui/core/FormHelperText';
import MenuItem from '@material-ui/core/MenuItem';
import SingleGroupDisplay from './SingleGroupDisplay';
import Groups from '../../../api/groups/groups';
import { getGroupName } from '../../../api/utils/functions';
const useStyles = makeStyles(() => ({
field: {
......@@ -25,22 +26,10 @@ const useStyles = makeStyles(() => ({
const GroupsSelector = ({ stateHook: [state, setState], errors, groupId }) => {
const classes = useStyles();
if (groupId !== undefined) {
const selectedIds = state.groups.map(({ _id }) => _id);
if (!selectedIds.includes(groupId)) {
const group = Groups.findOne({ _id: groupId });
if (group !== undefined) {
setState({
groups: [...state.groups, { _id: groupId, name: group.name }],
});
}
}
}
const handleSelect = (e) => {
const group = Groups.findOne(e.target.value);
setState({
groups: [...state.groups, { _id: e.target.value, name: group.name }],
groups: [...state.groups, { _id: e.target.value, name: group.name, type: group.type }],
});
};
......@@ -60,6 +49,20 @@ const GroupsSelector = ({ stateHook: [state, setState], errors, groupId }) => {
};
});
useEffect(() => {
if (groupId !== undefined) {
const selectedIds = state.groups.map(({ _id }) => _id);
if (!selectedIds.includes(groupId)) {
const group = Groups.findOne({ _id: groupId });
if (group !== undefined) {
setState({
groups: [...state.groups, { _id: groupId, name: group.name, type: group.type }],
});
}
}
}
}, [groups.ready]);
useTracker(() => {
Meteor.subscribe('users.groups', { groupsIds: state.groups.map(({ _id }) => _id) });
});
......@@ -78,7 +81,7 @@ const GroupsSelector = ({ stateHook: [state, setState], errors, groupId }) => {
</MenuItem>
{groups.list.map((group) => (
<MenuItem key={group._id} value={group._id}>
{group.name}
{getGroupName(group)}
</MenuItem>
))}
</Select>
......@@ -96,8 +99,12 @@ const GroupsSelector = ({ stateHook: [state, setState], errors, groupId }) => {
export default GroupsSelector;
GroupsSelector.defaultProps = {
groupId: undefined,
};
GroupsSelector.propTypes = {
stateHook: PropTypes.arrayOf(PropTypes.any).isRequired,
errors: PropTypes.objectOf(PropTypes.any).isRequired,
groupId: PropTypes.objectOf(PropTypes.String).isRequired,
groupId: PropTypes.string,
};
......@@ -14,6 +14,7 @@ import IconButton from '@material-ui/core/IconButton';
import DoneIcon from '@material-ui/icons/Done';
import RemoveIcon from '@material-ui/icons/CloseOutlined';
import Groups from '../../../api/groups/groups';
import { getGroupName } from '../../../api/utils/functions';
const useStyles = makeStyles((theme) => ({
chip: {
......@@ -66,7 +67,7 @@ const SingleGroupDisplay = ({ group, handleDelete, view, event }) => {
<DeleteButton />
</IconButton>
)}
<Typography>{group.name}</Typography>
<Typography>{getGroupName(group)}</Typography>
</AccordionSummary>
<AccordionDetails className={classes.results}>
{users.map(({ emails = [], avatar, _id }) => {
......
import React, { useState, useEffect } from 'react';
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Toolbar from '@material-ui/core/Toolbar';
import AppBar from '@material-ui/core/AppBar';
import i18n from 'meteor/universe:i18n';
import { useAppContext } from '../../contexts/context';
import { getAppSettingsLinks } from '../../../api/appsettings/methods';
const useStyles = makeStyles((theme) => ({
root: {
......@@ -37,52 +38,46 @@ const useStyles = makeStyles((theme) => ({
},
}));
export const footer = [
{
text: 'legal',
path: '/legal/legalnotice',
},
{
text: 'accessibility',
path: '/legal/accessibility',
},
{
text: 'gcu',
path: '/legal/conditions',
},
{
text: 'personalData',
path: '/legal/personal-data',
},
{
text: 'contact',
path: '/contact',
},
];
export const LEGAL_ROUTES = {
legal: 'legalnotice',
personalData: 'personal-data',
accessibility: 'accessibility',
gcu: 'conditions',
};
const Footer = () => {
const classes = useStyles();
const [{ isMobile }] = useAppContext();
const { laboiteURL } = Meteor.settings.public;
const laboiteURL = Meteor.settings.public.laboiteUrl;
const [settingsData, setSettingsData] = useState([]);
const toolbarContent = () => {
return (
<>
{footer.map(({ path, text }) => {
{settingsData.map(({ key, external, link, text }) => {
return isMobile ? (
<li key={text} className={classes.li}>
<a className={classes.mobileLink} target="_blank" href={`${laboiteURL}${path}`} rel="noreferrer noopener">
{i18n.__(`components.Footer.${text}`)}
</a>
<li key={key} className={classes.li}>
{external ? (
<a className={classes.mobileLink} target="_blank" href={link} rel="noreferrer noopener">
{i18n.__(`components.Footer.${text}`)}
</a>
) : (
<a
className={classes.mobileLink}
target="_blank"
rel="noreferrer noopener"
href={`${laboiteURL}/legal/${link}`}
>
{i18n.__(`components.Footer.${text}`)}
</a>
)}
</li>
) : external ? (
<a key={key} className={classes.link} target="_blank" href={link} rel="noreferrer noopener">
{i18n.__(`components.Footer.${text}`)}
</a>
) : (
<a
key={text}
className={classes.link}
target="_blank"
href={`${laboiteURL}${path}`}
rel="noreferrer noopener"
>
<a className={classes.link} target="_blank" rel="noreferrer noopener" href={`${laboiteURL}/legal/${link}`}>
{i18n.__(`components.Footer.${text}`)}
</a>
);
......@@ -91,6 +86,27 @@ const Footer = () => {
);
};
useEffect(() => {
let isCancelled = false;
getAppSettingsLinks.call(null, (error, result) => {
const newData = { ...result };
delete newData._id;
const keys = Object.keys(newData);
const appsettings = keys.map((key) => ({
key,
external: newData[key].external,
link: newData[key].external ? newData[key].link : LEGAL_ROUTES[key],
text: key,
}));
if (!isCancelled) setSettingsData(appsettings);
});
return () => {
// fix to avoid modifying component after unmounting
// see : https://stackoverflow.com/questions/56442582/react-hooks-cant-perform-a-react-state-update-on-an-unmounted-component/56443045#56443045
isCancelled = true;
};
}, []);
return (
<AppBar style={{ position: 'relative' }}>
{isMobile ? (
......
{
"name": "agenda3",
"version": "3.6.0",
"version": "3.7.0-testing.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "agenda3",
"version": "3.6.0",
"version": "3.7.0-testing.1",
"license": "EUPL-1.2",
"description": "Online diary",
"author": "EOLE/PCLL <team@eole.education> - DINUM",
......
......@@ -19,8 +19,8 @@ Factory.define('event', Events, {
end: () => new Date(moment().add(7, 'days').add(1, 'hour').format()),
allDay: () => randomBoolean(),
groups: () => [
{ _id: Random.id(), name: faker.company.companyName() },
{ _id: Random.id(), name: faker.company.companyName() },
{ _id: Random.id(), name: faker.company.companyName(), type: 0 },
{ _id: Random.id(), name: faker.company.companyName(), type: 0 },
],
participants: () => {
const users = [1, 2, 3];
......
......@@ -3,7 +3,7 @@
"enableKeycloak": true,
"keycloakUrl": "",
"keycloakRealm": "",
"laboiteURL": "",
"laboiteUrl": "",
"caldavUrl": "",
"packages": {
"dynamic-import": {
......