This commit is contained in:
root 2020-07-15 20:32:42 +00:00
parent 666943d4f1
commit 51208cad8d
27 changed files with 1003 additions and 678 deletions

4
nodemon.json Normal file
View file

@ -0,0 +1,4 @@
{
"verbose": false,
"ignore": ["views", "static"]
}

1
presentation.md Normal file
View file

@ -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.

View file

@ -12,6 +12,7 @@ import AdminAuthMiddleware from './middlewares/AdminAuthMiddleware'
import DelegateAuthMiddleware from './middlewares/DelegateAuthMiddleware' import DelegateAuthMiddleware from './middlewares/DelegateAuthMiddleware'
import PublicController from './controllers/PublicController' import PublicController from './controllers/PublicController'
import cors from 'cors' import cors from 'cors'
import twig from 'twig'
dotenv.config({ dotenv.config({
path: __dirname + '/../.env' path: __dirname + '/../.env'
@ -21,6 +22,8 @@ const app: express.Application = express()
const host: string = "0.0.0.0" const host: string = "0.0.0.0"
const port: number = 8001 const port: number = 8001
twig.cache(false)
let main = async () => { let main = async () => {
mongoose.connection.on('error', err => { mongoose.connection.on('error', err => {
console.error(err) console.error(err)
@ -36,6 +39,10 @@ let main = async () => {
console.log('> Connected to mongodb') console.log('> Connected to mongodb')
}) })
app.set("twig options", {
allow_async: true,
strict_variables: false
})
app.use(cors()) app.use(cors())
app.use(bodyParser.json()) app.use(bodyParser.json())
@ -46,6 +53,8 @@ let main = async () => {
app.get('/', PublicController.home) app.get('/', PublicController.home)
app.get('/association/:slug', PublicController.organization) 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('/icon/:id', DefaultController.viewIcon)
app.get('/email', DefaultController.sendEmail) app.get('/email', DefaultController.sendEmail)
@ -79,6 +88,8 @@ let main = async () => {
.put('/', DelegateController.update) .put('/', DelegateController.update)
.post('/submit', DelegateController.submit) .post('/submit', DelegateController.submit)
.post('/thumbnail', DelegateController.uploadThumbnail()) .post('/thumbnail', DelegateController.uploadThumbnail())
.post('/cover', DelegateController.uploadCover())
.post('/medias', DelegateController.uploadMedias())
.delete('/', DelegateController.destroy) .delete('/', DelegateController.destroy)
) )
@ -92,7 +103,7 @@ let main = async () => {
*/ */
app.post('/api/media', MediaController.uploadRoute()) 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')) app.use(express.static('/apps/forum_server/static'))

View file

@ -4,6 +4,7 @@ import cryptoRandomString from 'crypto-random-string'
import EmailService from '../EmailService' import EmailService from '../EmailService'
import slugify from 'slugify' import slugify from 'slugify'
import { Document } from 'mongoose' import { Document } from 'mongoose'
import MediaService from '../MediaService'
export default class AdminOrganizationController { export default class AdminOrganizationController {
static getMany(req: express.Request, res: express.Response) { static getMany(req: express.Request, res: express.Response) {
@ -22,22 +23,22 @@ export default class AdminOrganizationController {
let body: any = { let body: any = {
token: AdminOrganizationController.generateToken(), token: AdminOrganizationController.generateToken(),
createdAt: new Date(), createdAt: new Date(),
slug: req.body.adminName === undefined ? undefined : slugify(req.body.adminName).toLowerCase(), slug: slugify(req.body.adminName).toLowerCase(),
...req.body ...req.body,
// ...{ ...{
// proposedVersion: { proposedVersion: {
// name: '', name: req.body.adminName
// descriptionShort: '', // descriptionShort: '',
// descriptionLong: '', // descriptionLong: '',
// contacts: [], // contacts: [],
// schedule: [], // schedule: [],
// pricing: [], // pricing: [],
// tag: null, // tag: null,
// cover: null, // cover: null,
// gallery: [], // gallery: [],
// thumbnail: null // thumbnail: null
// } }
// } }
} }
Organization.create(body).then(data => { Organization.create(body).then(data => {
AdminOrganizationController.sendEmailTokenUniversal(data) AdminOrganizationController.sendEmailTokenUniversal(data)
@ -75,16 +76,46 @@ export default class AdminOrganizationController {
} }
static destroy(req: express.Request, res: express.Response) { static destroy(req: express.Request, res: express.Response) {
Organization.deleteOne({ _id: req.params.id }).then(data => { Organization.findById(req.params.id).then(organization => {
let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0 Organization.deleteOne({ _id: req.params.id }).then(data => {
res.status(isSuccess ? 200 : 400).json({ if (organization === null) {
success: isSuccess, return
data }
}) // delete all media from this organization
}).catch(err => res.status(400).json({ let keys: string[] = []
success: false, const proposedVersion: any = organization.get('proposedVersion')
errors: err.errors 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) { static sendEmailToken(req: express.Request, res: express.Response) {
@ -150,11 +181,16 @@ export default class AdminOrganizationController {
} }
static sendEmailTokenUniversal(data: Document) { static sendEmailTokenUniversal(data: Document) {
const baseUrl = process.env.WEB_UI_URL === undefined ? "URL_NOT_FOUND" : process.env.WEB_UI_URL
EmailService.send( EmailService.send(
data.get('email'), data.get('email'),
"Votre lien secret pour modifier votre association", "Votre lien secret pour modifier votre association",
"token", "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(() => { ).then(() => {
console.log('> A token email was sent') console.log('> A token email was sent')
}).catch(() => { }).catch(() => {

View file

@ -1,3 +1,4 @@
import Tag from '../models/Tag'
import Organization from '../models/Organization' import Organization from '../models/Organization'
import * as express from 'express' import * as express from 'express'
import EmailService from '../EmailService' import EmailService from '../EmailService'
@ -9,7 +10,15 @@ import slugify from 'slugify'
export default class DelegateController { export default class DelegateController {
static get(req: express.Request, res: express.Response) { 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) { static test(req: express.Request, res: express.Response) {
@ -17,28 +26,92 @@ export default class DelegateController {
} }
static update(req: express.Request, res: express.Response) { 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({ return res.json({
success: false, success: false,
errors: [{ code: 'update-forbidden', message: 'Organization cannot be updated while the validationState is set to "pending"' }] 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 }, { const next = (tag: any) => {
proposedVersion: req.body, // only update proposedVersion
updatedAt: new Date() let proposedVersion: any = req.body
}).then(data => { proposedVersion.tag = tag
res.json({ proposedVersion.descriptionLong = proposedVersion.descriptionLong.replace(/\n/g, '')
success: true,
data, // validate contact.address
body: req.body // 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, if (req.body.tag !== undefined && req.body.tag !== null && req.body.tag.length > 2) {
errors: err.errors !== undefined ? err.errors : err // 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) { static submit(req: express.Request, res: express.Response) {
@ -78,7 +151,6 @@ export default class DelegateController {
} }
static uploadThumbnail(): express.RequestHandler[] { static uploadThumbnail(): express.RequestHandler[] {
// we upload the thumbnail
return [ return [
multer({ multer({
storage: multerS3({ storage: multerS3({
@ -88,14 +160,14 @@ export default class DelegateController {
contentType: multerS3.AUTO_CONTENT_TYPE, contentType: multerS3.AUTO_CONTENT_TYPE,
key: (_: any, file: any, cb: any) => { key: (_: any, file: any, cb: any) => {
console.log(file) console.log(file)
cb(null, Date.now().toString() + '_' + slugify(file.originalname)) cb(null, Date.now().toString() + '_thumbnail')
} }
}) })
}).single('file'), }).single('file'),
(req: express.Request, res: express.Response) => { (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 // 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 // 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 const publishedVersion: any = res.locals.organization.publishedVersion
if ( if (
proposedVersion !== undefined && proposedVersion !== null && proposedVersion !== undefined && proposedVersion !== null &&
@ -109,7 +181,7 @@ export default class DelegateController {
Bucket: MediaService.getBucket(), Bucket: MediaService.getBucket(),
Key: proposedVersion.thumbnail.key Key: proposedVersion.thumbnail.key
}, (err, _) => { }, (err, _) => {
if (err !== null) { if (err !== null) {
console.error('> Cannot delete a old thumbnail') console.error('> Cannot delete a old thumbnail')
console.log(err, err.stack) console.log(err, err.stack)
} }
@ -122,13 +194,134 @@ export default class DelegateController {
contentType: req.file.contentType, contentType: req.file.contentType,
// @ts-ignore // @ts-ignore
location: req.file.location, location: req.file.location,
// @ts-ignore
size: req.file.size,
// @ts-ignore
originalFileName: req.file.originalname,
type: 'thumbnail' type: 'thumbnail'
} }
proposedVersion = { ...res.locals.organization.proposedVersion, thumbnail }
Organization.updateOne({ _id: res.locals.organization._id }, { Organization.updateOne({ _id: res.locals.organization._id }, {
proposedVersion: { thumbnail }, proposedVersion,
updatedAt: new Date() updatedAt: new Date()
}).then(data => { }).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 => { }).catch(err => {
res.status(400).json({ res.status(400).json({
success: false, success: false,

View file

@ -16,7 +16,7 @@ export default class MediaController {
contentType: multerS3.AUTO_CONTENT_TYPE, contentType: multerS3.AUTO_CONTENT_TYPE,
key: (_: any, file: any, cb: any) => { key: (_: any, file: any, cb: any) => {
console.log(file) 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'), }).single('file'),
@ -25,9 +25,6 @@ export default class MediaController {
} }
static upload(req: express.Request, res: express.Response) { static upload(req: express.Request, res: express.Response) {
res.json({ res.json({
success: true, success: true,
data: { file: req.file } data: { file: req.file }

View file

@ -7,24 +7,78 @@ import Mustache from 'mustache'
import fs from 'fs' import fs from 'fs'
import { IconService, IconInterface } from '../IconService' import { IconService, IconInterface } from '../IconService'
import IORedis from 'ioredis' import IORedis from 'ioredis'
import Tag from '../models/Tag'
export default class PublicController { export default class PublicController {
static async home(req: express.Request, res: express.Response) { static async home(req: express.Request, res: express.Response) {
// let client: IORedis.Redis = RedisService.getClient() // let client: IORedis.Redis = RedisService.getClient()
// await client.set('hello', 'world') // await client.set('hello', 'world')
// res.json({ // res.json({
// data: await client.get('hello') // data: await client.get('hello')
// }) // })
res.render('home.twig', { Tag.find().then(tags => {
message : "Hello World" 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) { static async organization(req: express.Request, res: express.Response) {
res.render('index.twig', { Organization.find({ slug: req.params.slug }).then(data => {
message : "Hello World" 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')
} }
} }

View file

@ -29,11 +29,6 @@ class AllowedString extends mongoose.SchemaType {
// @ts-ignore // @ts-ignore
mongoose.Schema.Types['AllowedString'] = AllowedString mongoose.Schema.Types['AllowedString'] = AllowedString
const ScheduleIntervalBoundary = {
hour: { type: Number },
minute: { type: Number }
}
const Media = { const Media = {
location: { type: String }, location: { type: String },
type: { type: {
@ -41,8 +36,10 @@ const Media = {
name: 'MediaType', name: 'MediaType',
allowedValues: ['cover', 'thumbnail', 'video', 'image'] allowedValues: ['cover', 'thumbnail', 'video', 'image']
}, },
originalFileName: { type: String },
key: { type: String }, key: { type: String },
contentType: { type: String } contentType: { type: String },
size: { type: Number } // size of the file in bytes
} }
const OrganizationVersion = { const OrganizationVersion = {
@ -68,8 +65,8 @@ const OrganizationVersion = {
description: { type: String }, description: { type: String },
when: [{ when: [{
day: { type: String }, // (Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche) day: { type: String }, // (Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche)
from: ScheduleIntervalBoundary, from: { type: String },
to: ScheduleIntervalBoundary to: { type: String }
}] }]
}], }],
pricing: [{ pricing: [{

View file

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<meta charset="UTF-8" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" />
<link rel="stylesheet" href="./assets/main.css" />
<link rel="stylesheet" href="./assets/home.css" />
<meta name="viewport" content="width=device-width">
</head>
<body>
<div class="header">
<div class="container header-container">
<div class="header-image">
<img src="assets/img/espace_condorcet_logo.jpg" />
</div>
<div class="header-content">
<a
href="https://www.espacecondorcet.org/"
class="header-sub-title">
Espace Condorcet Centre Social
</a>
<h1 class="header-title">Forum des associations</h1>
<div class="header-description">
Cette année nous vous invitons à découvrir le forum virtuel des associations.
</div>
</div>
</div>
</div>
<div class="container">
<div class="content">
<p>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?</p>
</div>
</div>
<script></script>
</body>
</html>

View file

@ -17,7 +17,6 @@
.header-menu { .header-menu {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
} }
.header-menu a { .header-menu a {
@ -94,6 +93,11 @@
height: 1.2em; height: 1.2em;
} }
.nav-item svg {
width: .75em;
height: .75em;
}
.nav-icon { .nav-icon {
width: 3em; width: 3em;
padding-left: .5em; padding-left: .5em;
@ -114,7 +118,7 @@
.nav-title { .nav-title {
display: flex; display: flex;
align-items: center; align-items: center;
opacity: 0.85; opacity: 0.7;
} }
.nav-access { .nav-access {
@ -194,6 +198,7 @@
} }
.card-content { .card-content {
/* width: 100%; */
padding: 1.5em; padding: 1.5em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -213,6 +218,11 @@
font-size: 1.4em; font-size: 1.4em;
} }
.card-icon svg {
width: 1em;
height: 1em;
}
.card-title { .card-title {
font-size: 1.6em; font-size: 1.6em;
color: #B12008; color: #B12008;
@ -316,4 +326,3 @@
margin: 0; margin: 0;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -10,6 +10,11 @@ let navContent = document.getElementById('nav-content')
let mosaic = document.getElementById('mosaic') let mosaic = document.getElementById('mosaic')
let mosaicHeader = document.getElementById('mosaic-header') 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 () => { navEnabler.onclick = async () => {
if (!navOpened) { if (!navOpened) {
// open the menu // open the menu
@ -73,6 +78,14 @@ function renderNavItem(tag) {
return navItem 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) { function renderCard(organization) {
let card = createEl('card') let card = createEl('card')
@ -92,13 +105,19 @@ function renderCard(organization) {
titleContainer.appendChild(title) titleContainer.appendChild(title)
let icon = createEl('card-icon') let icon = createEl('card-icon')
let iconTag = createEl(organization.tag.icon, 'i') icon.innerHTML = `<svg
icon.appendChild(iconTag) aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${organization.tag.icon.width} ${organization.tag.icon.height}">
<path fill="currentColor" d="${organization.tag.icon.path}"></path>
</svg>`
titleContainer.appendChild(icon) titleContainer.appendChild(icon)
upperContent.appendChild(titleContainer) upperContent.appendChild(titleContainer)
let description = createEl('card-description') let description = createEl('card-description')
description.textContent = organization.descriptionShort description.textContent = organization.description
upperContent.appendChild(description) upperContent.appendChild(description)
let link = createEl('card-link') let link = createEl('card-link')
@ -109,6 +128,10 @@ function renderCard(organization) {
content.appendChild(upperContent) content.appendChild(upperContent)
content.appendChild(link) content.appendChild(link)
card.appendChild(content) card.appendChild(content)
card.onclick = () => {
window.location = aTag.href
}
return card return card
} }
@ -129,7 +152,7 @@ function enableTag(node) {
if (!all) { if (!all) {
tagId = node.attributes['data-tag-id'].value 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) let cards = renderMosaic(data)
if (currentCardContainer !== null) { if (currentCardContainer !== null) {
mosaic.removeChild(currentCardContainer) mosaic.removeChild(currentCardContainer)

View file

@ -8,7 +8,7 @@ body {
} }
.container { .container {
width: 80%; width: 60%;
margin: 0 auto; margin: 0 auto;
} }
@ -23,8 +23,21 @@ a:hover {
color: #2980b9; color: #2980b9;
} }
@media (max-width: 1600px) {
.container {
width: 70%;
}
}
@media (max-width: 1500px) {
.container {
width: 80%;
}
}
@media (max-width: 900px) { @media (max-width: 900px) {
.container { .container {
width: 92%; width: 92%;
} }
} }

View file

@ -23,13 +23,19 @@
color: #34495e; color: #34495e;
cursor: pointer; cursor: pointer;
transition: all .2s; transition: all .2s;
text-decoration: none;
} }
.return-icon { .return-icon {
font-size: 1.5em; display: flex;
align-items: center;
margin-right: .5em; margin-right: .5em;
} }
.return-icon svg {
width: 1.3em;
}
.return:hover { .return:hover {
opacity: 0.88; opacity: 0.88;
text-decoration: underline; text-decoration: underline;
@ -140,7 +146,7 @@
transform: scale(1.2); transform: scale(1.2);
} }
.media-main { .media-container:first-of-type {
grid-row: 1 / span 2; grid-row: 1 / span 2;
} }
@ -207,10 +213,29 @@ section {
margin-bottom: .5em; margin-bottom: .5em;
} }
.description p {
margin-top: .75em;
}
.description p:last-child { .description p:last-child {
margin-bottom: 0; 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 * SCHEDULE
@ -237,7 +262,7 @@ section {
.schedule-category-collapse-icon { .schedule-category-collapse-icon {
color: #B12008; color: #B12008;
font-size: 1.5em; width: 1.5em;
transition: all 0.1s ease-out; transition: all 0.1s ease-out;
} }
@ -251,7 +276,6 @@ section {
.schedule-category-days-container { .schedule-category-days-container {
border-radius: 4px; border-radius: 4px;
background-color: #ECF0F1; background-color: #ECF0F1;
width: 40;
margin-left: 2em; margin-left: 2em;
margin-top: 1em; margin-top: 1em;
@ -283,14 +307,14 @@ section {
.pricing { .pricing {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
} }
.pricing-card { .pricing-card {
max-width: 14em;
height: 10em; height: 10em;
width: 100%; width: 100%;
padding-top: 1.5em; padding: 1.5em 1em;
padding-bottom: 1.5em;
text-align: center; text-align: center;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@ -360,6 +384,10 @@ section {
font-size: 1.5em; font-size: 1.5em;
} }
.contact-icon svg {
width: .7em;
}
.contact-content { .contact-content {
text-align: right; text-align: right;
} }
@ -369,21 +397,45 @@ section {
font-weight: bold; font-weight: bold;
} }
.external-link {
width: .8em;
}
.email .contact-icon svg {
width: .9em;
}
.website .contact-icon svg {
width: .9em;
}
.facebook .contact-icon { .facebook .contact-icon {
background-color: #3B5999; background-color: #3B5999;
color: white; color: white;
} }
.facebook .contact-icon svg {
width: .5em;
}
.instagram .contact-icon { .instagram .contact-icon {
background:linear-gradient(45deg, #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d); background:linear-gradient(45deg, #405de6, #5851db, #833ab4, #c13584, #e1306c, #fd1d1d);
color: white; color: white;
} }
.instagram .contact-icon svg {
width: .75em;
}
.twitter .contact-icon { .twitter .contact-icon {
background-color: #1DA1F2; background-color: #1DA1F2;
color: white; color: white;
} }
.twitter .contact-icon svg {
width: .75em;
}
/* ***************************************************************************** /* *****************************************************************************
* FOOTER * FOOTER
@ -412,6 +464,13 @@ section {
} }
@media (max-width: 1200px) {
.schedule-category-days-container {
margin-right: 25em;
}
}
@media (max-width: 900px) { @media (max-width: 900px) {
.cover-background { .cover-background {
@ -446,23 +505,40 @@ section {
grid-template-columns: .5fr .5fr; grid-template-columns: .5fr .5fr;
grid-template-rows: 1fr .5fr .5fr; grid-template-rows: 1fr .5fr .5fr;
} }
.media-main {
.media-container:first-of-type {
grid-row: 1 / span 1; grid-row: 1 / span 1;
grid-column: 1 / span 2; grid-column: 1 / span 2;
} }
.schedule-category-header {
padding-left: 1em;
padding-right: 1em;
}
.schedule-category-days-container { .schedule-category-days-container {
margin-right: 5em; margin-right: 5em;
} }
.pricing-card { .pricing {
margin-right: .5em; flex-direction: column;
align-items: center;
} }
.pricing-card {
margin-right: 0;
margin-bottom: 1em;
}
.contact { .contact {
width: 100%; width: 100%;
} }
.contact-icon {
width: 1.5em;
height: 1.5em;
}
.contact-item { .contact-item {
padding-left: 1em; padding-left: 1em;
padding-right: 1em; padding-right: 1em;
@ -470,5 +546,21 @@ section {
.contact-content { .contact-content {
word-break: break-all; 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;
} }
} }

View file

@ -1 +0,0 @@
/home/mbess/font_awesome_icons/

View file

@ -1,248 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<meta charset="UTF-8" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" />
<link rel="stylesheet" href="./assets/main.css" />
<link rel="stylesheet" href="./assets/home.css" />
<meta name="viewport" content="width=device-width">
</head>
<body>
<div class="header">
<div class="container header-container">
<div class="header-left">
<div class="header-image">
<img src="assets/img/espace_condorcet_logo.jpg" />
</div>
<div class="header-content">
<a
href="https://www.espacecondorcet.org/"
class="header-sub-title">
Espace Condorcet Centre Social
</a>
<h1 class="header-title">Forum des associations</h1>
<div class="header-description">
Cette année nous vous invitons à découvrir le forum virtuel des associations.
</div>
</div>
</div>
<div class="header-menu">
<a href="#">Mentions Légales</a>
<a href="#">A propos</a>
</div>
</div>
</div>
<div class="container">
<div class="content">
<div class="nav">
<div
id="nav-enabler"
class="nav-item nav-mobile-enabler">
<div class="nav-icon">
<i class="fas fa-ellipsis-h"></i>
</div>
<div class="nav-item-content">
<div id="nav-enabler-text" class="nav-title">
Afficher le menu
</div>
<div class="nav-access">
<i
id="nav-enabler-icon"
class="fas fa-chevron-down">
</i>
</div>
</div>
</div>
<div id="nav-content">
<div class="nav-item" id="nav-all">
<div class="nav-icon">
<i class="fas fa-ellipsis-h"></i>
</div>
<div class="nav-item-content">
<div class="nav-title">
Toutes
</div>
<div class="nav-access">
<i class="fas fa-chevron-right"></i>
</div>
</div>
</div>
<div class="nav-item" data-tag-id="culturel">
<div class="nav-icon">
<i class="fas fa-theater-masks"></i>
</div>
<div class="nav-item-content">
<div class="nav-title">
Culturelles
</div>
<div class="nav-access">
<i class="fas fa-chevron-right"></i>
</div>
</div>
</div>
<div class="nav-item" data-tag-id="music">
<div class="nav-icon">
<i class="fas fa-music"></i>
</div>
<div class="nav-item-content">
<div class="nav-title">
Danse et musique
</div>
<div class="nav-access">
<i class="fas fa-chevron-right"></i>
</div>
</div>
</div>
</div>
</div>
<div class="mosaic" id="mosaic">
<div class="mosaic-header" id="mosaic-header">
</div>
<div class="card-container">
<!--
<div class="card">
<div class="card-image">
<img src="./assets/img/cross.png" />
</div>
<div class="card-content">
<div>
<div class="card-title-container">
<h2 class="card-title">
Croix-rouge gaillonaise
</h2>
<div class="card-icon">
<i class="fas fa-hands-helping"></i>
</div>
</div>
<p class="card-description">
Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius
</p>
</div>
<div class="card-link">
<a href="#link">En savoir plus</a>
</div>
</div>
</div>
<div class="card">
<div class="card-image">
<img src="./assets/img/adam.png" />
</div>
<div class="card-content">
<div>
<div class="card-title-container">
<h2 class="card-title">
Association des amis de la musique
</h2>
<div class="card-icon">
<i class="fas fa-hands-helping"></i>
</div>
</div>
<p class="card-description">
Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius...
</p>
</div>
<div class="card-link">
<a href="#link">En savoir plus</a>
</div>
</div>
</div>
<div class="card">
<div class="card-image">
<img src="./assets/img/werobot.png" />
</div>
<div class="card-content">
<div>
<div class="card-title-container">
<h2 class="card-title">
We Robot
</h2>
<div class="card-icon">
<i class="fas fa-hands-helping"></i>
</div>
</div>
<p class="card-description">
Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius...
</p>
</div>
<div class="card-link">
<a href="#link">En savoir plus</a>
</div>
</div>
</div>
-->
</div>
</div>
</div>
</div>
<script>
let tags = [
{
"id": "culturel",
"icon": "fas fa-theater-masks",
"name": "Culturelles"
},
{
"id": "music",
"icon": "fas fa-music",
"name": "Danse et musique"
},
{
"id": "maritime",
"icon": "fas fa-anchor",
"name": "Maritime"
}
]
let organizations = [
{
"name": "Croix-rouge gaillonaise",
"slug": "croix-rouge",
"icon": "fas fa-hands-helping",
"thumbnail": "./assets/img/cross.png",
"descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
"tag": tags[0]
},
{
"name": "Association des amis de la musique",
"slug": "adam",
"thumbnail": "./assets/img/adam.png",
"descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
"tag": tags[1]
},
{
"name": "We Robot",
"slug": "werobot",
"thumbnail": "./assets/img/werobot.png",
"descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
"tag": tags[0]
},
{
"name": "Hello world",
"slug": "autre-assos",
"thumbnail": "./assets/img/cross.png",
"descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
"tag": tags[2]
},
// {
// "name": "Jean paul lel",
// "slug": "jean-paul-lel",
// "thumbnail": "https://randomuser.me/api/portraits/men/93.jpg",
// "descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
// "tag": tags[2]
// },
// {
// "name": "WOWO",
// "slug": "wowo",
// "thumbnail": "https://randomuser.me/api/portraits/men/84.jpg",
// "descriptionShort": "Qui totam quibusdam ut. Provident sint est inventore quod deleniti labore. Nemo voluptate deserunt in dolorem doloremque corrupti asperiores. Aut consequatur ea eum. Aut sint dolore numquam saepe qui earum officia eius",
// "tag": tags[0]
// }
]
</script>
<script src="assets/js/home.js"></script>
</body>
</html>

View file

@ -1,288 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Association</title>
<meta charset="UTF-8" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css" />
<link rel="stylesheet" href="./assets/main.css" />
<link rel="stylesheet" href="./assets/organization.css" />
<meta name="viewport" content="width=device-width">
</head>
<body>
<div class="header">
<div class="container header-container">
<div class="return">
<div class="return-icon"><i class="fas fa-chevron-circle-left"></i></div>
<div class="return-title">Revenir à la liste</div>
</div>
<div class="header-title">
Forum des associations
</div>
</div>
</div>
<div class="cover-background" style="background-image: url('https://s.werobot.fr/headerCover.jpeg')"></div>
<div class="cover">
<div class="cover-content container">
<div class="cover-image" style="background-image: url('./assets/img/werobot.png');">
</div>
<div class="cover-title-container">
<h1 class="cover-title">
We Robot
</h1>
<h4 class="cover-sub-title">
Association de robotique
</h4>
</div>
</div>
</div>
<div class="container">
<div class="media-mosaic">
<div class="media-container media-main">
<div class="media" style="background-image: url('assets/img/dummy-1.jpg')"></div>
</div>
<div class="media-container media-1-1">
<div class="media" style="background-image: url('assets/img/dummy-3.jpg')"></div>
</div>
<div class="media-container media-1-2">
<div class="media" style="background-image: url('assets/img/dummy-2.jpg')"></div>
<div class="media-overlay"><i class="fas fa-play-circle"></i></div>
</div>
<div class="media-container media-2-1">
<div class="media" style="background-image: url('assets/img/dummy-4.jpg')"></div>
</div>
<div class="media-container media-2-2">
<div class="media" style="background-image: url('assets/img/dummy-5.jpg')"></div>
</div>
</div>
<section>
<div class="section-title">
<h2>Présentation</h2>
<div class="section-divider"></div>
</div>
<div class="description">
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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
</p>
</div>
</section>
<section>
<div class="section-title">
<h2>Crénaux</h2>
<div class="section-divider"></div>
</div>
<div class="schedule">
<div class="schedule-category">
<div class="schedule-category-header" title="Déroulez">
<div class="schedule-category-name">
Création de robots super cool
</div>
<i class="schedule-category-collapse-icon fas fa-chevron-circle-up"></i>
</div>
<div class="schedule-category-table">
<div class="schedule-category-days-container">
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Lundi
</div>
<div class="schedule-category-hours">
17:00 <span class="separator">-</span> 18:00
</div>
</div>
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Mardi
</div>
<div class="schedule-category-hours">
14:00 <span class="separator">-</span> 15:00
</div>
</div>
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Samedi
</div>
<div class="schedule-category-hours">
16:00 <span class="separator">-</span> 17:00
</div>
</div>
</div>
</div>
</div>
<div class="schedule-category">
<div class="schedule-category-header" title="Déroulez">
<div class="schedule-category-name">
Cours de programmation de gros expert
</div>
<i class="schedule-category-collapse-icon fas fa-chevron-circle-up"></i>
</div>
<div class="schedule-category-table">
<div class="schedule-category-days-container">
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Lundi
</div>
<div class="schedule-category-hours">
17:00 <span class="separator">-</span> 18:00
</div>
</div>
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Mardi
</div>
<div class="schedule-category-hours">
14:00 <span class="separator">-</span> 15:00
</div>
</div>
<div class="schedule-category-day-container">
<div class="schedule-category-day">
Samedi
</div>
<div class="schedule-category-hours">
16:00 <span class="separator">-</span> 17:00
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="section-title">
<h2>Tarifs</h2>
<div class="section-divider"></div>
</div>
<div class="pricing">
<div class="pricing-card">
<div class="pricing-label">
12 €
</div>
<div class="pricing-name">
Enfants
</div>
<div class="pricing-description">
Facere quas dolor eum ut.
</div>
</div>
<div class="pricing-card">
<div class="pricing-label">
12 €
</div>
<div class="pricing-name">
Enfants
</div>
<div class="pricing-description">
Facere quas dolor eum ut.
</div>
</div>
<div class="pricing-card">
<div class="pricing-label">
12 €
</div>
<div class="pricing-name">
Enfants
</div>
<div class="pricing-description">
Facere quas dolor eum ut.
</div>
</div>
</div>
</section>
<section>
<div class="section-title">
<h2>Contact</h2>
<div class="section-divider"></div>
</div>
<div class="contact">
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-user"></i>
</div>
<div class="contact-content">
Frank GITON
</div>
</div>
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-at"></i>
</div>
<div class="contact-content">
<a href="mailto:contact@werobot.fr">contact@werobot.fr</a>
</div>
</div>
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-map-marker-alt"></i>
</div>
<div class="contact-content">
<p>
6, Rue qui fait des efforts
</p>
<p>
27940 Gaillon
</p>
</div>
</div>
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-globe"></i>
</div>
<div class="contact-content">
<a href="https://werobot.fr">https://werobot.fr <i class="external-link fa fa-external-link-alt"></i></a>
</div>
</div>
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-phone"></i>
</div>
<div class="contact-content">
<a href="telto:+33783424852">07 81 42 88 42</a>
</div>
</div>
<div class="contact-item facebook">
<div class="contact-icon">
<i class="fab fa-facebook-square"></i>
</div>
<div class="contact-content">
<a href="https://www.facebook.com/WeRobot/">https://www.facebook.com/WeRobot/ <i class="external-link fa fa-external-link-alt"></i></a>
</div>
</div>
<div class="contact-item instagram">
<div class="contact-icon">
<i class="fab fa-instagram"></i>
</div>
<div class="contact-content">
<a href="https://www.instagram.com/werobot/">https://www.instagram.com/werobot/ <i class="external-link fa fa-external-link-alt"></i></a>
</div>
</div>
<div class="contact-item twitter">
<div class="contact-icon">
<i class="fab fa-twitter"></i>
</div>
<div class="contact-content">
<a href="https://www.twitter.com/werobot_FR/">https://www.twitter.com/werobot_FR/ <i class="external-link fa fa-external-link-alt"></i></a>
</div>
</div>
</div>
</section>
</div>
<div class="footer">
<div></div>
<div></div>
<div></div>
</div>
<script src="assets/js/organization.js"></script>
</body>
</html>

View file

15
views/about.twig Normal file
View file

@ -0,0 +1,15 @@
{% extends "./base.twig" %}
{% block title %}A propos{% endblock %}
{% block content %}
<div>
<p>
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?
</p>
<p>
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?
</p>
<p>
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?
</p>
</div>
{% endblock %}

View file

@ -1,11 +1,55 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>{% block title %}{% endblock %} - Forum des associations 2020</title> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width">
<link rel="icon" type="image/png" href="/assets/img/favicon.png" />
<title>{% block title %}{% endblock %} | Forum des associations 2020</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Slab&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/assets/main.css" />
{% if layout is not defined %}
<link rel="stylesheet" href="/assets/home.css" />
{% endif %}
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body> <body>
<div id="content">{% block content %}{% endblock %}</div> {% if layout is not defined %}
{% block script %}{% endblock %} <div class="header">
<div class="container header-container">
<div class="header-left">
<div class="header-image">
<a href="/"><img src="/assets/img/espace_condorcet_logo.jpg" /></a>
</div>
<div class="header-content">
<a
href="https://www.espacecondorcet.org/"
class="header-sub-title">
Espace Condorcet Centre Social
</a>
<h1 class="header-title">Forum des associations</h1>
<div class="header-description">
Cette année nous vous invitons à découvrir le forum virtuel des associations.
</div>
</div>
</div>
<div class="header-menu">
<a href="/mentions-legales">Mentions Légales</a>
<a href="/a-propos">A propos</a>
</div>
</div>
</div>
<div class="container">
<div class="content">
{% block content %}{% endblock %}
</div>
</div>
{% else %}
{% block content %}{% endblock %}
{% endif %}
{% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -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... N'hésitez pas à corriger les informations et à resoumettre pour une nouvelle verification et peut être cette fois ci vous serez publiés...
</p> </p>
<p>
Pour rappel, vous pouvez utiliser ce lien pour vous connecter sur votre interface : <a href="{{ link }}">{{ link }}</a>
</p>
<p>Coordialement</p> <p>Coordialement</p>

View file

@ -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. 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.
</p> </p>
<a href="{{ link }}">{{ link }}</a> <p>
Vous pouvez utilisez ce lien pour vous connecter sur l'interface web permettant de modifier votre association sur la platforme : <a href="{{ link }}">{{ link }}</a>
</p>
<p>Voici votre clée: {{ token }}</p> <p>Voici votre clée: {{ token }}</p>

View file

@ -1,13 +1,97 @@
{% extends "./base.twig" %} {% extends "./base.twig" %}
{% block title %}Index{% endblock %} {% block title %}Accueil{% endblock %}
{% block head %} {% block head %}{% endblock %}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %} {% block content %}
<h1>Index</h1> <div class="nav">
<p class="important"> <div
Welcome on my awesome homepage. id="nav-enabler"
</p> class="nav-item nav-mobile-enabler">
<div class="nav-icon">
<i class="fas fa-ellipsis-h"></i>
</div>
<div class="nav-item-content">
<div id="nav-enabler-text" class="nav-title">
Afficher le menu
</div>
<div class="nav-access">
<i
id="nav-enabler-icon"
class="fas fa-chevron-down">
</i>
</div>
</div>
</div>
<div id="nav-content">
<div class="nav-item" id="nav-all">
<div class="nav-icon">
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path fill="currentColor" d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"></path>
</svg>
</div>
<div class="nav-item-content">
<div class="nav-title">
Toutes
</div>
<div class="nav-access">
<i class="fas fa-chevron-right"></i>
</div>
</div>
</div>
{% for tag in tags %}
<div class="nav-item" data-tag-id="{{ tag._id }}">
<div class="nav-icon">
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 {{ tag.icon.width }} {{ tag.icon.height }}">
<path
fill="currentColor"
d="{{ tag.icon.path }}"
></path>
</svg>
</div>
<div class="nav-item-content">
<div class="nav-title">
{{ tag.name }}
</div>
<div class="nav-access">
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-w-10"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512">
<path
fill="currentColor"
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"
></path>
</svg>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="mosaic" id="mosaic">
<div class="mosaic-header" id="mosaic-header"></div>
<div class="card-container"></div>
</div>
{% endblock %}
{% block scripts %}
<script>
let tags = JSON.parse(`{{ tagsJSON }}`)
let organizations = JSON.parse(`{{ organizationsJSON }}`)
</script>
<script src="/assets/js/home.js"></script>
{% endblock %} {% endblock %}

43
views/legals.twig Normal file
View file

@ -0,0 +1,43 @@
{% extends "./base.twig" %}
{% block title %}Informations légales{% endblock %}
{% block content %}
<div>
<h1>Informations légales</h1>
<h3>Siège social</h3>
<p>
Espace Condorcet Centre Social<br>
12 rue Jean Moulin,<br>
27600 GAILLON
</p>
<h3>Contact</h3>
<p>
Téléphone : 02 32 77 50 80<br>
Fax : 02 32 77 50 99
</p>
<h3>Représentant légal</h3>
<p>Liliane COQUET</p>
<h3>Immatriculation</h3>
<p>
Numéro SIREN : 338 248 206 000 25<br>
Code APE 88 99 B
</p>
<h3>Hébergement</h3>
<p>
Scaleway<br>
ONLINE SAS BP 438 75366 PARIS CEDEX 08 FRANCE
</p>
</div>
{% endblock %}

7
views/not-found.twig Normal file
View file

@ -0,0 +1,7 @@
{% extends "./base.twig" %}
{% block title %}Page introuvable{% endblock %}
{% block content %}
<div>
<h1>Page introuvable</h1>
</div>
{% endblock %}

272
views/organization.twig Normal file
View file

@ -0,0 +1,272 @@
{% extends "./base.twig" %}
{% block title %}Association{% endblock %}
{% block head %}
<link rel="stylesheet" href="/assets/organization.css" />
{% endblock %}
{% block content %}
<div class="header">
<div class="container header-container">
<a href="/" class="return">
<div class="return-icon">
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path fill="currentColor" d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z"></path>
</svg>
</div>
<div class="return-title">Revenir à la liste</div>
</a>
<div class="header-title">
Forum des associations
</div>
</div>
</div>
<div class="cover-background" style="background-image: url({{ data.cover.location }})"></div>
<div class="cover">
<div class="cover-content container">
<div class="cover-image" style="background-image: url({{ data.thumbnail.location }});">
</div>
<div class="cover-title-container">
<h1 class="cover-title">
{{ data.name }}
</h1>
<h4 class="cover-sub-title">
</h4>
</div>
</div>
</div>
<div class="container">
<div class="media-mosaic">
{% for media in data.gallery %}
<div class="media-container">
<div class="media" style="background-image: url({{ media.location }})"></div>
</div>
{% endfor %}
{# <div class="media-container media-1-2">
<div class="media" style="background-image: url({{ data.gallery[2].location }})"></div>
<div class="media-overlay"><i class="fas fa-play-circle"></i></div>
</div> #}
</div>
{% if data.descriptionLong|length > 0 %}
<section>
<div class="section-title">
<h2>Présentation</h2>
<div class="section-divider"></div>
</div>
<div class="description">
{{ data.descriptionLong|raw }}
</div>
</section>
{% endif %}
{% if data.schedule|length > 0 %}
<section>
<div class="section-title">
<h2>Crénaux</h2>
<div class="section-divider"></div>
</div>
<div class="schedule">
{% for item in data.schedule %}
<div class="schedule-category">
<div class="schedule-category-header" title="Déroulez">
<div class="schedule-category-name">
{{ item.name }}
</div>
<svg
class="schedule-category-collapse-icon"
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512">
<path fill="currentColor" d="M8 256C8 119 119 8 256 8s248 111 248 248-111 248-248 248S8 393 8 256zm231-113.9L103.5 277.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L256 226.9l101.6 101.6c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L273 142.1c-9.4-9.4-24.6-9.4-34 0z"></path>
</svg>
</div>
<div class="schedule-category-table">
<div class="schedule-category-days-container">
{% for when in item.when %}
<div class="schedule-category-day-container">
<div class="schedule-category-day">
{{ when.day }}
</div>
<div class="schedule-category-hours">
{{ when.from }} <span class="separator">-</span> {{ when.to }}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</section>
{% endif %}
{% if data.pricing|length > 0 %}
<section>
<div class="section-title">
<h2>Tarifs</h2>
<div class="section-divider"></div>
</div>
<div class="pricing">
{% for item in data.pricing %}
<div class="pricing-card">
<div class="pricing-label">
{{ item.priceLabel }}
</div>
<div class="pricing-name">
{{ item.name }}
</div>
<div class="pricing-description">
{{ item.description }}
</div>
</div>
{% endfor %}
</div>
</section>
{% endif %}
{% if data.contacts is defined %}
<section>
<div class="section-title">
<h2>Contact</h2>
<div class="section-divider"></div>
</div>
<div class="contact">
{% if data.contacts.person|length > 0 %}
<div class="contact-item person">
<div class="contact-icon">
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path>
</svg>
</div>
<div class="contact-content">
{{ data.contacts.person }}
</div>
</div>
{% endif %}
{% if data.contacts.email|length > 0 %}
<div class="contact-item email">
<div class="contact-icon">
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path>
</svg>
</div>
<div class="contact-content">
<a href="mailto:{{ data.contacts.email }}">{{ data.contacts.email }}</a>
</div>
</div>
{% endif %}
{% if data.contacts.address|length > 0 %}
<div class="contact-item address">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
<path fill="currentColor" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0zM192 272c44.183 0 80-35.817 80-80s-35.817-80-80-80-80 35.817-80 80 35.817 80 80 80z"></path>
</svg>
</div>
<div class="contact-content">
{% for line in data.contacts.address %}
<span>{{ line }}</span><br>
{% endfor %}
</div>
</div>
{% endif %}
{% if data.contacts.phone|length > 0 %}
<div class="contact-item phone">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M493.4 24.6l-104-24c-11.3-2.6-22.9 3.3-27.5 13.9l-48 112c-4.2 9.8-1.4 21.3 6.9 28l60.6 49.6c-36 76.7-98.9 140.5-177.2 177.2l-49.6-60.6c-6.8-8.3-18.2-11.1-28-6.9l-112 48C3.9 366.5-2 378.1.6 389.4l24 104C27.1 504.2 36.7 512 48 512c256.1 0 464-207.5 464-464 0-11.2-7.7-20.9-18.6-23.4z"></path>
</svg>
</div>
<div class="contact-content">
<a href="telto:{{ data.contacts.phoneInt }}">{{ data.contacts.phoneSplit }}</a>
</div>
</div>
{% endif %}
{% if data.contacts.website|length > 0 %}
<div class="contact-item website">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M336.5 160C322 70.7 287.8 8 248 8s-74 62.7-88.5 152h177zM152 256c0 22.2 1.2 43.5 3.3 64h185.3c2.1-20.5 3.3-41.8 3.3-64s-1.2-43.5-3.3-64H155.3c-2.1 20.5-3.3 41.8-3.3 64zm324.7-96c-28.6-67.9-86.5-120.4-158-141.6 24.4 33.8 41.2 84.7 50 141.6h108zM177.2 18.4C105.8 39.6 47.8 92.1 19.3 160h108c8.7-56.9 25.5-107.8 49.9-141.6zM487.4 192H372.7c2.1 21 3.3 42.5 3.3 64s-1.2 43-3.3 64h114.6c5.5-20.5 8.6-41.8 8.6-64s-3.1-43.5-8.5-64zM120 256c0-21.5 1.2-43 3.3-64H8.6C3.2 212.5 0 233.8 0 256s3.2 43.5 8.6 64h114.6c-2-21-3.2-42.5-3.2-64zm39.5 96c14.5 89.3 48.7 152 88.5 152s74-62.7 88.5-152h-177zm159.3 141.6c71.4-21.2 129.4-73.7 158-141.6h-108c-8.8 56.9-25.6 107.8-50 141.6zM19.3 352c28.6 67.9 86.5 120.4 158 141.6-24.4-33.8-41.2-84.7-50-141.6h-108z"></path>
</svg>
</div>
<div class="contact-content">
<a href="{{ data.contacts.website }}">
{{ data.contacts.website }}
<svg class="external-link" aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path>
</svg>
</a>
</div>
</div>
{% endif %}
{% if data.contacts.facebook|length > 0 %}
<div class="contact-item facebook">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor" d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path>
</svg>
</div>
<div class="contact-content">
<a href="{{ data.contacts.facebook }}">
{{ data.contacts.facebook }}
<svg class="external-link" aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path>
</svg>
</a>
</div>
</div>
{% endif %}
{% if data.contacts.instagram|length > 0 %}
<div class="contact-item instagram">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path>
</svg>
</div>
<div class="contact-content">
<a href="{{ data.contacts.instagram }}">
{{ data.contacts.instagram }}
<svg class="external-link" aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path>
</svg>
</a>
</div>
</div>
{% endif %}
{% if data.contacts.twitter|length > 0 %}
<div class="contact-item twitter">
<div class="contact-icon">
<svg aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
</svg>
</div>
<div class="contact-content">
<a href="{{ data.contacts.twitter }}">
{{ data.contacts.twitter }}
<svg class="external-link" aria-hidden="true"focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path>
</svg>
</a>
</div>
</div>
{% endif %}
</div>
</section>
{% endif %}
</div>
<div class="footer">
<div></div>
<div></div>
<div></div>
</div>
{% endblock %}
{% block scripts %}
<script src="/assets/js/organization.js"></script>
{% endblock %}