Skip to content
Commits on Source (29)
extends:
- '@commitlint/config-conventional'
rules:
body-max-line-length: [2, 'always', 120]
footer-max-line-length: [2, 'always', 120]
header-max-length: [2, 'always', 72]
......@@ -10,10 +10,10 @@ include:
file: /templates/Runners/apps.education-docker.yaml
- project: EOLE/infra/ci-tools
ref: stable
file: /templates/Lint/Commitlint.yaml
file: /templates/Git.yaml
- project: EOLE/infra/ci-tools
ref: stable
file: /templates/Release/Semantic-release.yaml
file: /templates/Semantic-release.yaml
- project: EOLE/infra/ci-tools
ref: stable
file: /templates/Docker.yaml
......@@ -32,8 +32,14 @@ stages:
# Common setup for all meteor based jobs
.meteor:
extends: .not-on-stable
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.1
rules:
- !reference [.rules-map, not-on-schedule]
- !reference [.rules-map, not-on-draft]
- !reference [.rules-map, not-on-tag]
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
before_script:
- cd app
cache:
......@@ -55,6 +61,7 @@ stages:
# Execute `commitlint` before long `cache-dependencies` job
commitlint:
stage: initial-checks
extends: .git:commitlint
###############################################################################
# `deps` stage: to download and cache dependencies
......@@ -94,44 +101,38 @@ meteor-tests:
#
# The ordering is important:
# 1. exclude schedules and drafts
# 2. include `on-release-tag` which must match before `not-on-semantic-release-commit`
# 3. we exclude stable which just produce the release tag
# 4. we exclude `semantic-release` commits like when merging `release` on `dev`
# 5. run on every branches
# 2. include release and prerelease tag which must match before `not-on-semantic-release-commit`
# 3. we exclude `semantic-release` commits except for `dev`
# 4. run on every branches
build-docker:
extends: .build-docker-image
extends: .docker:image:build
rules:
- !reference [.rules-map, not-on-schedule]
- !reference [.rules-map, not-on-draft]
- !reference [.rules-map, on-release-tag]
- !reference [.rules-map, not-on-stable]
- !reference [.rules-map, on-testing-tag]
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
###############################################################################
# `release` stage: `semantic-release`, `merge-to-dev`, `tag *`
# `release` stage: `new release`, testing prerelease`, `merge-to-dev`, `tag *`
###############################################################################
# Create the release versions on `$STABLE_BRANCH`
new release: {extends: '.semantic-release:stable'}
# Create the prereleases versions on `$TESTING_BRANCH`
# update `.releaserc.js` variable `betaBranch`
testing prerelease: {extends: '.semantic-release:testing'}
# Avoid regression and update `version` of app/package*.json in `$DEV_BRANCH`
merge-to-dev:
stage: release
extends:
- .on-release-tag
image: 'hub.eole.education/eole/commitlint:latest'
script:
# Add `upstream` remote to get access to `upstream/dev`
# Use `${GITLAB_TOKEN}` for write permission
- 'git remote show upstream 2> /dev/null || git remote add upstream ${CI_REPOSITORY_URL/${CI_JOB_TOKEN}/${GITLAB_TOKEN}}'
- 'git fetch --all'
- 'git checkout -b ${DEV_BRANCH} upstream/${DEV_BRANCH}'
- 'git merge --no-edit ${CI_COMMIT_TAG}'
- 'git push upstream ${DEV_BRANCH}'
# Remove `upstream` to avoid caching `CI_JOB_TOKEN`
- 'git remote remove upstream'
merge-to-dev: {extends: '.git:merge-to', variables: {GIT_MERGE_TARGET: $DEV_BRANCH}}
## tag contribution branches with a more stable name than `git-${CI_COMMIT_SHORT_SHA}`
tag contrib branch:
extends:
- .tag-docker-image
- .docker:image:tag
- .on-branches
variables:
# `feature/foo-bar_quux` → `feature-foo-bar-quux`
......@@ -139,45 +140,49 @@ tag contrib branch:
## dev images
tag dev:
extends:
- .tag-docker-image
- .on-dev
extends: .docker:image:tag
rules:
- !reference [.rules-map, not-on-schedule]
- !reference [.rules-map, not-on-draft]
- !reference [.rules-map, on-dev]
variables:
IMAGE_TAG: dev
## testing images
tag testing prerelease:
extends:
- .docker:image:tag
- .on-testing-tag
tag testing:
extends:
- .tag-docker-image
- .on-testing
- .docker:image:tag
- .on-testing-tag
variables:
IMAGE_TAG: testing
## stable images
tag release:
extends: .tag-docker-image
extends: .docker:image:tag
tag major:
extends: .tag-docker-image
extends: .docker:image:tag
before_script:
- export RELEASE_PREFIX=${RELEASE_PREFIX:-release/}
- export RELEASE=${CI_COMMIT_TAG#${RELEASE_PREFIX}}
- export RELEASE=${CI_COMMIT_TAG#${RELEASE_TAG_PREFIX}}
- export IMAGE_TAG=${RELEASE%%.*}
tag minor:
extends:
- .tag-docker-image
extends: .docker:image:tag
before_script:
- export RELEASE_PREFIX=${RELEASE_PREFIX:-release/}
- export RELEASE=${CI_COMMIT_TAG#${RELEASE_PREFIX}}
- export RELEASE=${CI_COMMIT_TAG#${RELEASE_TAG_PREFIX}}
- export IMAGE_TAG=${RELEASE%.${RELEASE##*.}}
tag stable:
extends: .tag-docker-image
extends: .docker:image:tag
variables:
IMAGE_TAG: stable
tag latest:
extends: .tag-docker-image
extends: .docker:image:tag
variables:
IMAGE_TAG: latest
......@@ -6,19 +6,113 @@
const branch = process.env.CI_COMMIT_REF_NAME;
const gitAssets = [];
// Configure your branches names
const stableBranch = 'master';
// Assign a branch name to produce a `beta` prerelease tag
const betaBranch = 'testing';
// Assign a branch name to produce a `alpha` prerelease tag
const alphaBranch = undefined;
// Assign a branch name to produce a `dev` prerelease tag
const devBranch = undefined;
// Configure semantic-release plugins
const breakingKeywords = ["BREAKING CHANGE", "BREAKING-CHANGE", "BREAKING CHANGES", "BREAKING-CHANGES"];
const changelogFile = 'docs/CHANGELOG.md';
// Configure `conventionalcommits`
// See:
// - https://github.com/conventional-changelog/conventional-changelog/issues/838
// - https://github.com/conventional-changelog/conventional-changelog/issues/317
// - https://github.com/conventional-changelog/conventional-changelog/pull/421
const commitTypes = [
{type: "build", section: "Build System", hidden: false},
{type: "chore", section: "Maintenance", hidden: true},
{type: "ci", section: "Continuous Integration", hidden: false},
{type: "docs", section: "Documentation", hidden: false},
{type: "feat", section: "Features", hidden: false},
{type: "fix", section: "Bug Fixes", hidden: false},
{type: "perf", section: "Performance Improvements", hidden: false},
{type: "refactor", section: "Code Refactoring", hidden: false},
{type: "revert", section: "Reverts", hidden: false},
{type: "style", section: "Styles", hidden: false},
{type: "test", section: "Tests", hidden: false},
];
const releaseRules = [
{ breaking: true, release: 'major' },
// { type: 'build', release: 'patch'},
// { type: 'chore', release: 'patch'},
// { type: 'ci', release: 'patch'},
{ type: 'docs', release: 'patch' },
{ type: 'feat', release: 'minor' },
{ type: 'fix', release: 'patch' },
{ type: 'perf', release: 'patch' },
{ type: 'refactor', release: 'patch' },
{ type: 'revert', release: 'patch' },
{ type: 'style', release: 'patch' },
{ type: 'test', release: 'patch' },
];
const semanticBranches = [stableBranch];
if (betaBranch) {
semanticBranches.push({
name: betaBranch,
prerelease: true
});
}
if (alphaBranch) {
semanticBranches.push({
name: alphaBranch,
prerelease: true
});
}
if (devBranch) {
semanticBranches.push({
name: devBranch,
prerelease: true
});
}
const config = {
branches: 'master',
branches: semanticBranches,
/* eslint no-template-curly-in-string: "off" */
tagFormat: 'release/${version}',
plugins: [
[
'@semantic-release/commit-analyzer',
{
preset: 'angular',
releaseRules: './release-rules.js',
preset: 'conventionalcommits',
parserOpts:
{
noteKeywords: breakingKeywords,
},
releaseRules: releaseRules,
presetConfig:
{
types: commitTypes,
},
},
],
[
'@semantic-release/release-notes-generator',
{
preset: 'conventionalcommits',
parserOpts:
{
noteKeywords: breakingKeywords,
},
presetConfig:
{
types: commitTypes,
},
},
],
'@semantic-release/release-notes-generator',
],
};
......@@ -30,11 +124,11 @@ if (
config.plugins.push([
'@semantic-release/changelog',
{
changelogFile: 'docs/CHANGELOG.md',
changelogFile: changelogFile,
changelogTitle: '# Changelog',
},
]);
gitAssets.push('docs/CHANGELOG.md');
gitAssets.push(changelogFile);
}
// We need to update package*.json
......@@ -63,89 +157,4 @@ config.plugins.push(
'@semantic-release/gitlab',
);
config.generateNotes = {
preset: 'angular',
writerOpts: {
// Required due to upstream bug preventing all types being displayed.
// Bug: https://github.com/conventional-changelog/conventional-changelog/issues/317
// Fix: https://github.com/conventional-changelog/conventional-changelog/pull/410
/* eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["commit", "note"] }] */
transform: (commit, context) => {
const issues = [];
commit.notes.forEach((note) => {
note.title = `BREAKING CHANGES`;
});
// NOTE: Any changes here must be reflected in `CONTRIBUTING.md`.
if (commit.type === `feat`) {
commit.type = `Features`;
} else if (commit.type === `fix`) {
commit.type = `Bug Fixes`;
} else if (commit.type === `perf`) {
commit.type = `Performance Improvements`;
} else if (commit.type === `revert`) {
commit.type = `Reverts`;
} else if (commit.type === `docs`) {
commit.type = `Documentation`;
} else if (commit.type === `style`) {
commit.type = `Styles`;
} else if (commit.type === `refactor`) {
commit.type = `Code Refactoring`;
} else if (commit.type === `test`) {
commit.type = `Tests`;
} else if (commit.type === `build`) {
commit.type = `Build System`;
// } else if (commit.type === `chore`) {
// commit.type = `Maintenance`
} else if (commit.type === `ci`) {
commit.type = `Continuous Integration`;
} else {
return null;
}
if (commit.scope === `*`) {
commit.scope = ``;
}
if (typeof commit.hash === `string`) {
commit.shortHash = commit.hash.substring(0, 7);
}
if (typeof commit.subject === `string`) {
let url = context.repository ? `${context.host}/${context.owner}/${context.repository}` : context.repoUrl;
if (url) {
url = `${url}/issues/`;
// Issue URLs.
commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {
issues.push(issue);
return `[#${issue}](${url}${issue})`;
});
}
if (context.host) {
// User URLs.
commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => {
if (username.includes('/')) {
return `@${username}`;
}
return `[@${username}](${context.host}/${username})`;
});
}
}
// remove references that already appear in the subject
commit.references = commit.references.filter((reference) => {
if (issues.indexOf(reference.issue) === -1) {
return true;
}
return false;
});
return commit;
},
},
};
module.exports = config;
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.1
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.7.3
# Copy app package.json and package-lock.json into container
COPY ./app/package*.json $APP_SOURCE_FOLDER/
......@@ -13,7 +13,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-bundle.sh
# Rather than Node 8 latest (Alpine), you can also use the specific version of Node expected by your Meteor release, per https://docs.meteor.com/changelog.html
FROM hub.eole.education/proxyhub/library/node:14.19.1-alpine
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......@@ -36,7 +36,7 @@ RUN bash $SCRIPTS_FOLDER/build-meteor-npm-dependencies.sh --build-from-source
# Start another Docker stage, so that the final image doesn’t contain the layer with the build dependencies
# See previous FROM line; this must match
FROM hub.eole.education/proxyhub/library/node:14.19.1-alpine
FROM hub.eole.education/proxyhub/library/node:14.19.3-alpine
ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker
......
# L'application **Agenda** environnement DEV :
- [Installation](#installation)
- [Application : Laboite](#application-laboite)
- [Application : Agenda](#application-agenda)
- [Paramètres](#paramètres)
- [Lancer le projet](#lancer-le-projet)
- [Dans un terminal **laboite**](#dans-un-terminal-laboite)
- [Lancer un autre terminal **agenda**](#lancer-un-autre-terminal-agenda)
- [Ajouter des groupes à votre utilisateur](#ajouter-des-groupes-à-votre-utilisateur)
- [Via l'interface utilisateur **localhost:3000**](#via-linterface-utilisateur-localhost3000)
---
## Installation
### Application : Laboite
Procédure d'installation :
```
git clone https://gitlab.mim-libre.fr/alphabet/laboite.git
cd laboite
cp config/settings.development.json.sample config/settings.development.json
cd app
meteor npm install
```
### Application : Agenda
Procédure d'installation :
```
git clone https://gitlab.mim-libre.fr/alphabet/agenda.git
cd agenda
cp config/settings.development.json.sample config/settings.development.json
cd app
meteor npm install
```
### Paramètres
Pour le fonctionnement de l'**Agenda** en local, il faut configurer une instance locale de **LaBoite** avec authentification sur un serveur Keycloak. Ajouter au moins une clé d'API dans la variable `private:apiKeys`.
Se reporter au [document relatif à la configuration](config/LISEZ-MOI.md).
## Lancer le projet
### Dans un terminal **laboite**
```
cd laboite/app
meteor npm start
```
Il est possible de vérifier le fonctionnement de la boite en tapant la ligne suivante à partir d'un navigateur.
```
http://localhost:3000
```
### Lancer un autre terminal **agenda**
```
cd agenda/app
meteor npm start
```
A partir du navigateur, tapez ceci :
```
http://localhost:3030
```
### Ajouter des groupes à votre utilisateur
#### Via l'interface utilisateur **localhost:3000**
À partir de l'application `laboite` que vous accédez à partir du navigateur
```
http://localhost:3000
```
Aller dans le fichier de config de la boîte ./config/settings.development.json
Modifier l'attribut : "whiteDomains" en fonction de votre mail user
Exemple :
Pour un mailUser = 'toto@gmail.com', il faudra ajouter "^gmail.com"
Ce qui donnerait :
"whiteDomains": [
"^ac-[a-z-]\\.fr",
"^[a-z-]\\.gouv.fr",
"^gmail.com"
]
Relancer la boite
Naviguez sur `http://localhost:3000` (créez votre utilisateur dans keycloak en suivant le lien proposé sur la page d'authentification).
En allant dans l'onglet "Groupes", vous pouvez "Rejoindre le groupe" automatiquement pour tous les groupes en bleu.
Rafraîchir la page **Agenda** du navigateur
```
http://localhost:3030
```
# L'application **agenda** environnement DEV :
# The DEV environment **Agenda** application :
Sommaire :
- [Install](#install)
- [Application : Laboite](#application-laboite)
- [Application : Agenda](#application-agenda)
- [Parameters](#parameters)
- [Run project](#run-project)
- [In a terminal **laboite**](#in-a-terminal-laboite)
- [Run an other terminal **agenda**](#run-an-other-terminal-agenda)
- [Add groups to your user](#add-groups-to-your-user)
- [In user interface **localhost:3000**](#in-user-interface-localhost3000)
- Installation
- Application : la boite
- Application : Agenda
- Paramètres
- Lancer le projet
- Dans un terminal **la boite**
- Lancer un autre terminal **agenda**
- Ajouter des groupes à votre utilisateur
- Via l'interface utilisateur **localhost:3000**
---
## Installation
## Install
### Application : la boite
### Application : Laboite
Procédure d'nstallation :
Install process :
```
git clone https://gitlab.mim-libre.fr/alphabet/laboite
git clone https://gitlab.mim-libre.fr/alphabet/laboite.git
cd laboite
cp config/settings.development.json.sample config/settings.development.json
cd app
npm install
meteor npm install
```
### Application : Agenda
Procédure d'nstallation :
Install process :
```
git clone https://gitlab.mim-libre.fr/alphabet/apps-agenda.git
cd apps-agenda
git clone https://gitlab.mim-libre.fr/alphabet/agenda.git
cd agenda
npm install
cp config/settings.development.json.sample config/settings.development.json
cd app
meteor npm install
```
### Paramètres
### Parameters
Pour le fonctionnement de l'**agenda** en local, il faut configurer une instance locale de **LaBoite**' avec authentification sur un serveur Keycloak. Ajouter au moins une clé d'API dans la variable private:apiKeys.
To run **Agenda** locally, you need to configure a **LaBoite**' local instance with authentication on a Keycloak server. Add at least one API key in the private:apiKeys variable. See [configuration document for more informations](config/README.md).
## Lancer le projet
## Run project
### Dans un terminal **la boite**
### In a terminal **laboite**
```
cd laboite/app
npm start
meteor npm start
```
Il est possible de vérifier le fonctionnement de la boite en tapant la ligne suivante à partir d'un navigateur
It is possible to check the operation of the box by typing the following line from an web browser
```
http://localhost:3000
```
### lancer un autre terminal **agenda**
### Run an other terminal **agenda**
```
cd agenda
npm start
cd agenda/app
meteor npm start
```
copier le fichier config/settings.development.sample.json sur config/settings.development.json et le remplir de cette façon :
- public:"agendaUrl": "http://localhost:3030",
- public:"laboiteUrl": "http://localhost:3000"
- public:"enableKeycloak": true
Reprendre les paramètre de l'instance LaBoite pour les variables suivantes :
- public:"keycloakUrl"
- public:"keycloakRealm"
- keycloak:"pubkey"
- keycloak:"client"
- private:"apiKeys" (nécessaire pour tester l'envoi de notification à LaBoite)
A partir du navigateur, tapez ceci :
From the browser, type this :
```
http://localhost:3030
```
### Ajouter des groupes à votre utilisateur
### Add groups to your user
#### Via l'interface utilisateur **localhost:3000**
#### In user interface **localhost:3000**
A partir de l'appliation `laboite`que vous accédez à partir du navigateur
From the `LaBoite` app that you access from the browser
```
http://localhost:3000
```
Aller dans le fichier de config de la boîte ./config/settings.development.json
Go to the config file in LaBoite ./config/settings.development.json
Modifier l'attribut : "whiteDomains" en fonction de votre mail user
Change the attribute : "whiteDomains" according to your mail provider.
Exemple :
Pour un mailUser = 'toto@gmail.com', il faudra ajouter "^gmail.com"
For mailUser = 'toto@gmail.com', you must add "^gmail.com"
Ce qui donnerait :
which would give :
"whiteDomains": [
"^ac-[a-z-]\\.fr",
......@@ -109,13 +96,13 @@ Ce qui donnerait :
"^gmail.com"
]
Relancer la boite
Re run la boite
Naviguez sur `http://localhost:3000` (créez vôtre utilisateur dans keycloak en suivant le lien proposé sur la page d'authentification).
Go on `http://localhost:3000` (create your user in keycloak by following the link on the authentication page).
En allant dans l'onglet "Groupes", vous pouvez "Rejoindre le groupe" automatiquement pour tous les groupes en bleu
By going to the "Groups" tab, you can "Join Group" automatically for all groups in blue.
Raffraichir la page **Agenda** du navigateur
Refresh the page **Agenda** of browser
```
http://localhost:3030
......
......@@ -6,7 +6,7 @@
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.14.6 # The database Meteor supports right now
mongo@1.15.0 # The database Meteor supports right now
reactive-var@1.0.11 # Reactive variable for tracker
standard-minifier-css@1.8.1 # CSS minifier run for production mode
......
accounts-base@2.2.2
accounts-base@2.2.3
accounts-oauth@1.4.1
accounts-password@2.3.1
aldeed:collection2@3.5.0
......@@ -6,10 +6,10 @@ aldeed:schema-index@3.0.0
allow-deny@1.1.1
autoupdate@1.8.0
babel-compiler@7.9.0
babel-runtime@1.5.0
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.2
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
......@@ -37,7 +37,7 @@ fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
hot-module-replacement@0.5.1
html-tools@1.1.2
html-tools@1.1.3
htmljs@1.1.1
http@2.0.0
hwillson:stub-collections@1.0.9
......@@ -60,12 +60,12 @@ minifier-js@2.7.4
minimongo@1.8.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modern-browsers@0.1.8
modules@0.18.0
modules-runtime@0.13.0
modules-runtime-hot@0.14.0
mongo@1.14.6
mongo-decimal@0.1.2
mongo@1.15.0
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.3.1
......@@ -77,7 +77,7 @@ raix:eventemitter@1.0.0
random@1.2.0
rate-limit@1.0.9
react-fast-refresh@0.2.3
react-meteor-data@2.4.0
react-meteor-data@2.5.1
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
......@@ -85,12 +85,12 @@ routepolicy@1.1.1
service-configuration@1.3.0
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.4.0
spacebars-compiler@1.3.0
socket-stream-client@0.5.0
spacebars-compiler@1.3.1
standard-minifier-css@1.8.1
standard-minifier-js@2.8.0
static-html@1.3.2
templating-tools@1.2.1
templating-tools@1.2.2
tmeasday:check-npm-versions@1.0.2
tracker@1.2.0
typescript@4.5.4
......
......@@ -3,6 +3,10 @@
<link rel="icon" type="image/png" href="/images/puce_eole.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Online diary" />
<meta name="author" content="EOLE/PCLL <team@eole.education> - DINUM" />
<meta name="licence" content="EUPL-1.2" />
</head>
<body>
......
......@@ -107,7 +107,9 @@
},
"MainMenu": {
"menuLogoutLabel": "Logout",
"menuAddEvent": "Add event"
"menuAddEvent": "Add event",
"copyCaldavUrl": "My Caldav URL",
"successCopyCaldav": "URL copied successfully"
},
"AppVersion": {
"title": "Version"
......
......@@ -107,7 +107,9 @@
},
"MainMenu": {
"menuLogoutLabel": "Se déconnecter",
"menuAddEvent": "Ajouter événement"
"menuAddEvent": "Ajouter événement",
"copyCaldavUrl": "Mon URL Caldav",
"successCopyCaldav": "Url copié avec succès"
},
"AppVersion": {
"title": "Version"
......
......@@ -22,9 +22,21 @@ const useStyles = makeStyles(() => ({
},
}));
const GroupsSelector = ({ stateHook: [state, setState], errors }) => {
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({
......@@ -32,9 +44,9 @@ const GroupsSelector = ({ stateHook: [state, setState], errors }) => {
});
};
const handleDelete = (groupId) => {
const handleDelete = (idGroup) => {
setState({
groups: [...state.groups.filter((g) => g._id !== groupId)],
groups: [...state.groups.filter((g) => g._id !== idGroup)],
});
};
......@@ -87,4 +99,5 @@ export default GroupsSelector;
GroupsSelector.propTypes = {
stateHook: PropTypes.arrayOf(PropTypes.any).isRequired,
errors: PropTypes.objectOf(PropTypes.any).isRequired,
groupId: PropTypes.objectOf(PropTypes.String).isRequired,
};
......@@ -16,6 +16,7 @@ import Avatar from '@material-ui/core/Avatar';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import { validateEmail } from '../../../api/utils/functions';
import { useAppContext } from '../../contexts/context';
// import Groups from '../../../api/groups/groups';
const useStyles = makeStyles(() => ({
......@@ -34,15 +35,14 @@ const ParticipantsSelector = ({ stateHook: [state, setState], handleCheckBoxUser
const classes = useStyles();
const [search, setSearch] = useState('');
const [{ user, userId }] = useAppContext();
const searchResults = useTracker(() => {
const regex = new RegExp(search, 'i');
const exclude = state.participants.map(({ _id }) => _id);
Meteor.subscribe('users.search', { search, exclude });
return Meteor.users
.find({ _id: { $ne: Meteor.userId(), $nin: exclude }, 'emails.address': { $regex: regex } })
.fetch();
return Meteor.users.find({ _id: { $ne: userId, $nin: exclude }, 'emails.address': { $regex: regex } }).fetch();
});
// const searchAll = useTracker(() => {
// // Meteor.subscribe('users.searchAll');
......@@ -124,7 +124,7 @@ const ParticipantsSelector = ({ stateHook: [state, setState], handleCheckBoxUser
...state[key].filter((item) => (typeof item === 'object' ? item.email !== itemEmail : item !== itemEmail)),
],
});
if (Meteor.user().emails[0].address === itemEmail) {
if (user.emails[0].address === itemEmail) {
setState({
participateUserEvent: !state.participateUserEvent,
});
......@@ -185,7 +185,7 @@ const ParticipantsSelector = ({ stateHook: [state, setState], handleCheckBoxUser
</Grid>
<Grid item md={12} xs={12}>
{state.participants
.filter(({ _id, groupId }) => !groupId && _id !== Meteor.userId())
.filter(({ _id, groupId }) => !groupId && _id !== userId)
.map((part) => (
<Chip
key={part._id}
......
......@@ -81,7 +81,13 @@ const SingleGroupDisplay = ({ group, handleDelete, view, event }) => {
return (
<Chip
key={emails[0].address}
avatar={<Avatar alt={emails[0].address} src={avatar} />}
avatar={
<Avatar
alt={emails[0].address}
src={avatar}
style={{ backgroundColor: avatar ? 'rgba(255,255,255,0.7)' : '' }}
/>
}
label={emails[0].address}
className={`${classes.chip} ${
displayStatus ? (status() === 0 ? classes.errorChip : classes.successChip) : ''
......
......@@ -6,6 +6,7 @@ import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import Divider from '@material-ui/core/Divider';
import Tooltip from '@material-ui/core/Tooltip';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import PropTypes from 'prop-types';
......@@ -34,6 +35,16 @@ const MainMenu = ({ user = {} }) => {
const [openLogout, setOpenLogout] = useState(false);
const history = useHistory();
const { pathname } = useLocation();
const startCaldavUrl = Meteor.settings.public.caldavUrl;
const endCaldavUrl = `${user.username}/calendar.ics/`;
const userCaldavUrl = () => {
if (startCaldavUrl.charAt(startCaldavUrl.length - 1) === '/') {
return `${startCaldavUrl}${endCaldavUrl}`;
}
return `${startCaldavUrl}/${endCaldavUrl}`;
};
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleMenuClick = (path) => {
......@@ -77,6 +88,12 @@ const MainMenu = ({ user = {} }) => {
}
};
const handleCopyCaldavUrl = () => {
navigator.clipboard.writeText(userCaldavUrl());
closeLogoutDialog();
msg.success(i18n.__('components.MainMenu.successCopyCaldav'));
};
return (
<>
<Button
......@@ -119,6 +136,16 @@ const MainMenu = ({ user = {} }) => {
);
})}
<Divider />
{Meteor.settings.public.caldavUrl ? (
<>
<Tooltip title={userCaldavUrl()}>
<MenuItem onClick={handleCopyCaldavUrl}>
<T>copyCaldavUrl</T>
</MenuItem>
</Tooltip>
<Divider />
</>
) : null}
<MenuItem onClick={onLogout}>
<T>menuLogoutLabel</T>
......
......@@ -75,7 +75,14 @@ const Calendar = () => {
return (
<div className={classes.container}>
<input ref={inputRef} onChange={importICSToAgenda} className={classes.hidden} id="myfile" type="file" />
<input
ref={inputRef}
onChange={importICSToAgenda}
className={classes.hidden}
id="myfile"
type="file"
accept=".ics"
/>
<FullCalendar
locale={language}
timeZone="local"
......
......@@ -12,6 +12,7 @@ import EditEvent from '../pages/EditEvent';
import NotLoggedIn from '../pages/NotLoggedIn';
import Logout from '../pages/Logout';
import ROUTES from './routes';
import Spinner from '../components/system/Spinner';
import { useAppContext } from '../contexts/context';
import SiteInMaintenance from '../components/system/SiteInMaintenance';
import UserFailed from '../components/system/UserFailed';
......@@ -25,7 +26,7 @@ const useStyles = makeStyles((theme) => ({
const MainLayout = ({ userFailed, setUserFailed }) => {
const classes = useStyles();
const [{ userId, appsettings }] = useAppContext();
const [{ userId, appsettings, loading }] = useAppContext();
useEffect(() => {
if (userId) setUserFailed(false);
}, [userId]);
......@@ -39,16 +40,20 @@ const MainLayout = ({ userFailed, setUserFailed }) => {
userFailed ? (
<UserFailed />
) : userId ? (
<>
<Calendar />
<Switch>
<Route exact path={ROUTES.LOGOUT} component={Logout} />
<Route exact path={ROUTES.ADD_EVENT} component={AddEvent} />
<Route exact path={ROUTES.EVENT} component={ReadEvent} />
<Route exact path={ROUTES.EVENT_EDIT} component={EditEvent} />
<Redirect from="*" to={ROUTES.HOME} />
</Switch>
</>
loading ? (
<Spinner />
) : (
<>
<Calendar />
<Switch>
<Route exact path={ROUTES.LOGOUT} component={Logout} />
<Route exact path={ROUTES.ADD_EVENT} component={AddEvent} />
<Route exact path={ROUTES.EVENT} component={ReadEvent} />
<Route exact path={ROUTES.EVENT_EDIT} component={EditEvent} />
<Redirect from="*" to={ROUTES.HOME} />
</Switch>
</>
)
) : (
<Switch>
<Route exact path={ROUTES.LOGOUT} component={Logout} />
......
......@@ -13,6 +13,7 @@ const ROUTES = {
LOGOUT: '/logout',
ADD_EVENT: '/add-event',
ADD_EVENT_TO_DATE: ({ date, start, end }) => `/add-event?${queryStringMaker({ date, start, end })}`,
ADD_EVENT_TO_GROUP: ({ groupId }) => `/add-event?${queryStringMaker({ groupId })}`,
EVENT: '/event/:_id',
EVENT_MAKE: (id) => `/event/${id}`,
EVENT_EDIT: '/edit-event/:_id',
......
......@@ -11,12 +11,17 @@ import ModalWrapper from '../components/system/ModalWrapper';
import ROUTES from '../layouts/routes';
import { useErrors, initialState } from '../components/events/utils';
import InformationsForm from '../components/events/InformationsForm';
import { useAppContext } from '../contexts/context';
import ParticipantsSelector from '../components/events/ParticipantsSelector';
import GroupsSelector from '../components/events/GroupsSelector';
const AddEvent = ({ history }) => {
const goHome = () => history.push(ROUTES.HOME);
const [{ user, userId }] = useAppContext();
if (user === undefined) return null;
const { date, start, end } = useQuery();
const { groupId } = useQuery();
const [state, setState] = useObjectState(initialState);
const errors = useErrors(state);
const [isValid, setValidity] = useState(false);
......@@ -52,16 +57,13 @@ const AddEvent = ({ history }) => {
}, [date, start, end]);
useEffect(() => {
const organizerId = Meteor.userId();
const organizerId = userId;
if (
state.participateUserEvent === true &&
!state.participants.some((participant) => participant._id === organizerId)
) {
setState({
participants: [
...state.participants,
...[{ _id: organizerId, email: Meteor.user().emails[0].address, status: 2 }],
],
participants: [...state.participants, ...[{ _id: organizerId, email: user.emails[0].address, status: 2 }]],
});
} else if (
state.participateUserEvent === false &&
......@@ -84,7 +86,7 @@ const AddEvent = ({ history }) => {
try {
setLoading(true);
const organizerId = Meteor.userId();
const organizerId = userId;
const { groups, participants, endDate, startDate, endTime, startTime, daysOfWeek, ...rest } = state;
let allParticipants = [...participants];
let organizerGroup = '';
......@@ -105,12 +107,14 @@ const AddEvent = ({ history }) => {
})
.fetch();
allParticipants = [
...allParticipants.map((user) =>
user._id === Meteor.userId() && organizerGroup !== '' ? { ...user, groupId: organizerGroup } : user,
...allParticipants.map((participant) =>
participant._id === userId && organizerGroup !== ''
? { ...participant, groupId: organizerGroup }
: participant,
),
...users.map((user) => ({
email: user.emails[0].address,
_id: user._id,
...users.map((participant) => ({
email: participant.emails[0].address,
_id: participant._id,
groupId: _id,
})),
];
......@@ -167,7 +171,7 @@ const AddEvent = ({ history }) => {
]}
>
<InformationsForm errors={errors} stateHook={[state, setState]} />
<GroupsSelector errors={errors} stateHook={[state, setState]} />
<GroupsSelector errors={errors} stateHook={[state, setState]} groupId={groupId} />
<ParticipantsSelector stateHook={[state, setState]} handleCheckBoxUser={handleCheckBoxUser} />
</ModalWrapper>
);
......