diff --git a/package.json b/package.json index af2cae3..245f19e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@types/multer-s3": "^2.7.7", "@types/mustache": "^4.0.1", "@types/nodemailer": "^6.4.0", + "@types/papaparse": "^5.0.4", "@types/sanitize-html": "^1.23.3", "@types/twig": "^1.12.3", "aws-sdk": "^2.706.0", @@ -32,6 +33,7 @@ "mustache": "^4.0.1", "nanoid": "^3.1.10", "nodemailer": "^6.4.10", + "papaparse": "^5.2.0", "sanitize-html": "^1.27.1", "slugify": "^1.4.4", "ts-jest": "^26.1.2", diff --git a/src/EmailService.ts b/src/EmailService.ts index 2e8ce76..8e417c4 100644 --- a/src/EmailService.ts +++ b/src/EmailService.ts @@ -14,15 +14,18 @@ export default class EmailService { if (process.env.SMTP_HOST == undefined || process.env.SMTP_PORT == undefined) { throw new Error("Invalid SMTP config") } - const config: any = { + let config: any = { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT), - secure: process.env.SMTP_SECURE == 'true', - auth: { + secure: process.env.SMTP_SECURE == 'true' + } + if (process.env.SMTP_USERNAME !== undefined && process.env.SMTP_PASSWORD !== undefined) { + config.auth = { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD, } } + console.log(config) return nodemailer.createTransport(config) } @@ -54,8 +57,8 @@ export default class EmailService { const text: string = htmlToText.fromString(html, { wordwrap: 130 }) // for now replace every email by a predefined one - console.info(to + ' replaced') - to = "spamfree@matthieubessat.fr" + // console.info(to + ' replaced') + // to = "spamfree@matthieubessat.fr" subject += " - Forum des associations 2020" return new Promise((resolve, reject) => { @@ -81,11 +84,11 @@ export default class EmailService { } static getWebUiBaseUrl() { - return process.env.WEB_UI_URL === undefined ? "URL_NOT_FOUND" : process.env.WEB_UI_URL + return process.env.WEB_UI_URL === undefined ? "WEB_UI_URL_NOT_FOUND" : process.env.WEB_UI_URL } static getBaseUrl() { - return process.env.BASE_URL === undefined ? "URL_NOT_FOUND" : process.env.BASE_URL + return process.env.BASE_URL === undefined ? "BASE_URL_NOT_FOUND" : process.env.BASE_URL } static isMocked() { @@ -93,4 +96,8 @@ export default class EmailService { // CUSTOM DESACTIVATION PLEASE ENABLE IN PRODUCTION return process.env.MOCK_EMAIL === "true" } + + static getAdminAddress() { + return process.env.ADMIN_ADDRESS === undefined ? "ADMIN_ADDRESS_NOT_FOUND" : process.env.ADMIN_ADDRESS + } } \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 1165c11..cf514c7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -72,6 +72,7 @@ let main = async () => { .delete('/:id', AdminTagController.destroy) ) .use('/organizations', express.Router() + .post('/import', AdminOrganizationController.import) .get('/', AdminOrganizationController.getMany) .get('/:id', AdminOrganizationController.getOne) .post('', AdminOrganizationController.store) diff --git a/src/controllers/AdminOrganizationController.ts b/src/controllers/AdminOrganizationController.ts index 90d1940..4c085a1 100644 --- a/src/controllers/AdminOrganizationController.ts +++ b/src/controllers/AdminOrganizationController.ts @@ -18,6 +18,14 @@ export default class AdminOrganizationController { .catch(err => res.status(400).json({ success: false, errors: err })) } + static import(req: express.Request, res: express.Response) { + const data = req.body.csv + res.json({ + success: true, + data + }) + } + static store(req: express.Request, res: express.Response) { let body: any = { token: AdminOrganizationController.generateToken(), @@ -51,8 +59,8 @@ export default class AdminOrganizationController { extra.slug = slugify(req.body.name) } Organization.updateOne({ _id: req.params.id }, { - ...req.body, ...extra, + ...req.body, updatedAt: new Date() }) .then(data => res.json({ success: true, data })) @@ -179,7 +187,7 @@ export default class AdminOrganizationController { }).then(updateData => { EmailService.send( data.get('email'), - "Félicitation, vos changements ont été approvés et publiés !", + "Félicitations, vos changements ont été approuvés et publiés !", "published", { adminName: data.get('adminName'), diff --git a/src/controllers/DelegateController.ts b/src/controllers/DelegateController.ts index 12c0951..0cd1a09 100644 --- a/src/controllers/DelegateController.ts +++ b/src/controllers/DelegateController.ts @@ -54,9 +54,9 @@ export default class DelegateController { proposedVersion.gallery = [] } if (Array.isArray(organization.proposedVersion.gallery)) { - let toDeleteMedias = organization.proposedVersion.gallery.filter((media: any) => - // if a existing media is not in the new version we delete it AND in the published version - proposedVersion.gallery.filter((m: any) => m.key === media.key).length === 0) + let toDeleteMedias = organization.proposedVersion.gallery + .filter((media: any) => proposedVersion.gallery.filter((m: any) => m.key === media.key).length === 0) + .map((media: any) => media.key) MediaService.deleteMany(toDeleteMedias, 'galleryUpdated') } @@ -129,8 +129,8 @@ export default class DelegateController { // send email to the manager const organization: any = res.locals.organization EmailService.send( - organization.email, - "L'association \"" + organization.adminName + "\" demande à être vérifé", + EmailService.getAdminAddress(), + "L'association \"" + organization.adminName + "\" demande à être vérifié", "approval", { adminName: organization.adminName, diff --git a/src/controllers/PublicController.ts b/src/controllers/PublicController.ts index 60c84fd..d763d00 100644 --- a/src/controllers/PublicController.ts +++ b/src/controllers/PublicController.ts @@ -30,7 +30,7 @@ export default class PublicController { name: version.name, description: version.descriptionShort, thumbnail: version.thumbnail.location, - tag: version.tag._id, + tag: version.tag === null ? 'tag_not_found' : version.tag._id, slug: o.get('slug') } }) diff --git a/static/assets/js/organization.js b/static/assets/js/organization.js index c93cadb..1d0ea5b 100644 --- a/static/assets/js/organization.js +++ b/static/assets/js/organization.js @@ -1,3 +1,6 @@ +/** + * Schedule collapsable section animation + */ document.querySelectorAll('.schedule-category').forEach(node => { let opened = false let icon = node.querySelector('.schedule-category-collapse-icon') @@ -17,3 +20,25 @@ document.querySelectorAll('.schedule-category').forEach(node => { } }) +/** + * Gallery modal to view media in large + */ +let mediaModal = document.querySelector('#media-modal') +let mediaModalImage = document.querySelector('#media-modal-image') + +let openModal = url => { + mediaModal.style.visibility = 'visible' + mediaModal.style.opacity = 1 + let attr = document.createAttribute('src') + attr.value = url + mediaModalImage.attributes.setNamedItem(attr) + document.body.style.height = "100vh" + document.body.style.overflow = "hidden" +} + +let closeModal = () => { + mediaModal.style.visibility = 'hidden' + mediaModal.style.opacity = 0 + document.body.style.overflow = "initial" + document.body.style.height = "initial" +} diff --git a/static/assets/organization.css b/static/assets/organization.css index 3aa4ea7..18ed8fa 100644 --- a/static/assets/organization.css +++ b/static/assets/organization.css @@ -106,16 +106,10 @@ margin-top: .75em; width: 100%; height: 25em; - display: grid; - grid-template-columns: 1fr .5fr .5fr; - grid-template-rows: 1fr 1fr; grid-column-gap: .75em; grid-row-gap: .75em; - - /* Negative to cancel the grid gap */ - /* margin-bottom: -.75em; */ } .media-overlay { @@ -146,7 +140,45 @@ transform: scale(1.2); } -.media-container:first-of-type { +/** +* MOSAIC CASES +**/ +/* Mosaic level 1 */ +.mosaic-1 { + display: flex; + justify-content: center; +} +.mosaic-1 .media-container { + width: 70%; +} + +/* Mosaic level 2 */ +.mosaic-2 { + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; +} + +/* Mosaic level 3 */ +.mosaic-3 { + grid-template-columns: 1fr .5fr; + grid-template-rows: 1fr 1fr; +} +.mosaic-3 .media-container:first-of-type { + grid-row: 1 / span 2; +} + +/* Mosaic level 4 */ +.mosaic-4 { + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; +} + +/* With five medias */ +.mosaic-5 { + grid-template-columns: 1fr .5fr .5fr; + grid-template-rows: 1fr 1fr; +} +.mosaic-5 .media-container:first-of-type { grid-row: 1 / span 2; } @@ -168,6 +200,54 @@ background-position: center; } +/** +* Media modal +*/ +.media-modal-container { + position: absolute; + width: 100%; + height: 100vh; + z-index: 99; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, .80); + + display: flex; + justify-content: center; + align-items: center; + + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear,opacity 0.1s linear; +} + +.media-modal { + position: relative; + width: 50%; +} + +.media-modal img { + border-radius: 4px; + width: 100%; +} + +.media-close { + position: absolute; + width: 1.5em; + height: 1.5em; + right: -.75em; + top: -.75em; + color: white; + cursor: pointer; + opacity: 0.7; + transition: all 0.2s; +} + +.media-close:hover { + transform: scale(1.2); + opacity: 1; +} + /* ***************************************************************************** * SECTION @@ -513,11 +593,46 @@ section { .media-mosaic { height: 33em; + } + + /** + * MOSAIC CASES + */ + /* Mosaic level 1 */ + .mosaic-1 .media-container { + width: 100%; + } + + /* Mosaic level 2 */ + .mosaic-2, .mosaic-3 { + grid-template-columns: 1fr; + } + + /* Mosaic level 3 */ + .mosaic-3 { + grid-template-rows: 1fr 1fr 1fr; + } + .mosaic-3 .media-container:first-of-type { + grid-row: 1 / span 1; + } + + /* Mosaic level 4 */ + .mosaic-4 { + grid-template-rows: 1fr 1fr 1fr; + } + .mosaic-4 .media-container:first-of-type, .mosaic-4 .media-container:nth-of-type(2) { + grid-column: 1 / span 2; + } + .mosaic-4 .media-container:first-of-type { + grid-row: 1 / span 1; + } + + /* Mosaic level 5 */ + .mosaic-5 { grid-template-columns: .5fr .5fr; grid-template-rows: 1fr .5fr .5fr; } - - .media-container:first-of-type { + .mosaic-5 .media-container:first-of-type { grid-row: 1 / span 1; grid-column: 1 / span 2; } diff --git a/views/emails/approval.twig b/views/emails/approval.twig index ae5f3c3..b0bf3af 100644 --- a/views/emails/approval.twig +++ b/views/emails/approval.twig @@ -1,6 +1,6 @@
Bonjour,
-L'association "{{ adminName }}" vient de proposer de nouveau changements à propos de son association sur la platforme.
+L'association "{{ adminName }}" vient de proposer de nouveaux changements à propos de son association sur la plateforme.
L'email associé à cette association est {{ email }}
@@ -8,4 +8,4 @@Lien pour consulter les changements
-Coordialement
+Cordialement
diff --git a/views/emails/published.twig b/views/emails/published.twig index 8cd80c3..8b0e51e 100644 --- a/views/emails/published.twig +++ b/views/emails/published.twig @@ -1,13 +1,13 @@Bonjour,
-Nous vous informons que les changements qui ont été soumis à verification concernant l'association "{{ adminName }}" viennent d'être publiés. Félicitations ! +Nous vous informons que les changements qui ont été soumis à vérification concernant l'association "{{ adminName }}" viennent d'être publiés. Félicitations !
Nous vous invitons à consulter la page web générée à partir des informations que vous avez soumises : {{ link }}
-Coordialement
+Cordialement
{% include 'views/emails/delegateFooter.twig' %} diff --git a/views/emails/rejected.twig b/views/emails/rejected.twig index d282bef..e3dba1a 100644 --- a/views/emails/rejected.twig +++ b/views/emails/rejected.twig @@ -1,11 +1,11 @@Bonjour,
-Nous vous informons que les changements qui ont été soumis à verification concernant l'association "{{ adminName }}" ne peuvent pas en l'état être publiées. +Nous vous informons que les changements qui ont été soumis à vérification concernant l'association "{{ adminName }}" ne peuvent pas en l'état être publiées.
-La personne vérifiant ces modifications a laissé un message indiquant la raison pour laquelle ces modifications ne peuvent pas être publiés : +La personne vérifiant ces modifications a laissé un message indiquant la raison pour laquelle ces modifications ne peuvent pas être publiées :
@@ -15,13 +15,13 @@ La personne vérifiant ces modifications a laissé un message indiquant la raiso
-N'hésitez pas à corriger les informations et à resoumettre pour une nouvelle verification et peut être cette fois ci vous serez publié... +N'hésitez pas à corriger les informations et à resoumettre pour une nouvelle vérification et peut-être cette fois-ci vous serez publié....
Pour rappel, vous pouvez utiliser ce lien pour vous connecter sur votre interface : {{ link }}
-Coordialement
+Cordialement
{% include 'views/emails/delegateFooter.twig' %} diff --git a/views/emails/token.twig b/views/emails/token.twig index 1b43a37..431b981 100644 --- a/views/emails/token.twig +++ b/views/emails/token.twig @@ -1,15 +1,15 @@Bonjour,
-Ceci est un email automatique afin de communiquer le lien au gérant de l'association nommée "{{ adminName }}" dans le but de modifier les informations concernant cette association sur la platforme de espace condorcet. +Bienvenue ! Ceci est juste un petit email pour communiquer le lien de gestion de l'association "{{ adminName }}" dans le but de modifier les informations concernant cette association sur la platforme de espace condorcet.
-Vous pouvez utilisez ce lien pour vous connecter sur l'interface web permettant de modifier votre association sur la platforme : {{ link }} +Vous pouvez utiliser ce lien pour vous connecter sur l'interface web permettant de modifier votre association sur la platforme : {{ link }}
-Voici votre clée: {{ token }}
+Et voici votre clé: {{ token }}
-Coordialement
+Cordialement
{% include 'views/emails/delegateFooter.twig' %} diff --git a/views/organization.twig b/views/organization.twig index 35e2ed4..1ccca63 100644 --- a/views/organization.twig +++ b/views/organization.twig @@ -8,12 +8,7 @@