From 51208cad8d07f36a45317bcf5db9ea5728b1975c Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Jul 2020 20:32:42 +0000 Subject: [PATCH] update --- nodemon.json | 4 + presentation.md | 1 + src/app.ts | 13 +- .../AdminOrganizationController.ts | 90 ++++-- src/controllers/DelegateController.ts | 239 +++++++++++++-- src/controllers/MediaController.ts | 5 +- src/controllers/PublicController.ts | 66 +++- src/models/Organization.ts | 13 +- static/about.html | 39 --- static/assets/home.css | 15 +- static/assets/img/favicon.png | Bin 0 -> 4687 bytes static/assets/js/home.js | 31 +- static/assets/main.css | 17 +- static/assets/organization.css | 112 ++++++- static/fa_icons | 1 - static/home.html | 248 --------------- static/organization.html | 288 ------------------ views/about.html | 0 views/about.twig | 15 + views/base.twig | 50 ++- views/emails/rejected.twig | 4 + views/emails/token.twig | 4 +- views/home.twig | 104 ++++++- views/legals.twig | 43 +++ views/not-found.twig | 7 + views/organization.html | 0 views/organization.twig | 272 +++++++++++++++++ 27 files changed, 1003 insertions(+), 678 deletions(-) create mode 100644 nodemon.json create mode 100644 presentation.md delete mode 100644 static/about.html create mode 100644 static/assets/img/favicon.png delete mode 120000 static/fa_icons delete mode 100644 static/home.html delete mode 100644 static/organization.html delete mode 100644 views/about.html create mode 100644 views/about.twig create mode 100644 views/legals.twig create mode 100644 views/not-found.twig delete mode 100644 views/organization.html create mode 100644 views/organization.twig diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..f44c2de --- /dev/null +++ b/nodemon.json @@ -0,0 +1,4 @@ +{ + "verbose": false, + "ignore": ["views", "static"] +} \ No newline at end of file diff --git a/presentation.md b/presentation.md new file mode 100644 index 0000000..2a434f2 --- /dev/null +++ b/presentation.md @@ -0,0 +1 @@ +Potremmo impermutabile e lui carissime ignoranza, della nome noi sempre a prieghi in beato per. Noia quale lodato nella raccontare novella che. Nel dare modo in liberalita piaceri, alla impetrata il di e la novella manifestamente siamo, che che quali. \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 87619ed..35d17ac 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,6 +12,7 @@ import AdminAuthMiddleware from './middlewares/AdminAuthMiddleware' import DelegateAuthMiddleware from './middlewares/DelegateAuthMiddleware' import PublicController from './controllers/PublicController' import cors from 'cors' +import twig from 'twig' dotenv.config({ path: __dirname + '/../.env' @@ -21,6 +22,8 @@ const app: express.Application = express() const host: string = "0.0.0.0" const port: number = 8001 +twig.cache(false) + let main = async () => { mongoose.connection.on('error', err => { console.error(err) @@ -36,6 +39,10 @@ let main = async () => { console.log('> Connected to mongodb') }) + app.set("twig options", { + allow_async: true, + strict_variables: false + }) app.use(cors()) app.use(bodyParser.json()) @@ -46,6 +53,8 @@ let main = async () => { app.get('/', PublicController.home) app.get('/association/:slug', PublicController.organization) + app.get('/a-propos', PublicController.about) + app.get('/mentions-legales', PublicController.legals) app.get('/icon/:id', DefaultController.viewIcon) app.get('/email', DefaultController.sendEmail) @@ -79,6 +88,8 @@ let main = async () => { .put('/', DelegateController.update) .post('/submit', DelegateController.submit) .post('/thumbnail', DelegateController.uploadThumbnail()) + .post('/cover', DelegateController.uploadCover()) + .post('/medias', DelegateController.uploadMedias()) .delete('/', DelegateController.destroy) ) @@ -92,7 +103,7 @@ let main = async () => { */ app.post('/api/media', MediaController.uploadRoute()) - app.delete('/api/media/:key', MediaController.delete) + //app.delete('/api/media/:key', MediaController.delete) app.use(express.static('/apps/forum_server/static')) diff --git a/src/controllers/AdminOrganizationController.ts b/src/controllers/AdminOrganizationController.ts index 5d0d0b4..8ad2edc 100644 --- a/src/controllers/AdminOrganizationController.ts +++ b/src/controllers/AdminOrganizationController.ts @@ -4,6 +4,7 @@ import cryptoRandomString from 'crypto-random-string' import EmailService from '../EmailService' import slugify from 'slugify' import { Document } from 'mongoose' +import MediaService from '../MediaService' export default class AdminOrganizationController { static getMany(req: express.Request, res: express.Response) { @@ -22,22 +23,22 @@ export default class AdminOrganizationController { let body: any = { token: AdminOrganizationController.generateToken(), createdAt: new Date(), - slug: req.body.adminName === undefined ? undefined : slugify(req.body.adminName).toLowerCase(), - ...req.body - // ...{ - // proposedVersion: { - // name: '', - // descriptionShort: '', - // descriptionLong: '', - // contacts: [], - // schedule: [], - // pricing: [], - // tag: null, - // cover: null, - // gallery: [], - // thumbnail: null - // } - // } + slug: slugify(req.body.adminName).toLowerCase(), + ...req.body, + ...{ + proposedVersion: { + name: req.body.adminName + // descriptionShort: '', + // descriptionLong: '', + // contacts: [], + // schedule: [], + // pricing: [], + // tag: null, + // cover: null, + // gallery: [], + // thumbnail: null + } + } } Organization.create(body).then(data => { AdminOrganizationController.sendEmailTokenUniversal(data) @@ -75,16 +76,46 @@ export default class AdminOrganizationController { } static destroy(req: express.Request, res: express.Response) { - Organization.deleteOne({ _id: req.params.id }).then(data => { - let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0 - res.status(isSuccess ? 200 : 400).json({ - success: isSuccess, - data - }) - }).catch(err => res.status(400).json({ - success: false, - errors: err.errors - })) + Organization.findById(req.params.id).then(organization => { + Organization.deleteOne({ _id: req.params.id }).then(data => { + if (organization === null) { + return + } + // delete all media from this organization + let keys: string[] = [] + const proposedVersion: any = organization.get('proposedVersion') + if (proposedVersion.thumbnail !== undefined && proposedVersion.thumbnail !== null) { + keys.push(proposedVersion.thumbnail.key) + } + if (proposedVersion.cover !== undefined && proposedVersion.cover !== null) { + keys.push(proposedVersion.cover.key) + } + console.log(proposedVersion.gallery) + if (Array.isArray(proposedVersion.gallery)) { + keys = keys.concat(proposedVersion.gallery.map((m: any) => m.key)) + } + + keys.forEach((key: string) => { + if (key === undefined || key === null || key.length <= 2) { return } + console.log('> OrganizationDestroyMediaCleanup: Deleted ' + key) + MediaService.getS3().deleteObject({ + Bucket: MediaService.getBucket(), + Key: key + }, (err, _) => { + if (err !== null) { + console.error('> OrganizationDestroyMediaCleanup: Cannot delete a media from a organization which will be deleted') + console.log(err, err.stack) + } + }) + }) + + let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0 + res.status(isSuccess ? 200 : 400).json({ + success: isSuccess, + data + }) + }).catch(err => res.status(400).json({ success: false, errors: err.errors })) + }).catch(err => res.status(400).json({ success: false, errors: err })) } static sendEmailToken(req: express.Request, res: express.Response) { @@ -150,11 +181,16 @@ export default class AdminOrganizationController { } static sendEmailTokenUniversal(data: Document) { + const baseUrl = process.env.WEB_UI_URL === undefined ? "URL_NOT_FOUND" : process.env.WEB_UI_URL EmailService.send( data.get('email'), "Votre lien secret pour modifier votre association", "token", - { adminName: data.get('adminName'), token: data.get('token') } + { + adminName: data.get('adminName'), + token: data.get('token'), + link: baseUrl + '/delegate?delegateToken=' + data.get('token') + } ).then(() => { console.log('> A token email was sent') }).catch(() => { diff --git a/src/controllers/DelegateController.ts b/src/controllers/DelegateController.ts index 475aea3..b2a54cd 100644 --- a/src/controllers/DelegateController.ts +++ b/src/controllers/DelegateController.ts @@ -1,3 +1,4 @@ +import Tag from '../models/Tag' import Organization from '../models/Organization' import * as express from 'express' import EmailService from '../EmailService' @@ -9,7 +10,15 @@ import slugify from 'slugify' export default class DelegateController { static get(req: express.Request, res: express.Response) { - res.json({ success: true, data: res.locals.organization }) + Tag.find().then(tags => { + res.json({ + success: true, + data: { + organization: res.locals.organization, + tags + } + }) + }) } static test(req: express.Request, res: express.Response) { @@ -17,28 +26,92 @@ export default class DelegateController { } static update(req: express.Request, res: express.Response) { - if (res.locals.organization.validationState === 'pending') { + const organization: any = res.locals.organization + if (organization.validationState === 'pending') { return res.json({ success: false, errors: [{ code: 'update-forbidden', message: 'Organization cannot be updated while the validationState is set to "pending"' }] }) } - // only update proposedVersion - Organization.updateOne({ _id: res.locals.organization._id }, { - proposedVersion: req.body, - updatedAt: new Date() - }).then(data => { - res.json({ - success: true, - data, - body: req.body + + const next = (tag: any) => { + // only update proposedVersion + let proposedVersion: any = req.body + proposedVersion.tag = tag + proposedVersion.descriptionLong = proposedVersion.descriptionLong.replace(/\n/g, '') + + // validate contact.address + // validate all fields to not overflow + // validate the size of all the json, all the data recorded + + // manage medias + // delete media that are not used + if (!Array.isArray(proposedVersion.gallery)) { + proposedVersion.gallery = [] + } + if (Array.isArray(organization.proposedVersion.gallery)) { + organization.proposedVersion.gallery.forEach((media: any) => { + // if a existing media is not in the new version we delete it + if (proposedVersion.gallery.filter((m: any) => m.key === media.key).length === 0) { + console.log('> Old media cleanup: we must delete ', media) + MediaService.getS3().deleteObject({ + Bucket: MediaService.getBucket(), + Key: media.key + }, (err, _) => { + if (err !== null) { + console.error('> Cannot delete a old media element') + console.log(err, err.stack) + } + }) + } + }) + } + + // format schedule, pricing + if (!Array.isArray(proposedVersion.schedule)) { + proposedVersion.schedule = [] + } + if (!Array.isArray(proposedVersion.pricing)) { + proposedVersion.pricing = [] + } + + Organization.updateOne({ _id: organization._id }, { + proposedVersion, + updatedAt: new Date() + }).then(data => { + res.json({ + success: true, + data, + body: req.body + }) + }).catch(err => { + res.status(400).json({ + success: false, + errors: err.errors !== undefined ? err.errors : err + }) }) - }).catch(err => { - res.status(400).json({ - success: false, - errors: err.errors !== undefined ? err.errors : err - }) - }) + } + + if (req.body.tag !== undefined && req.body.tag !== null && req.body.tag.length > 2) { + // skip the tag part if the tag didn't changed + if ( + organization.proposedVersion.tag === undefined || + organization.proposedVersion.tag === null || + req.body.tag !== organization.proposedVersion.tag._id + ) { + // if the tag is defined, search the tag id + Tag.findById(req.body.tag).then(tag => { + next(tag) + }).catch(err => { + console.log(err) + res.status(400).json({ success: false, errors: err, _note: 'The tag id provided is invalid' }) + }) + } else { + next(organization.tag) + } + } else { + next(null) + } } static submit(req: express.Request, res: express.Response) { @@ -78,7 +151,6 @@ export default class DelegateController { } static uploadThumbnail(): express.RequestHandler[] { - // we upload the thumbnail return [ multer({ storage: multerS3({ @@ -88,14 +160,14 @@ export default class DelegateController { contentType: multerS3.AUTO_CONTENT_TYPE, key: (_: any, file: any, cb: any) => { console.log(file) - cb(null, Date.now().toString() + '_' + slugify(file.originalname)) + cb(null, Date.now().toString() + '_thumbnail') } }) }).single('file'), (req: express.Request, res: express.Response) => { // if the current thumbnail is defined AND the published thumnbnail is defined AND the current thumbnail is different from the published one // THEN we delete the current thumbnail - const proposedVersion: any = res.locals.organization.proposedVersion + let proposedVersion: any = res.locals.organization.proposedVersion const publishedVersion: any = res.locals.organization.publishedVersion if ( proposedVersion !== undefined && proposedVersion !== null && @@ -109,7 +181,7 @@ export default class DelegateController { Bucket: MediaService.getBucket(), Key: proposedVersion.thumbnail.key }, (err, _) => { - if (err !== null) { + if (err !== null) { console.error('> Cannot delete a old thumbnail') console.log(err, err.stack) } @@ -122,13 +194,134 @@ export default class DelegateController { contentType: req.file.contentType, // @ts-ignore location: req.file.location, + // @ts-ignore + size: req.file.size, + // @ts-ignore + originalFileName: req.file.originalname, type: 'thumbnail' } + proposedVersion = { ...res.locals.organization.proposedVersion, thumbnail } Organization.updateOne({ _id: res.locals.organization._id }, { - proposedVersion: { thumbnail }, + proposedVersion, updatedAt: new Date() }).then(data => { - res.json({ success: true, data, thumbnail }) + res.json({ success: true, data, thumbnail, proposedVersion }) + }).catch(err => { + res.status(400).json({ + success: false, + errors: err.errors !== undefined ? err.errors : err + }) + }) + } + ] + } + + static uploadCover(): express.RequestHandler[] { + return [ + multer({ + storage: multerS3({ + s3: MediaService.getS3(), + bucket: 'development-bucket', + acl: 'public-read', + contentType: multerS3.AUTO_CONTENT_TYPE, + key: (_: any, file: any, cb: any) => { + console.log(file) + cb(null, Date.now().toString() + '_cover') + } + }) + }).single('file'), + (req: express.Request, res: express.Response) => { + // if the current thumbnail is defined AND the published thumnbnail is defined AND the current thumbnail is different from the published one + // THEN we delete the current thumbnail + let proposedVersion: any = res.locals.organization.proposedVersion + const publishedVersion: any = res.locals.organization.publishedVersion + if ( + proposedVersion !== undefined && proposedVersion !== null && + proposedVersion.cover !== undefined && proposedVersion.cover !== null && + publishedVersion !== undefined && publishedVersion !== null && + publishedVersion.cover !== undefined && publishedVersion.cover !== null && + publishedVersion.cover.location !== proposedVersion.cover.location + ) { + console.log(' we must delete ', proposedVersion.cover) + MediaService.getS3().deleteObject({ + Bucket: MediaService.getBucket(), + Key: proposedVersion.cover.key + }, (err, _) => { + if (err !== null) { + console.error('> Cannot delete a old cover') + console.log(err, err.stack) + } + }) + } + const cover: any = { + // @ts-ignore + key: req.file.key, + // @ts-ignore + contentType: req.file.contentType, + // @ts-ignore + location: req.file.location, + // @ts-ignore + size: req.file.size, + // @ts-ignore + originalFileName: req.file.originalname, + type: 'cover' + } + proposedVersion = { ...res.locals.organization.proposedVersion, cover } + Organization.updateOne({ _id: res.locals.organization._id }, { + proposedVersion, + updatedAt: new Date() + }).then(data => { + res.json({ success: true, data, cover, proposedVersion }) + }).catch(err => { + res.status(400).json({ + success: false, + errors: err.errors !== undefined ? err.errors : err + }) + }) + } + ] + } + + static uploadMedias(): express.RequestHandler[] { + return [ + multer({ + storage: multerS3({ + s3: MediaService.getS3(), + bucket: 'development-bucket', + acl: 'public-read', + contentType: multerS3.AUTO_CONTENT_TYPE, + key: (_: any, file: any, cb: any) => { + console.log(file) + cb(null, Date.now().toString() + '_media') + } + }) + }).array('file'), + (req: express.Request, res: express.Response) => { + let proposedVersion: any = res.locals.organization.proposedVersion + + // @ts-ignore + req.files.forEach((file: any) => { + proposedVersion.gallery.push({ + key: file.key, + contentType: file.contentType, + location: file.location, + size: file.size, + type: 'image', + originalFileName: file.originalname + }) + }) + + Organization.updateOne({ _id: res.locals.organization._id }, { + proposedVersion, + updatedAt: new Date() + }).then(result => { + res.json({ + success: true, + data: { + result, + gallery: proposedVersion.gallery + } + }) }).catch(err => { res.status(400).json({ success: false, diff --git a/src/controllers/MediaController.ts b/src/controllers/MediaController.ts index c651baa..8eff1c2 100644 --- a/src/controllers/MediaController.ts +++ b/src/controllers/MediaController.ts @@ -16,7 +16,7 @@ export default class MediaController { contentType: multerS3.AUTO_CONTENT_TYPE, key: (_: any, file: any, cb: any) => { console.log(file) - cb(null, Date.now().toString() + '_' + slugify(file.originalname)) //use Date.now() for unique file keys + cb(null, Date.now().toString()) //use Date.now() for unique file keys } }) }).single('file'), @@ -25,9 +25,6 @@ export default class MediaController { } static upload(req: express.Request, res: express.Response) { - - - res.json({ success: true, data: { file: req.file } diff --git a/src/controllers/PublicController.ts b/src/controllers/PublicController.ts index 273f712..64b5a9b 100644 --- a/src/controllers/PublicController.ts +++ b/src/controllers/PublicController.ts @@ -7,24 +7,78 @@ import Mustache from 'mustache' import fs from 'fs' import { IconService, IconInterface } from '../IconService' import IORedis from 'ioredis' +import Tag from '../models/Tag' export default class PublicController { - static async home(req: express.Request, res: express.Response) { // let client: IORedis.Redis = RedisService.getClient() // await client.set('hello', 'world') // res.json({ // data: await client.get('hello') // }) - res.render('home.twig', { - message : "Hello World" + Tag.find().then(tags => { + Organization.find().then(organizations => { + res.render('home.twig', { + tags, + tagsJSON: JSON.stringify(tags), + organizationsJSON: JSON.stringify(organizations.map(o => { + const version = o.get('proposedVersion') + return { + name: version.name, + description: version.descriptionShort, + thumbnail: version.thumbnail.location, + tag: version.tag._id, + slug: o.get('slug') + } + })) + }) + }) }) } static async organization(req: express.Request, res: express.Response) { - res.render('index.twig', { - message : "Hello World" - }) + Organization.find({ slug: req.params.slug }).then(data => { + if (data.length === 0) { + res.status(404).render('not-found.twig') + } else { + const version = data[0].get('proposedVersion') + if (version.contacts !== null && version.contacts !== undefined) { + if (typeof version.contacts.address === 'string') { + version.contacts.address = version.contacts.address.split('\n') + } + if (typeof version.contacts.phone === 'string') { + let phone = version.contacts.phone + if (phone.indexOf('+33') === 0) { + phone = '0' + phone.substr(3) + } + let phoneSplit = '' + let partEnd = false + for (var i = 0; i < phone.length; i++) { + phoneSplit += phone.charAt(i) + if (partEnd === true) { + phoneSplit += ' ' + } + partEnd = !partEnd + } + version.contacts.phoneInt = "+33" + phone.substr(1) + version.contacts.phoneSplit = phoneSplit + } + } + console.log(version) + res.render('organization.twig', { + layout: 'standalone', + data: version + }) + } + }).catch(_ => res.status(404).render('not-found.twig')) + } + + static async about(req: express.Request, res: express.Response) { + res.render('about.twig') + } + + static async legals(req: express.Request, res: express.Response) { + res.render('legals.twig') } } \ No newline at end of file diff --git a/src/models/Organization.ts b/src/models/Organization.ts index 0cd41e5..4e99abb 100644 --- a/src/models/Organization.ts +++ b/src/models/Organization.ts @@ -29,11 +29,6 @@ class AllowedString extends mongoose.SchemaType { // @ts-ignore mongoose.Schema.Types['AllowedString'] = AllowedString -const ScheduleIntervalBoundary = { - hour: { type: Number }, - minute: { type: Number } -} - const Media = { location: { type: String }, type: { @@ -41,8 +36,10 @@ const Media = { name: 'MediaType', allowedValues: ['cover', 'thumbnail', 'video', 'image'] }, + originalFileName: { type: String }, key: { type: String }, - contentType: { type: String } + contentType: { type: String }, + size: { type: Number } // size of the file in bytes } const OrganizationVersion = { @@ -68,8 +65,8 @@ const OrganizationVersion = { description: { type: String }, when: [{ day: { type: String }, // (Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche) - from: ScheduleIntervalBoundary, - to: ScheduleIntervalBoundary + from: { type: String }, + to: { type: String } }] }], pricing: [{ diff --git a/static/about.html b/static/about.html deleted file mode 100644 index 81b2855..0000000 --- a/static/about.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - Home - - - - - - - - - -
-
-
- -
-
- - Espace Condorcet Centre Social - -

Forum des associations

-
- Cette année nous vous invitons à découvrir le forum virtuel des associations. -
-
-
-
-
-
-

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem alias sapiente neque? Dignissimos harum blanditiis fugiat eius alias nam repudiandae, et explicabo nihil eos, quos reprehenderit nobis aperiam quibusdam ad?

-
-
- - - \ No newline at end of file diff --git a/static/assets/home.css b/static/assets/home.css index 56e9499..235167c 100644 --- a/static/assets/home.css +++ b/static/assets/home.css @@ -17,7 +17,6 @@ .header-menu { display: flex; align-items: flex-end; - } .header-menu a { @@ -94,6 +93,11 @@ height: 1.2em; } +.nav-item svg { + width: .75em; + height: .75em; +} + .nav-icon { width: 3em; padding-left: .5em; @@ -114,7 +118,7 @@ .nav-title { display: flex; align-items: center; - opacity: 0.85; + opacity: 0.7; } .nav-access { @@ -194,6 +198,7 @@ } .card-content { + /* width: 100%; */ padding: 1.5em; display: flex; flex-direction: column; @@ -213,6 +218,11 @@ font-size: 1.4em; } +.card-icon svg { + width: 1em; + height: 1em; +} + .card-title { font-size: 1.6em; color: #B12008; @@ -316,4 +326,3 @@ margin: 0; } } - diff --git a/static/assets/img/favicon.png b/static/assets/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3f77dc215a7d90281077f3d1eb033bd437726cfb GIT binary patch literal 4687 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 RFfIT9002ovPDHLkV1nIA*bM*x literal 0 HcmV?d00001 diff --git a/static/assets/js/home.js b/static/assets/js/home.js index 5c2422d..b862ff7 100644 --- a/static/assets/js/home.js +++ b/static/assets/js/home.js @@ -10,6 +10,11 @@ let navContent = document.getElementById('nav-content') let mosaic = document.getElementById('mosaic') let mosaicHeader = document.getElementById('mosaic-header') +organizations = organizations.map(org => { + org.tag = tags.filter(t => t._id === org.tag)[0] + return org +}) + navEnabler.onclick = async () => { if (!navOpened) { // open the menu @@ -73,6 +78,14 @@ function renderNavItem(tag) { return navItem } +function setAttributes(node, attrs) { + for (var key in attrs) { + attr = document.createAttribute(key) + attr.value = attrs[key] + node.attributes.setNamedItem(attr) + } +} + function renderCard(organization) { let card = createEl('card') @@ -92,13 +105,19 @@ function renderCard(organization) { titleContainer.appendChild(title) let icon = createEl('card-icon') - let iconTag = createEl(organization.tag.icon, 'i') - icon.appendChild(iconTag) + icon.innerHTML = `` titleContainer.appendChild(icon) upperContent.appendChild(titleContainer) let description = createEl('card-description') - description.textContent = organization.descriptionShort + description.textContent = organization.description upperContent.appendChild(description) let link = createEl('card-link') @@ -109,6 +128,10 @@ function renderCard(organization) { content.appendChild(upperContent) content.appendChild(link) card.appendChild(content) + + card.onclick = () => { + window.location = aTag.href + } return card } @@ -129,7 +152,7 @@ function enableTag(node) { if (!all) { tagId = node.attributes['data-tag-id'].value } - let data = organizations.filter(orga => orga.tag.id === tagId || all) + let data = organizations.filter(orga => orga.tag._id === tagId || all) let cards = renderMosaic(data) if (currentCardContainer !== null) { mosaic.removeChild(currentCardContainer) diff --git a/static/assets/main.css b/static/assets/main.css index 8038f15..6312487 100644 --- a/static/assets/main.css +++ b/static/assets/main.css @@ -8,7 +8,7 @@ body { } .container { - width: 80%; + width: 60%; margin: 0 auto; } @@ -23,8 +23,21 @@ a:hover { color: #2980b9; } + +@media (max-width: 1600px) { + .container { + width: 70%; + } +} + +@media (max-width: 1500px) { + .container { + width: 80%; + } +} + @media (max-width: 900px) { .container { width: 92%; } -} \ No newline at end of file +} diff --git a/static/assets/organization.css b/static/assets/organization.css index 8420c56..982469a 100644 --- a/static/assets/organization.css +++ b/static/assets/organization.css @@ -23,13 +23,19 @@ color: #34495e; cursor: pointer; transition: all .2s; + text-decoration: none; } .return-icon { - font-size: 1.5em; + display: flex; + align-items: center; margin-right: .5em; } +.return-icon svg { + width: 1.3em; +} + .return:hover { opacity: 0.88; text-decoration: underline; @@ -140,7 +146,7 @@ transform: scale(1.2); } -.media-main { +.media-container:first-of-type { grid-row: 1 / span 2; } @@ -207,10 +213,29 @@ section { margin-bottom: .5em; } +.description p { + margin-top: .75em; +} .description p:last-child { margin-bottom: 0; } +.description h3 { + font-size: 1.4em; + margin-top: 1em; + margin-bottom: 0; +} + +.description h4 { + font-size: 1.2em; + margin-top: 1em; + margin-bottom: 0; +} + +.description h3, .description h4 { + opacity: 0.8; +} + /* ***************************************************************************** * SCHEDULE @@ -237,7 +262,7 @@ section { .schedule-category-collapse-icon { color: #B12008; - font-size: 1.5em; + width: 1.5em; transition: all 0.1s ease-out; } @@ -251,7 +276,6 @@ section { .schedule-category-days-container { border-radius: 4px; background-color: #ECF0F1; - width: 40; margin-left: 2em; margin-top: 1em; @@ -283,14 +307,14 @@ section { .pricing { display: flex; - justify-content: space-between; + justify-content: center; } .pricing-card { + max-width: 14em; height: 10em; width: 100%; - padding-top: 1.5em; - padding-bottom: 1.5em; + padding: 1.5em 1em; text-align: center; border-radius: 4px; display: flex; @@ -360,6 +384,10 @@ section { font-size: 1.5em; } +.contact-icon svg { + width: .7em; +} + .contact-content { text-align: right; } @@ -369,21 +397,45 @@ section { font-weight: bold; } +.external-link { + width: .8em; +} + +.email .contact-icon svg { + width: .9em; +} + +.website .contact-icon svg { + width: .9em; +} + .facebook .contact-icon { background-color: #3B5999; color: white; } +.facebook .contact-icon svg { + width: .5em; +} + .instagram .contact-icon { background:linear-gradient(45deg, #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d); color: white; } +.instagram .contact-icon svg { + width: .75em; +} + .twitter .contact-icon { background-color: #1DA1F2; color: white; } +.twitter .contact-icon svg { + width: .75em; +} + /* ***************************************************************************** * FOOTER @@ -412,6 +464,13 @@ section { } +@media (max-width: 1200px) { + .schedule-category-days-container { + margin-right: 25em; + } +} + + @media (max-width: 900px) { .cover-background { @@ -446,23 +505,40 @@ section { grid-template-columns: .5fr .5fr; grid-template-rows: 1fr .5fr .5fr; } - .media-main { + + .media-container:first-of-type { grid-row: 1 / span 1; grid-column: 1 / span 2; } + .schedule-category-header { + padding-left: 1em; + padding-right: 1em; + } + .schedule-category-days-container { margin-right: 5em; } - .pricing-card { - margin-right: .5em; + .pricing { + flex-direction: column; + align-items: center; } + .pricing-card { + margin-right: 0; + margin-bottom: 1em; + } + .contact { width: 100%; } + .contact-icon { + width: 1.5em; + height: 1.5em; + } + .contact-item { padding-left: 1em; padding-right: 1em; @@ -470,5 +546,21 @@ section { .contact-content { word-break: break-all; + font-size: .9em; + } + + .facebook .contact-content, .twitter .contact-content, .website .contact-content, .instagram .contact-content { + font-size: .8em; + } +} + +@media (max-width: 600px) { + .schedule-category-days-container { + margin-right: 1em; + padding-left: 1em; + padding-right: 1em; + } + .return-title { + display: none; } } \ No newline at end of file diff --git a/static/fa_icons b/static/fa_icons deleted file mode 120000 index ccdb5bc..0000000 --- a/static/fa_icons +++ /dev/null @@ -1 +0,0 @@ -/home/mbess/font_awesome_icons/ \ No newline at end of file diff --git a/static/home.html b/static/home.html deleted file mode 100644 index cd25905..0000000 --- a/static/home.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - Home - - - - - - - - - -
-
-
-
- -
-
- - Espace Condorcet Centre Social - -

Forum des associations

-
- Cette année nous vous invitons à découvrir le forum virtuel des associations. -
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/static/organization.html b/static/organization.html deleted file mode 100644 index 8db9c12..0000000 --- a/static/organization.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - Association - - - - - - - - -
-
-
-
-
Revenir à la liste
-
-
- Forum des associations -
-
-
-
-
-
-
-
-
-

- We Robot -

-

- Association de robotique -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Présentation

-
-
-
-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Officia minima aliquam corporis fugit repellat obcaecati consequatur cumque, dolore omnis et porro, sit iusto similique blanditiis vel, alias quam ducimus voluptates. -

-

- Unde neque adipisci et. Consequatur labore similique quia. Rerum nihil eius assumenda quae. Non vel sapiente omnis. Eum explicabo neque maxime sapiente et perspiciatis et. -

-

- Delectus unde inventore similique ut quo. Consequatur assumenda quaerat aliquid velit et corrupti. Laboriosam qui magnam culpa est amet nobis tenetur. Ducimus a sint ea. Expedita omnis libero ipsum dolor ipsam dolor beatae. -

-

- Qui vel sit expedita eum recusandae nemo. Facere quas dolor eum ut. Aut omnis et qui repellat nihil accusantium. Et vitae beatae ratione. Tenetur sit omnis sa -

-
-
-
-
-

Crénaux

-
-
-
-
-
-
- Création de robots super cool -
- -
-
-
-
-
- Lundi -
-
- 17:00 - 18:00 -
-
-
-
- Mardi -
-
- 14:00 - 15:00 -
-
-
-
- Samedi -
-
- 16:00 - 17:00 -
-
-
-
-
-
-
-
- Cours de programmation de gros expert -
- -
-
-
-
-
- Lundi -
-
- 17:00 - 18:00 -
-
-
-
- Mardi -
-
- 14:00 - 15:00 -
-
-
-
- Samedi -
-
- 16:00 - 17:00 -
-
-
-
-
-
-
-
-
-

Tarifs

-
-
-
-
-
- 12 € -
-
- Enfants -
-
- Facere quas dolor eum ut. -
-
-
-
- 12 € -
-
- Enfants -
-
- Facere quas dolor eum ut. -
-
-
-
- 12 € -
-
- Enfants -
-
- Facere quas dolor eum ut. -
-
-
-
- - -
-
-

Contact

-
-
-
-
-
- -
-
- Frank GITON -
-
-
-
- -
- -
-
-
- -
-
-

- 6, Rue qui fait des efforts -

-

- 27940 Gaillon -

-
-
-
-
- -
- -
-
-
- -
- -
- - - -
-
-
- - - - - - \ No newline at end of file diff --git a/views/about.html b/views/about.html deleted file mode 100644 index e69de29..0000000 diff --git a/views/about.twig b/views/about.twig new file mode 100644 index 0000000..50ae9a0 --- /dev/null +++ b/views/about.twig @@ -0,0 +1,15 @@ +{% extends "./base.twig" %} +{% block title %}A propos{% endblock %} +{% block content %} +
+

+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem alias sapiente neque? Dignissimos harum blanditiis fugiat eius alias nam repudiandae, et explicabo nihil eos, quos reprehenderit nobis aperiam quibusdam ad? +

+

+ Takimata dolores sanctus lorem dolor labore dolores lorem voluptua diam ipsum, at accusam sed tempor accusam ea clita et tempor. Duo kasd eirmod at amet sed sed sanctus sit, sed kasd eos dolore amet diam nonumy est ipsum diam, lorem tempor dolore sed tempor sed eos justo no amet. Est.Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem alias sapiente neque? Dignissimos harum blanditiis fugiat eius alias nam repudiandae, et explicabo nihil eos, quos reprehenderit nobis aperiam quibusdam ad? +

+

+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Autem alias sapiente neque? Dignissimos harum blanditiis fugiat eius alias nam repudiandae, et explicabo nihil eos, quos reprehenderit nobis aperiam quibusdam ad? +

+
+{% endblock %} diff --git a/views/base.twig b/views/base.twig index cea3613..93828a4 100644 --- a/views/base.twig +++ b/views/base.twig @@ -1,11 +1,55 @@ - {% block title %}{% endblock %} - Forum des associations 2020 + + + + + {% block title %}{% endblock %} | Forum des associations 2020 + + + + {% if layout is not defined %} + + {% endif %} + {% block head %}{% endblock %} -
{% block content %}{% endblock %}
- {% block script %}{% endblock %} + {% if layout is not defined %} +
+
+
+
+ +
+
+ + Espace Condorcet Centre Social + +

Forum des associations

+
+ Cette année nous vous invitons à découvrir le forum virtuel des associations. +
+
+
+ +
+
+
+
+ {% block content %}{% endblock %} +
+
+ {% else %} + {% block content %}{% endblock %} + {% endif %} + + {% block scripts %}{% endblock %} \ No newline at end of file diff --git a/views/emails/rejected.twig b/views/emails/rejected.twig index 4e8f159..7abc809 100644 --- a/views/emails/rejected.twig +++ b/views/emails/rejected.twig @@ -20,5 +20,9 @@ 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és...

+

+Pour rappel, vous pouvez utiliser ce lien pour vous connecter sur votre interface : {{ link }} +

+

Coordialement

diff --git a/views/emails/token.twig b/views/emails/token.twig index 4072109..0b5453c 100644 --- a/views/emails/token.twig +++ b/views/emails/token.twig @@ -4,7 +4,9 @@ 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.

-{{ link }} +

+Vous pouvez utilisez ce lien pour vous connecter sur l'interface web permettant de modifier votre association sur la platforme : {{ link }} +

Voici votre clée: {{ token }}

diff --git a/views/home.twig b/views/home.twig index 31cd237..319ee80 100644 --- a/views/home.twig +++ b/views/home.twig @@ -1,13 +1,97 @@ {% extends "./base.twig" %} -{% block title %}Index{% endblock %} -{% block head %} - -{% endblock %} +{% block title %}Accueil{% endblock %} +{% block head %}{% endblock %} {% block content %} -

Index

-

- Welcome on my awesome homepage. -

+ +
+
+
+
+{% endblock %} + +{% block scripts %} + + {% endblock %} \ No newline at end of file diff --git a/views/legals.twig b/views/legals.twig new file mode 100644 index 0000000..261d35e --- /dev/null +++ b/views/legals.twig @@ -0,0 +1,43 @@ +{% extends "./base.twig" %} +{% block title %}Informations légales{% endblock %} +{% block content %} +
+

Informations légales

+ +

Siège social

+ +

+ Espace Condorcet Centre Social
+ 12 rue Jean Moulin,
+ 27600 GAILLON +

+ +

Contact

+ +

+ Téléphone : 02 32 77 50 80
+ Fax : 02 32 77 50 99 +

+ +

Représentant légal

+ +

Liliane COQUET

+ + +

Immatriculation

+ +

+ Numéro SIREN : 338 248 206 000 25
+ + Code APE 88 99 B +

+ +

Hébergement

+ +

+ Scaleway
+ + ONLINE SAS BP 438 75366 PARIS CEDEX 08 FRANCE +

+
+{% endblock %} diff --git a/views/not-found.twig b/views/not-found.twig new file mode 100644 index 0000000..aa6857e --- /dev/null +++ b/views/not-found.twig @@ -0,0 +1,7 @@ +{% extends "./base.twig" %} +{% block title %}Page introuvable{% endblock %} +{% block content %} +
+

Page introuvable

+
+{% endblock %} diff --git a/views/organization.html b/views/organization.html deleted file mode 100644 index e69de29..0000000 diff --git a/views/organization.twig b/views/organization.twig new file mode 100644 index 0000000..35e2ed4 --- /dev/null +++ b/views/organization.twig @@ -0,0 +1,272 @@ +{% extends "./base.twig" %} +{% block title %}Association{% endblock %} +{% block head %} + +{% endblock %} +{% block content %} +
+
+ +
+ +
+
Revenir à la liste
+
+
+ Forum des associations +
+
+
+
+
+
+
+
+
+

+ {{ data.name }} +

+

+ +

+
+
+
+
+
+ {% for media in data.gallery %} +
+
+
+ {% endfor %} + {#
+
+
+
#} +
+ + {% if data.descriptionLong|length > 0 %} +
+
+

Présentation

+
+
+
+ {{ data.descriptionLong|raw }} +
+
+ {% endif %} + + {% if data.schedule|length > 0 %} +
+
+

Crénaux

+
+
+
+ {% for item in data.schedule %} +
+
+
+ {{ item.name }} +
+ +
+
+
+ {% for when in item.when %} +
+
+ {{ when.day }} +
+
+ {{ when.from }} - {{ when.to }} +
+
+ {% endfor %} +
+
+
+ {% endfor %} +
+
+ {% endif %} + + {% if data.pricing|length > 0 %} +
+
+

Tarifs

+
+
+
+ {% for item in data.pricing %} +
+
+ {{ item.priceLabel }} +
+
+ {{ item.name }} +
+
+ {{ item.description }} +
+
+ {% endfor %} +
+
+ {% endif %} + + {% if data.contacts is defined %} +
+
+

Contact

+
+
+
+ {% if data.contacts.person|length > 0 %} +
+
+ +
+
+ {{ data.contacts.person }} +
+
+ {% endif %} + {% if data.contacts.email|length > 0 %} + + {% endif %} + {% if data.contacts.address|length > 0 %} +
+
+ +
+
+ {% for line in data.contacts.address %} + {{ line }}
+ {% endfor %} +
+
+ {% endif %} + {% if data.contacts.phone|length > 0 %} +
+
+ +
+ +
+ {% endif %} + {% if data.contacts.website|length > 0 %} + + {% endif %} + {% if data.contacts.facebook|length > 0 %} + + {% endif %} + {% if data.contacts.instagram|length > 0 %} + + {% endif %} + {% if data.contacts.twitter|length > 0 %} + + {% endif %} +
+
+ {% endif %} +
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file