diff --git a/app.js b/app.js index ace05dc1b3762f670805a908b67ba7b3d5160038..77be80c51a8d33e32e17f4fa3bdd30f849aaca54 100644 --- a/app.js +++ b/app.js @@ -112,7 +112,7 @@ app.use(function(err, req, res, next) { // render the error page res.status(err.status || 500); - res.render('error'); + res.render('error', {title: 'ERREUR'}); }); module.exports = app; diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 910a8befcb44ab516ae713550aaf053beb33dbf0..ced89f440400f3337ac25ebd46aae8aae923618a 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,37 +1,81 @@ +:root { + --bg-color: #e3e7f1; + --sh-color: #494ca2; + --fg-color: #222; + --hl-color: #8186d5; +} + body { margin: 0; padding: 0; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; + background: var(--bg-color); +} + +body > main { + box-sizing: border-box; + padding: 25px; } .msgs { display: flex; flex-direction: column; - position: absolute; - left:0; + position: fixed; + right: 0; bottom: 0; - margin: 0 0 15px 15px; + margin: 0 15px 15px 0; min-width: 150px; } -#create-form { - overflow: hidden; - width: min-content; -} - -form > fieldset, form > button { - margin-top: 5%; +fieldset, form > button { + margin-top: 15px; } div.form-input { - display: inline-block; margin-bottom: 15px; + color: var(--bg-color); + font-weight: bold; } span.error-message { color: red; } +textarea { + border: solid 2px var(--fg-color); + border-radius: 5px; +} + +fieldset { + border-color: var(--sh-color); +} + +figure { + margin: 15px 7px; +} + +legend { + color: var(--bg-color); + font-size: 1.2em; + font-weight: bold; +} + +.form-input > label { + font-weight: bold; + color: var(--fg-color); + font-size: 1.2em; +} + +.form-input > ul { + display: flex; + flex-direction: column; + padding-left: 0; +} + +.form-input > ul > li { + list-style-type: none; +} + .msg { display: flex; justify-content: space-between; @@ -67,23 +111,260 @@ span.error-message { cursor: pointer; } -.ad-photos { +.ad-pictures { display: flex; align-items: center; - width: 100%; - max-width: 750px; justify-content: space-around; + /*Forced to specify a width here, otherwise container warps outside of parent*/ + width: 610px; } -.ad-photos > img { +.ad-pictures > img { box-sizing: border-box; - border: solid 2px black; - padding: 2px; - max-width: 25%; - height: 100%; + border: solid 5px var(--bg-color); + border-radius: 5px; + background: var(--sh-color); + flex: 1; + height: auto; margin-bottom: 15px; + box-shadow: 0 0 5px var(--fg-color); + margin: 0 7px; } .reply-form { display: none; +} + +.ad { + box-sizing: border-box; + box-shadow: 0 0 8px var(--fg-color); + border-radius: 15px; + padding: 25px; + width: 700px; + background: var(--hl-color); + color: var(--fg-color); + display: flex; + flex-direction: column; + margin-bottom: 30px; +} + +.ad-preview { + width: 400px; +} + +.ad-infos { + margin-bottom: 15px; +} + +.infos-title > h2 { + margin: 0; + margin-bottom: 0.33em; +} + +.infos-title > h1 { + color: var(--bg-color); + font-size: 1.8em; + margin: 0; +} + +.infos-details { + width: 100%; + color: var(--bg-color); + font-size: 1.2em; + display: grid; + grid-template-columns: 50% 50%; +} + +.ad-details > h2 { + margin: 0; +} + +.infos-item > p, .infos-item > time { + margin: 0; + font-size: 1.3em; +} + +.infos-item > h4 { + margin-bottom: 5px; + color: var(--fg-color) +} + +.ad-description { + color: var(--bg-color); + font-size: 1.2em; +} + +button, input[type=submit], a { + box-sizing: border-box; + background: var(--fg-color); + color: var(--bg-color); + border: none; + border-radius: 5px; + padding: 10px 20px; + font-size: 1.2em; + text-decoration: none; + cursor: pointer; + display: inline-block; + width: max-content; + font-family: "Lucida Grande", Helvetica, Arial, sans-serif; +} + +button.alt, input.alt[type=submit], a.alt { + background: var(--hl-color); + color: var(--bg-color); + font-weight: bold; +} + +button:hover, input[type=submit]:hover, a:hover { + color: var(--hl-color); +} + +button.alt:hover, input.alt[type=submit]:hover, a.alt:hover { + background: var(--sh-color); + color: var(--bg-color); + font-weight: bold; +} + +button:active, input[type=submit]:active, a:active { + background: var(--sh-color); + color: var(--fg-color); +} + +.ad-questions { + margin-bottom: 15px; +} + +.questions-details > .question:not(:last-child) { + margin-bottom: 10px; +} + +.question { + background: var(--sh-color); + padding: 15px; + border-radius: 15px; + border: solid 3px var(--fg-color); + color: var(--bg-color); +} + +.question > main, .reply > main { + font-size: 1.2em; +} + +.question > time, .reply > time { + font-weight: bold; + margin-left: 3em; +} + +.reply { + margin-left: 2em; +} + +.reply:not(:last-child) { + margin-bottom: 10px; +} + +.reply-control { + margin-top: 5px; +} + +.reply-btn { + font-weight: bold; + font-size: 1em; +} + +.m-t { + margin-top: 5px; +} + +.m-b { + margin-bottom: 5px; +} + +.m-l { + margin-left: 5px; +} + +.m-r { + margin-right: 5px; +} + +.m-t-lg { + margin-top: 20px; +} + +.m-b-lg { + margin-bottom: 20px; +} + +.m-l-lg { + margin-left: 20px; +} + +.m-r-lg { + margin-right: 20px; +} + +.ads { + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-around; +} + +nav { + display: flex; + justify-content: space-between; + background: var(--hl-color); + box-shadow: 0 2px 5px black; + margin-bottom: 5px; + padding-top: 5px; +} + +nav>.auth-links, nav>.nav-links { + display: flex; + align-items: center; +} + +.nav-links a { + background: transparent; + color: var(--bg-color); + font-weight: bold; + font-size: 1.4em; + cursor: pointer; +} + +.nav-links a:hover { + color: var(--fg-color); +} + +.nav-links a:active { + color: var(--sh-color); +} + +.auth-links { + font-size: 1.2em; + color: var(--bg-color); +} + +.auth-links a, .auth-links button { + box-sizing: border-box; + font-size: 0.9em; + padding: 5px 10px; + height: max-content; + font-weight: bold; +} + +.auth-form { + width: 300px; + display: flex; + flex-direction: column; +} + +.auth-form > .form-input { + display: flex; + flex-direction: column; +} + +.form-input > select { + width: min-content; } \ No newline at end of file diff --git a/routes/ads.js b/routes/ads.js index 7f8b76d8b7d2c5dcf2977b9d3e6928e1d862eab3..660be336cd46899f3c800b7d195dddb7e41f17e8 100644 --- a/routes/ads.js +++ b/routes/ads.js @@ -10,56 +10,67 @@ const upload = multer({ }); /* GET to get to get to the ad creation form */ -router.get('/create', authorize('agent'), function(req, res, next) { - res.render('ads/create'); -}) - .post('/create', authorize('agent'), - upload.array('pictures', 3), function(req, res, next) { - const body = req.body; - const id = body.id; - - const formData = { - title: body.title, - type: body.type, - transactionStatus: body.transactionStatus, - price: body.price && body.price.replace(',', '.'), - published: body.published === 'on', - description: body.description, - availabilityDate: body.availabilityDate === '' ? - null : body.availabilityDate, - }; - - errCallback = (reason) => res.status(406).render('ads/create', reason); +router + .get('/create', authorize('agent'), function(req, res) { + res.render('ads/create', {title: 'Créer annonce'}); + }) + .post('/create', authorize('agent'), upload.array('pictures', 3), function( + req, + res, + ) { + const body = req.body; + const id = body.id; + + const formData = { + title: body.title, + type: body.type, + transactionStatus: body.transactionStatus, + price: body.price.replace(',', '.'), + published: body.published === 'on', + description: body.description, + availabilityDate: + body.availabilityDate === '' ? null : body.availabilityDate, + }; - if (req.files && req.files.length) { - formData.pictures = req.files.map((f) => ({ - name: f.fieldName, - body: f.buffer, - })); - } + if (req.files.length) { + formData.pictures = req.files.map((f) => ({ + name: f.fieldName, + body: f.buffer, + })); + } + + if (id) { + // Peut-être charger l'objet en amont et le retourner si erreur ? + adModel.Ad.updateOne({_id: id}, {$set: formData}) + .exec() + .then(() => { + req.flash('success', 'Mise à jour réussie !'); + res.redirect('/ads/'); + }) + .catch((reason) => { + res.render('ads/create', { + title: 'Modifier annonce', + errors: reason.errors, + }); + }); + } else { + const newAd = new adModel.Ad(formData); - if (id) { - // Peut-être charger l'objet en amont et le retourner si erreur ? - adModel.Ad.updateOne({_id: id}, {$set: formData}).exec() - .then((value) => { - req.flash('success', 'Mise à jour réussie !'); - res.redirect('/ads/'); - }) - .catch((reason) => { - errCallback(reason); - }); - } else { - const newAd = new adModel.Ad(formData); - - newAd.save() - .then((value) => { - res.redirect('/ads/'); - }) - .catch((reason) => { - errCallback(reason); - }); - } - }) + newAd + .save() + .then(() => { + req.flash('success', 'Mise à jour réussie !'); + res.redirect('/ads/'); + }) + .catch((reason) => { + console.error(reason); + res.render('ads/create', { + title: 'Créer annonce', + errors: reason.errors, + }); + }); + } + }) .get('/update/:id', authorize('agent'), function(req, res, next) { const id = req.params.id; let status; @@ -78,8 +89,7 @@ router.get('/create', authorize('agent'), function(req, res, next) { req.flash('success', 'Mise à jour réussie !'); } - res.status(status) - .render('ads/create', {ad: ad, errors_update: errors}); + res.render('ads/create', {title: 'Modifier annonce', ad: ad, errors_update: errors}); }); }) .get('/delete/:id', authorize('agent'), deleteAdAction) @@ -169,6 +179,7 @@ router.get('/create', authorize('agent'), function(req, res, next) { // on trie les annonces par ordre alphabétique res.render('ads/index', { + title: 'Annonces', ads: { published: publishedAds, notPublished: notPublishedAds, @@ -179,6 +190,7 @@ router.get('/create', authorize('agent'), function(req, res, next) { console.error(err); res.status(500); res.render('ads/index', { + title: 'Annonces', ads: [], _messages: [ ...res.locals._messages, @@ -199,7 +211,7 @@ function renderAdAction(published) { adModel.Ad.findOne({_id: req.params.id, published}, {'pictures.body': 0}) .then((ad) => { if (ad) { - res.render('ads/show', {ad}); + res.render('ads/show', {title: ad.title, ad}); } else { req.flash('error', 'L\'annonce demandé n\'a pas été trouvé'); res.redirect('/'); diff --git a/routes/auth.js b/routes/auth.js index eff9aabe56034cc7d2dd88f0fd053dd33cdcd043..797ba6d76bb8dc2ae525fa2b6ca78ee250fa2515 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -9,6 +9,7 @@ const users = require('../models/user'); router .get('/register', function(req, res, next) { res.render('register', { + title: 'Inscription', roles: Object.values(users.USER_ROLE), }); }) @@ -42,7 +43,7 @@ router .catch(next); }) .get('/login', function(req, res, next) { - res.render('login'); + res.render('login', {title: 'Connexion'}); }) .post('/login', function(req, res, next) { passport.authenticate('local', function(err, user, info) { diff --git a/views/ads/create.hbs b/views/ads/create.hbs index 1572d361eae40df247ad9cbd740694455ee3e3f6..0ed3e582395fe204a1065f12b90c787d5577b720 100644 --- a/views/ads/create.hbs +++ b/views/ads/create.hbs @@ -1,105 +1,113 @@ -
- -
-

Créer une annonce immobilière

- - - {{#each errors_update}} -

{{this}}

- {{/each}} -
-
- -
-
- - - {{errors.title.message}} -
- - -
- Informations sur l'annonce - -
+

Créer une annonce immobilière

+ +
+ {{#each errors_update}} +

{{this}}

+ {{/each}} +
+ + +
+
+ + + {{errors.title.message}} +
+ + +
+ Informations sur l'annonce + +
+
    - A la vente - A la location +
  • + A la vente +
  • +
  • + A la location +
- {{errors.type.message}} -
+ {{errors.type.message}} + -
+
    - Disponible - Louée - Vendue +
  • + Disponible +
  • +
  • + Louée +
  • +
  • + Vendue +
- {{errors.transactionStatus.message}} -
+ {{errors.transactionStatus.message}} + -
+
-
+
€ -
- {{errors.price.message}} -
+ + {{errors.price.message}} + -
-
+
-
- {{errors.published.message}} -
-
- - -
- Détails de l'annonce - -
- - - {{errors.pictures.message}} -
- - - -
- - - {{errors.description.message}} -
- -
- - - {{errors.date.message}} -
-
- - - + {{errors.published.message}} + +
+
+ + +
+ Détails de l'annonce + +
+ + + {{errors.pictures.message}} +
+ +
+ {{#with ad as |ad|}} + {{#each ad.pictures}} + {{title}} + {{/each}} + {{/with}} +
+ +
+ + + {{errors.description.message}} +
+ +
+ + + {{errors.date.message}} +
+
+ + + +
+ + \ No newline at end of file diff --git a/views/ads/index.hbs b/views/ads/index.hbs index 66ef9cbfb17511e239bdf07bbc2de34c14873e67..71b4549d887c275ed54ee0fb053ee9a8f77a745c 100644 --- a/views/ads/index.hbs +++ b/views/ads/index.hbs @@ -1,48 +1,80 @@ {{#authorize "agent"}}

Annonces non publiées

-Nouvelle annonce +Nouvelle annonce -{{#each ads.notPublished}} -
-
-

{{title}}

-
-

Type : {{getEnumValue "type" type}}

-

Statut : {{getEnumValue "transactionStatus" transactionStatus}}

-

Prix : {{formatPrice price}} €

-
- -
- -
-{{/each}} +
+ {{#each ads.notPublished}} +
+
+
+

{{title}}

+
+
+
+

Type

+

{{getEnumValue "type" type}}

+
+
+

Statut

+

{{getEnumValue "transactionStatus" transactionStatus}}

+
+
+

Prix

+

{{formatPrice price}} €

+
+
+

Disponible le

+ +
+
+
+ +
+ {{/each}} +
{{/authorize}}

Liste des annonces immobilières

-{{#each ads.published}} -
-
-

{{title}}

-
-

Type : {{getEnumValue "type" type}}

-

Statut : {{getEnumValue "transactionStatus" transactionStatus}}

-

Prix : {{formatPrice price}} €

-
- -
- -
-{{/each}} +
+ {{#each ads.published}} +
+
+
+

{{title}}

+
+
+
+

Type

+

{{getEnumValue "type" type}}

+
+
+

Statut

+

{{getEnumValue "transactionStatus" transactionStatus}}

+
+
+

Prix

+

{{formatPrice price}} €

+
+
+

Disponible le

+ +
+
+
+ +
+ {{/each}} +
{{#authorize "agent"}} diff --git a/views/ads/show.hbs b/views/ads/show.hbs index 40ff5b160a4fcec00e93645941726d03f19b87e1..becc5c4791d2ab76eb92e7d765fa708e34a25abc 100644 --- a/views/ads/show.hbs +++ b/views/ads/show.hbs @@ -1,16 +1,31 @@ -
-
-

Propriété : {{ad.title}}

-
-

Type : {{getEnumValue "type" ad.type}}

-

Statut : {{getEnumValue "transactionStatus" ad.transactionStatus}}

-

Prix : {{formatPrice ad.price}} €

+
+
+
+

Propriété

+

{{ad.title}}

+
+
+
+

Type

+

{{getEnumValue "type" ad.type}}

+
+
+

Statut

+

{{getEnumValue "transactionStatus" ad.transactionStatus}}

+
+
+

Prix

+

{{formatPrice ad.price}} €

+
+
+

Disponible le

+ +
-
-
+

Images

-
+
{{#with ad as |ad|}} {{#each ad.pictures}} {{title}} @@ -18,23 +33,24 @@ {{/with}}

Description

-

{{ad.description}}

+

{{ad.description}}

-
+ {{#if ad.published}} +

Questions

{{#authorize "client"}} -
+

Poser une question


- +
+
{{/authorize}} -
+

Voir les questions @@ -44,45 +60,48 @@ {{#each ad.questions}}
-

{{body}}

+ {{body}}
{{#authorize "agent"}}
-
+
- +
+
{{/authorize}} {{#if this.answers.length}} -
- Réponses - {{#each this.answers}} -
-
-

{{body}}

-
- -
- {{/each}} -
-
+
+ +

+ Réponses +

+
+ {{#each this.answers}} +
+
+ {{body}} +
+ +
+ {{/each}} +
{{else}}

Aucune réponse

{{/if}} +

{{else}}

Aucune question.

{{/each}} {{/with}}
+ {{/if}} {{#authorize "agent"}}
\ No newline at end of file