🎨 standardJS all files
This commit is contained in:
		
							
								
								
									
										13
									
								
								api/app.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								api/app.js
									
									
									
									
									
								
							| @@ -34,7 +34,9 @@ app.use('/quotes', require('./routes/quotes')) | ||||
| app.use('/tasks', require('./routes/tasks')) | ||||
|  | ||||
| /* Errors Handling */ | ||||
| app.use((_req, _res, next) => errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })) // 404 | ||||
| app.use((_req, _res, next) => | ||||
|   errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" }) | ||||
| ) // 404 | ||||
| app.use((error, _req, res, _next) => { | ||||
|   console.log(error) | ||||
|   const { statusCode, message } = error | ||||
| @@ -76,8 +78,11 @@ Tasks.belongsTo(Users, { constraints: false }) | ||||
|  | ||||
| /* Server */ | ||||
| // sequelize.sync({ force: true }) | ||||
| sequelize.sync() | ||||
| sequelize | ||||
|   .sync() | ||||
|   .then(() => { | ||||
|     app.listen(PORT, () => console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)) | ||||
|     app.listen(PORT, () => | ||||
|       console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`) | ||||
|     ) | ||||
|   }) | ||||
|   .catch((error) => console.log(error)) | ||||
|   .catch(error => console.log(error)) | ||||
|   | ||||
| @@ -26,16 +26,24 @@ exports.emailQuoteTemplate = (isValid, quote, frontendLink) => ` | ||||
|                                             <tr> | ||||
|                                                 <td align="left" valign="top" style="line-height:150%;font-family:Helvetica;font-size:14px;color:rgb(222, 222, 222);padding:30px;box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25);border: 1px solid black;border-radius: 1rem;"> | ||||
|                                                     <h2 style="font-size:22px;line-height:28px;margin:0 0 12px 0;"> | ||||
|                                                         La citation que vous avez proposée a été ${(isValid) ? 'validée' : 'supprimée'}. | ||||
|                                                         La citation que vous avez proposée a été ${ | ||||
|                                                           isValid | ||||
|                                                             ? 'validée' | ||||
|                                                             : 'supprimée' | ||||
|                                                         }. | ||||
|                                                     </h2> | ||||
|                                                     <p style="margin: 0 0 12px 0;"> | ||||
|                                                         <a style="color: #ffd800;" href="${frontendLink}/functions/randomQuote">Lien vers la fonction randomQuote de FunctionProject.</a> | ||||
|                                                     </p> | ||||
|                                                     ${(!isValid) ? ` | ||||
|                                                     ${ | ||||
|                                                       !isValid | ||||
|                                                         ? ` | ||||
|                                                         <p style="margin: 0 0 12px 0;"> | ||||
|                                                             Si votre citation a été supprimée et vous pensez que c'est une erreur, contactez-moi à cette adresse email : <a style="color: #ffd800;" href="mailto:contact@divlo.fr">contact@divlo.fr</a>. | ||||
|                                                         </p> | ||||
|                                                     ` : ''} | ||||
|                                                     ` | ||||
|                                                         : '' | ||||
|                                                     } | ||||
|                                                     <div> | ||||
|                                                         <p style="padding:0 0 10px 0"> | ||||
|                                                             La citation en question : <br/> | ||||
|   | ||||
| @@ -16,14 +16,24 @@ function armstrongNumber (number) { | ||||
|   let resultString = '' | ||||
|   for (let index = 0; index < numberStringLength; index++) { | ||||
|     result = result + parseInt(numberString[index]) ** numberStringLength | ||||
|     resultString = resultString + ' + ' + numberString[index] + '<sup>' + numberStringLength + '</sup>' | ||||
|     resultString = | ||||
|       resultString + | ||||
|       ' + ' + | ||||
|       numberString[index] + | ||||
|       '<sup>' + | ||||
|       numberStringLength + | ||||
|       '</sup>' | ||||
|   } | ||||
|  | ||||
|   const formattedNumber = formatNumberResult(number) | ||||
|   const isArmstrongNumber = (result === number) | ||||
|   const isArmstrongNumber = result === number | ||||
|   return { | ||||
|     isArmstrongNumber, | ||||
|     resultHTML: `<p>${formattedNumber} ${isArmstrongNumber ? 'est' : "n'est pas"} un nombre d'Armstrong, car ${resultString.slice(2)} = ${formatNumberResult(result)}.</p>` | ||||
|     resultHTML: `<p>${formattedNumber} ${ | ||||
|       isArmstrongNumber ? 'est' : "n'est pas" | ||||
|     } un nombre d'Armstrong, car ${resultString.slice( | ||||
|       2 | ||||
|     )} = ${formatNumberResult(result)}.</p>` | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -32,14 +42,17 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   let { number } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(number)) { | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseInt(number) | ||||
|   if (isNaN(number) || number <= 0) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json(armstrongNumber(number)) | ||||
|   | ||||
| @@ -2,7 +2,10 @@ const errorHandling = require('../../utils/errorHandling') | ||||
| const moment = require('moment') | ||||
| const { requiredFields } = require('../../config/errors') | ||||
|  | ||||
| function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYear }) { | ||||
| function calculateAge ( | ||||
|   currentDate, | ||||
|   { birthDateDay, birthDateMonth, birthDateYear } | ||||
| ) { | ||||
|   const day = currentDate.getDate() | ||||
|   const month = currentDate.getMonth() | ||||
|   const currentDateMoment = moment([currentDate.getFullYear(), month, day]) | ||||
| @@ -15,7 +18,7 @@ function calculateAge (currentDate, { birthDateDay, birthDateMonth, birthDateYea | ||||
|   birthDateMoment.add(ageMonths, 'months') | ||||
|   const ageDays = currentDateMoment.diff(birthDateMoment, 'days') | ||||
|  | ||||
|   const isBirthday = (birthDateDay === day && birthDateMonth === month) | ||||
|   const isBirthday = birthDateDay === day && birthDateMonth === month | ||||
|   return { ageYears, ageMonths, ageDays, isBirthday } | ||||
| } | ||||
|  | ||||
| @@ -24,20 +27,27 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   const { birthDate } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(birthDate)) { | ||||
|   if (!birthDate) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   const birthDateDay = parseInt(birthDate.substring(0, 2)) | ||||
|   const birthDateMonth = parseInt((birthDate.substring(3, 5)) - 1) | ||||
|   const birthDateMonth = parseInt(birthDate.substring(3, 5) - 1) | ||||
|   const birthDateYear = parseInt(birthDate.substring(6, 10)) | ||||
|  | ||||
|   // Si ce n'est pas une date valide | ||||
|   const currentDate = new Date() | ||||
|   const birthDateObject = new Date(birthDateYear, birthDateMonth, birthDateDay) | ||||
|   const result = calculateAge(currentDate, { birthDateYear, birthDateMonth, birthDateDay }) | ||||
|   if ((currentDate < birthDateObject) || isNaN(result.ageYears)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré une date valide...', statusCode: 400 }) | ||||
|   const result = calculateAge(currentDate, { | ||||
|     birthDateYear, | ||||
|     birthDateMonth, | ||||
|     birthDateDay | ||||
|   }) | ||||
|   if (currentDate < birthDateObject || isNaN(result.ageYears)) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré une date valide...', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   let resultHTML | ||||
|   | ||||
| @@ -15,23 +15,39 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseFloat(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   axios.get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`) | ||||
|     .then((response) => { | ||||
|   axios | ||||
|     .get(`https://api.exchangeratesapi.io/latest?base=${baseCurrency}`) | ||||
|     .then(response => { | ||||
|       const rate = response.data.rates[finalCurrency] | ||||
|       if (!rate) { | ||||
|         return errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 }) | ||||
|         return errorHandling(next, { | ||||
|           message: "La devise n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|       } | ||||
|       const result = rate * number | ||||
|       const dateObject = new Date(response.data.date) | ||||
|       const year = dateObject.getFullYear() | ||||
|       const day = ('0' + (dateObject.getDate())).slice(-2) | ||||
|       const day = ('0' + dateObject.getDate()).slice(-2) | ||||
|       const month = ('0' + (dateObject.getMonth() + 1)).slice(-2) | ||||
|       const date = `${day}/${month}/${year}` | ||||
|       const resultHTML = `<p>${formatNumberResult(number)} ${response.data.base} = ${formatNumberResult(result.toFixed(2))} ${finalCurrency}</p><p>Dernier rafraîchissement du taux d'échange : ${date}</p>` | ||||
|       const resultHTML = `<p>${formatNumberResult(number)} ${ | ||||
|         response.data.base | ||||
|       } = ${formatNumberResult( | ||||
|         result.toFixed(2) | ||||
|       )} ${finalCurrency}</p><p>Dernier rafraîchissement du taux d'échange : ${date}</p>` | ||||
|       return res.status(200).json({ date, result, resultHTML }) | ||||
|     }) | ||||
|     .catch(() => errorHandling(next, { message: "La devise n'existe pas.", statusCode: 404 })) | ||||
|     .catch(() => | ||||
|       errorHandling(next, { | ||||
|         message: "La devise n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,33 @@ const errorHandling = require('../../utils/errorHandling') | ||||
| const { requiredFields, generalError } = require('../../config/errors') | ||||
| const formatNumberResult = require('../secondary/formatNumberResult') | ||||
|  | ||||
| const correspondancesDistance = ['pm', null, null, 'nm', null, null, 'µm', null, null, 'mm', 'cm', 'dm', 'm', 'dam', 'hm', 'km', null, null, 'Mm', null, null, 'Gm', null, null, 'Tm'] | ||||
| const correspondancesDistance = [ | ||||
|   'pm', | ||||
|   null, | ||||
|   null, | ||||
|   'nm', | ||||
|   null, | ||||
|   null, | ||||
|   'µm', | ||||
|   null, | ||||
|   null, | ||||
|   'mm', | ||||
|   'cm', | ||||
|   'dm', | ||||
|   'm', | ||||
|   'dam', | ||||
|   'hm', | ||||
|   'km', | ||||
|   null, | ||||
|   null, | ||||
|   'Mm', | ||||
|   null, | ||||
|   null, | ||||
|   'Gm', | ||||
|   null, | ||||
|   null, | ||||
|   'Tm' | ||||
| ] | ||||
|  | ||||
| /** | ||||
|  * @description Convertis la longueur (distance) avec les unités allant de picomètre au Téramètre. | ||||
| @@ -21,7 +47,11 @@ function convertDistance (firstValue, unitFirstValue, unitFinalValue) { | ||||
|     const result = firstValue * Math.pow(10, difference) | ||||
|     return { | ||||
|       result, | ||||
|       resultHTML: `<p>${formatNumberResult(firstValue)} ${unitFirstValue} = ${formatNumberResult(result)} ${unitFinalValue}</p>` | ||||
|       resultHTML: `<p>${formatNumberResult( | ||||
|         firstValue | ||||
|       )} ${unitFirstValue} = ${formatNumberResult( | ||||
|         result | ||||
|       )} ${unitFinalValue}</p>` | ||||
|     } | ||||
|   } | ||||
|   return false | ||||
| @@ -39,7 +69,10 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseFloat(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = convertDistance(number, numberUnit, finalUnit) | ||||
|   | ||||
| @@ -73,7 +73,9 @@ function binaryToHexadecimal (value) { | ||||
|   if (isNaN(value)) { | ||||
|     return false | ||||
|   } else { | ||||
|     return parseInt(value).toString(16).toUpperCase() | ||||
|     return parseInt(value) | ||||
|       .toString(16) | ||||
|       .toUpperCase() | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -140,10 +142,15 @@ function numberUnicodeToText (string) { | ||||
| function textToBinary (s) { | ||||
|   try { | ||||
|     s = unescape(encodeURIComponent(s)) | ||||
|     let chr; let i = 0; const l = s.length; let out = '' | ||||
|     let chr | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let out = '' | ||||
|     for (; i < l; i++) { | ||||
|       chr = s.charCodeAt(i).toString(2) | ||||
|       while (chr.length % 8 !== 0) { chr = '0' + chr } | ||||
|       while (chr.length % 8 !== 0) { | ||||
|         chr = '0' + chr | ||||
|       } | ||||
|       out += chr | ||||
|     } | ||||
|     return out.replace(/(\d{8})/g, '$1 ').replace(/(^\s+|\s+$)/, '') | ||||
| @@ -161,10 +168,13 @@ function textToBinary (s) { | ||||
| function binaryToText (s) { | ||||
|   try { | ||||
|     s = s.replace(/\s/g, '') | ||||
|     let i = 0; const l = s.length; let chr; let out = '' | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let chr | ||||
|     let out = '' | ||||
|     for (; i < l; i += 8) { | ||||
|       chr = parseInt(s.substr(i, 8), 2).toString(16) | ||||
|       out += '%' + ((chr.length % 2 === 0) ? chr : '0' + chr) | ||||
|       out += '%' + (chr.length % 2 === 0 ? chr : '0' + chr) | ||||
|     } | ||||
|     return decodeURIComponent(out) | ||||
|   } catch (error) { | ||||
| @@ -181,10 +191,13 @@ function binaryToText (s) { | ||||
| function textToHexadecimal (s) { | ||||
|   try { | ||||
|     s = unescape(encodeURIComponent(s)) | ||||
|     let chr; let i = 0; const l = s.length; let out = '' | ||||
|     let chr | ||||
|     let i = 0 | ||||
|     const l = s.length | ||||
|     let out = '' | ||||
|     for (; i < l; i++) { | ||||
|       chr = s.charCodeAt(i).toString(16) | ||||
|       out += (chr.length % 2 === 0) ? chr : '0' + chr | ||||
|       out += chr.length % 2 === 0 ? chr : '0' + chr | ||||
|       out += ' ' | ||||
|     } | ||||
|     return out.toUpperCase() | ||||
| @@ -209,7 +222,20 @@ function hexadecimalToText (s) { | ||||
| } | ||||
|  | ||||
| /* OUTPUTS */ | ||||
| const convertEncoding = { decimalToBinary, binaryToDecimal, decimalToHexadecimal, hexadecimalToDecimal, binaryToHexadecimal, hexadecimalToBinary, textToNumberUnicode, numberUnicodeToText, textToBinary, binaryToText, textToHexadecimal, hexadecimalToText } | ||||
| const convertEncoding = { | ||||
|   decimalToBinary, | ||||
|   binaryToDecimal, | ||||
|   decimalToHexadecimal, | ||||
|   hexadecimalToDecimal, | ||||
|   binaryToHexadecimal, | ||||
|   hexadecimalToBinary, | ||||
|   textToNumberUnicode, | ||||
|   numberUnicodeToText, | ||||
|   textToBinary, | ||||
|   binaryToText, | ||||
|   textToHexadecimal, | ||||
|   hexadecimalToText | ||||
| } | ||||
| function executeFunction (option, value) { | ||||
|   return convertEncoding[option](value) | ||||
| } | ||||
| @@ -225,7 +251,10 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   // Si la fonction n'existe pas | ||||
|   // eslint-disable-next-line | ||||
|   if (!convertEncoding.hasOwnProperty(functionName)) { | ||||
|     return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: "Cette conversion n'existe pas.", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = executeFunction(functionName, value) | ||||
|   | ||||
| @@ -51,7 +51,7 @@ function convertArabicToRoman (nombre) { | ||||
|  */ | ||||
| function convertRomanToArabic (string) { | ||||
|   let result = 0 | ||||
|   correspondancesRomainArabe.forEach((correspondance) => { | ||||
|   correspondancesRomainArabe.forEach(correspondance => { | ||||
|     while (string.indexOf(correspondance[1]) === 0) { | ||||
|       // Ajout de la valeur décimale au résultat | ||||
|       result += correspondance[0] | ||||
| @@ -68,7 +68,7 @@ function convertRomanToArabic (string) { | ||||
| /* OUTPUTS */ | ||||
| const convertRomanToArabicOutput = ({ res, next }, number) => { | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(number)) { | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
| @@ -80,26 +80,44 @@ const convertRomanToArabicOutput = ({ res, next }, number) => { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|  | ||||
|   return res.status(200).json({ result, resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>` }) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p><span class="important">${number}</span> s'écrit <span class="important">${result}</span> en chiffres arabes.</p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| const convertArabicToRomanOutput = ({ res, next }, number) => { | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(number)) { | ||||
|   if (!number) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas un nombre | ||||
|   number = parseInt(number) | ||||
|   if (isNaN(number)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = convertArabicToRoman(number) | ||||
|   return res.status(200).json({ result, resultHTML: `<p><span class="important">${formatNumberResult(number)}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>` }) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p><span class="important">${formatNumberResult( | ||||
|         number | ||||
|       )}</span> s'écrit <span class="important">${result}</span> en chiffres romains.</p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| const convertRomanArabicObject = { convertRomanToArabicOutput, convertArabicToRomanOutput } | ||||
| const convertRomanArabicObject = { | ||||
|   convertRomanToArabicOutput, | ||||
|   convertArabicToRomanOutput | ||||
| } | ||||
| function executeFunction (option, value, { res, next }) { | ||||
|   return convertRomanArabicObject[option]({ res, next }, value) | ||||
| } | ||||
| @@ -115,7 +133,10 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   // Si la fonction n'existe pas | ||||
|   // eslint-disable-next-line | ||||
|   if (!convertRomanArabicObject.hasOwnProperty(functionName)) { | ||||
|     return errorHandling(next, { message: "Cette conversion n'existe pas.", statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: "Cette conversion n'existe pas.", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   executeFunction(functionName, value, { res, next }) | ||||
|   | ||||
| @@ -12,15 +12,17 @@ const formatNumberResult = require('../secondary/formatNumberResult') | ||||
| function convertTemperature (degree, unit) { | ||||
|   let temperatureValue = 0 | ||||
|   if (unit === '°C') { | ||||
|     temperatureValue = (degree - 32) * 5 / 9 | ||||
|     temperatureValue = ((degree - 32) * 5) / 9 | ||||
|   } else if (unit === '°F') { | ||||
|     temperatureValue = ((degree * 9 / 5) + 32) | ||||
|     temperatureValue = (degree * 9) / 5 + 32 | ||||
|   } else { | ||||
|     return false | ||||
|   } | ||||
|   return { | ||||
|     result: temperatureValue, | ||||
|     resultHTML: `<p>${formatNumberResult(degree)} ${(unit === '°C') ? '°F' : '°C'} = ${formatNumberResult(temperatureValue)} ${unit}</p>` | ||||
|     resultHTML: `<p>${formatNumberResult(degree)} ${ | ||||
|       unit === '°C' ? '°F' : '°C' | ||||
|     } = ${formatNumberResult(temperatureValue)} ${unit}</p>` | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -36,7 +38,10 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   // Si ce n'est pas un nombre | ||||
|   degree = parseFloat(degree) | ||||
|   if (isNaN(degree)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = convertTemperature(degree, unitToConvert) | ||||
|   | ||||
| @@ -20,27 +20,36 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   let { counter } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(counter)) { | ||||
|   if (!counter) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas un nombre | ||||
|   counter = parseInt(counter) | ||||
|   if (isNaN(counter)) { | ||||
|     return errorHandling(next, { message: 'Veuillez rentré un nombre valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez rentré un nombre valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si le nombre dépasse LIMIT_COUNTER | ||||
|   const LIMIT_COUNTER = 51 | ||||
|   if (counter >= LIMIT_COUNTER) { | ||||
|     return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un compteur dépassant ${LIMIT_COUNTER - 1}.`, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un compteur dépassant ${LIMIT_COUNTER - | ||||
|         1}.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = fibonacci(counter) | ||||
|   const resultFormatted = result.map((number) => formatNumberResult(number)) | ||||
|   const resultFormatted = result.map(number => formatNumberResult(number)) | ||||
|   return res.status(200).json({ | ||||
|     result, | ||||
|     resultFormatted, | ||||
|     resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join(', ')}</p>` | ||||
|     resultHTML: `<p>Les ${counter} premiers nombres de la suite de fibonacci :<br/> ${resultFormatted.join( | ||||
|       ', ' | ||||
|     )}</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -12,7 +12,7 @@ function findLongestWord (string) { | ||||
|   let stringLength = 0 | ||||
|   let result = '' | ||||
|  | ||||
|   arrayString.forEach((element) => { | ||||
|   arrayString.forEach(element => { | ||||
|     if (element.length > stringLength) { | ||||
|       result = element | ||||
|       stringLength = element.length | ||||
| @@ -27,7 +27,7 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   const { string } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(string)) { | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -18,9 +18,14 @@ function heapAlgorithm (string) { | ||||
|  | ||||
|   for (let indexString = 0; indexString < string.length; indexString++) { | ||||
|     const firstChar = string[indexString] | ||||
|     const charsLeft = string.substring(0, indexString) + string.substring(indexString + 1) | ||||
|     const charsLeft = | ||||
|       string.substring(0, indexString) + string.substring(indexString + 1) | ||||
|     const innerPermutations = heapAlgorithm(charsLeft) | ||||
|     for (let indexPermutation = 0; indexPermutation < innerPermutations.length; indexPermutation++) { | ||||
|     for ( | ||||
|       let indexPermutation = 0; | ||||
|       indexPermutation < innerPermutations.length; | ||||
|       indexPermutation++ | ||||
|     ) { | ||||
|       results.push(firstChar + innerPermutations[indexPermutation]) | ||||
|     } | ||||
|   } | ||||
| @@ -32,19 +37,26 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   const { string } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(string)) { | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   // Si la chaîne de caractère dépasse LIMIT_CHARACTERS caractères | ||||
|   const LIMIT_CHARACTERS = 7 | ||||
|   if (string.length > LIMIT_CHARACTERS) { | ||||
|     return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec un mot dépassant ${LIMIT_CHARACTERS} caractères.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = heapAlgorithm(string) | ||||
|   let resultHTML = `<p>Il y a ${formatNumberResult(result.length)} possibilités d'anagramme pour le mot "${string}" qui contient ${string.length} caractères, la liste : <br/><br/>` | ||||
|   result.forEach((string) => { | ||||
|   let resultHTML = `<p>Il y a ${formatNumberResult( | ||||
|     result.length | ||||
|   )} possibilités d'anagramme pour le mot "${string}" qui contient ${ | ||||
|     string.length | ||||
|   } caractères, la liste : <br/><br/>` | ||||
|   result.forEach(string => { | ||||
|     resultHTML += string + '<br/>' | ||||
|   }) | ||||
|   resultHTML += '</p>' | ||||
|   | ||||
| @@ -8,7 +8,10 @@ const { requiredFields } = require('../../config/errors') | ||||
|  * @example reverseString('Hello') → 'olleH' | ||||
|  */ | ||||
| function reverseString (string) { | ||||
|   return string.split('').reverse().join('') | ||||
|   return string | ||||
|     .split('') | ||||
|     .reverse() | ||||
|     .join('') | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -28,12 +31,15 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   let { string } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(string)) { | ||||
|   if (!string) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   if (typeof string !== 'string') { | ||||
|     return errorHandling(next, { message: 'Vous devez rentré une chaîne de caractère valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez rentré une chaîne de caractère valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   string = string.toLowerCase() | ||||
| @@ -43,6 +49,10 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   return res.status(200).json({ | ||||
|     isPalindrome: isPalindromeResult, | ||||
|     reverseString: reverseStringResult, | ||||
|     resultHTML: `<p>"${string}" ${(isPalindromeResult) ? 'est' : "n'est pas"} un palindrome car <br/> "${string}" ${(isPalindromeResult) ? '===' : '!=='} "${reverseStringResult}"</p>` | ||||
|     resultHTML: `<p>"${string}" ${ | ||||
|       isPalindromeResult ? 'est' : "n'est pas" | ||||
|     } un palindrome car <br/> "${string}" ${ | ||||
|       isPalindromeResult ? '===' : '!==' | ||||
|     } "${reverseStringResult}"</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -13,12 +13,19 @@ module.exports = async ({ res, next }, argsObject) => { | ||||
|  | ||||
|   // Si ce n'est pas une url | ||||
|   if (!validator.isURL(url)) { | ||||
|     return errorHandling(next, { message: 'Veuillez entré une URL valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Veuillez entré une URL valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si ce n'est pas de type slug | ||||
|   if (!validator.isSlug(shortcutName)) { | ||||
|     return errorHandling(next, { message: "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: | ||||
|         "Le nom de votre raccourci doit être de type slug (ne pas contenir d'espaces, ni de caractères spéciaux).", | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Sanitize shortcutName | ||||
| @@ -31,20 +38,33 @@ module.exports = async ({ res, next }, argsObject) => { | ||||
|     const urlInDatabase = await shortLinks.findOne({ where: { url } }) | ||||
|     if (urlInDatabase) { | ||||
|       const urlShort = `https://short-links.divlo.fr/?q=${urlInDatabase.shortcut}` | ||||
|       return errorHandling(next, { message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: `L'url a déjà été raccourcie... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Si le nom du raccourci existe déjà | ||||
|     const shortcutInDatabase = await shortLinks.findOne({ where: { shortcut: shortcutName } }) | ||||
|     const shortcutInDatabase = await shortLinks.findOne({ | ||||
|       where: { shortcut: shortcutName } | ||||
|     }) | ||||
|     if (shortcutInDatabase) { | ||||
|       const urlShort = `https://short-links.divlo.fr/?q=${shortcutInDatabase.shortcut}` | ||||
|       return errorHandling(next, { message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: `Le nom du raccourci a déjà été utilisé... <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${urlShort}">${urlShort}</a>`, | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Ajout du lien raccourci | ||||
|     const result = await shortLinks.create({ url, shortcut: shortcutName }) | ||||
|     const shortcutLinkResult = `https://short-links.divlo.fr/?q=${result.shortcut}` | ||||
|     return res.status(200).json({ resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, result: shortcutLinkResult }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ | ||||
|         resultHTML: `URL Raccourcie : <br/> <br/> <a target="_blank" rel="noopener noreferrer" href="${shortcutLinkResult}">${shortcutLinkResult}</a>`, | ||||
|         result: shortcutLinkResult | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   | ||||
| @@ -26,11 +26,21 @@ const randomNumberOutput = ({ res, next }, argsObject) => { | ||||
|   min = parseInt(min) | ||||
|   max = parseInt(max) | ||||
|   if (isNaN(min) || isNaN(max)) { | ||||
|     return errorHandling(next, { message: 'Les paramètres min et max doivent être des nombres...', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Les paramètres min et max doivent être des nombres...', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = randomNumber(min, max) | ||||
|   return res.status(200).json({ result, resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult(result)}</strong></p>` }) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       result, | ||||
|       resultHTML: `<p>Nombre aléatoire compris entre ${min} inclus et ${max} inclus : <strong>${formatNumberResult( | ||||
|         result | ||||
|       )}</strong></p>` | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.randomNumber = randomNumber | ||||
|   | ||||
| @@ -8,9 +8,7 @@ module.exports = async ({ res, next }, _argsObject) => { | ||||
|   try { | ||||
|     const quote = await Quotes.findOne({ | ||||
|       order: sequelize.random(), | ||||
|       include: [ | ||||
|         { model: Users, attributes: ['name', 'logo'] } | ||||
|       ], | ||||
|       include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|       attributes: { | ||||
|         exclude: ['isValidated'] | ||||
|       }, | ||||
|   | ||||
| @@ -23,15 +23,18 @@ function getRandomArrayElement (array) { | ||||
|  | ||||
| async function getAmazonProductList (subject) { | ||||
|   const url = `https://www.amazon.fr/s?k=${subject}` | ||||
|   const { data } = await axios.get(`http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`) | ||||
|   const { document } = (new JSDOM(data)).window | ||||
|   const { data } = await axios.get( | ||||
|     `http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}` | ||||
|   ) | ||||
|   const { document } = new JSDOM(data).window | ||||
|   const amazonProductList = document.querySelectorAll('.s-result-item') | ||||
|   const productsList = [] | ||||
|   for (const indexProduct in amazonProductList) { | ||||
|     try { | ||||
|       const elementProduct = amazonProductList[indexProduct] | ||||
|       const productImage = elementProduct.querySelector('.s-image') | ||||
|       const originalPrice = elementProduct.querySelector('.a-price-whole').innerHTML | ||||
|       const originalPrice = elementProduct.querySelector('.a-price-whole') | ||||
|         .innerHTML | ||||
|       productsList.push({ | ||||
|         name: productImage.alt, | ||||
|         image: productImage.src, | ||||
|   | ||||
| @@ -29,28 +29,41 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   const { numbersList } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(numbersList)) { | ||||
|   if (!numbersList) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   const numbersListArray = numbersList.split(',').map((number) => number.trim().replace(' ', '')).map(Number) | ||||
|   const numbersListArray = numbersList | ||||
|     .split(',') | ||||
|     .map(number => number.trim().replace(' ', '')) | ||||
|     .map(Number) | ||||
|  | ||||
|   // Si ce n'est pas une liste de nombres | ||||
|   if (numbersListArray.includes(NaN)) { | ||||
|     return errorHandling(next, { message: 'Vous devez rentrer une liste de nombres séparée par des virgules valide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: | ||||
|         'Vous devez rentrer une liste de nombres séparée par des virgules valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // Si la taille du tableau dépasse LIMIT_ARRAY_LENGTH | ||||
|   const LIMIT_ARRAY_LENGTH = 31 | ||||
|   if (numbersListArray.length >= LIMIT_ARRAY_LENGTH) { | ||||
|     return errorHandling(next, { message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH - 1} nombres.`, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: `Par souci de performance, vous ne pouvez pas exécuter cette fonction avec une liste de nombres dépassant ${LIMIT_ARRAY_LENGTH - | ||||
|         1} nombres.`, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const result = sortArray(numbersListArray) | ||||
|   const resultFormatted = result.map((number) => formatNumberResult(number)) | ||||
|   const resultFormatted = result.map(number => formatNumberResult(number)) | ||||
|   return res.status(200).json({ | ||||
|     result, | ||||
|     resultFormatted, | ||||
|     resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join(', ')}</p>` | ||||
|     resultHTML: `<p>La liste de nombres dans l'ordre croissant :<br/> ${resultFormatted.join( | ||||
|       ', ' | ||||
|     )}</p>` | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -26,21 +26,48 @@ module.exports = ({ res, next }, argsObject) => { | ||||
|   let { cityName } = argsObject | ||||
|  | ||||
|   // S'il n'y a pas les champs obligatoire | ||||
|   if (!(cityName)) { | ||||
|   if (!cityName) { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|  | ||||
|   cityName = cityName.split(' ').join('+') | ||||
|  | ||||
|   // Récupère les données météo grâce à l'API : openweathermap.org. (→ avec limite de 50 requêtes par minute) | ||||
|   queue.request(() => { | ||||
|     axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}`) | ||||
|       .then((response) => { | ||||
|         const json = response.data | ||||
|         const showDateTimeValue = dateTimeUTC((json.timezone / 60 / 60).toString()).showDateTimeValue | ||||
|         const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${json.coord.lat},${json.coord.lon}" rel="noopener noreferrer" target="_blank">${json.name}, ${json.sys.country}</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize(json.weather[0].description)}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${json.main.humidity}% <br/> <img src="https://openweathermap.org/img/wn/${json.weather[0].icon}@2x.png"/></p>` | ||||
|         return res.status(200).json({ result: json, resultHTML }) | ||||
|       }) | ||||
|       .catch(() => errorHandling(next, { message: "La ville n'existe pas (dans l'API de openweathermap.org).", statusCode: 404 })) | ||||
|   }, 'everyone', 'weatherRequest') | ||||
|   queue.request( | ||||
|     () => { | ||||
|       axios | ||||
|         .get( | ||||
|           `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&lang=fr&units=metric&appid=${WEATHER_API_KEY}` | ||||
|         ) | ||||
|         .then(response => { | ||||
|           const json = response.data | ||||
|           const showDateTimeValue = dateTimeUTC( | ||||
|             (json.timezone / 60 / 60).toString() | ||||
|           ).showDateTimeValue | ||||
|           const resultHTML = `<p>🌎 Position : <a href="https://www.google.com/maps/search/?api=1&query=${ | ||||
|             json.coord.lat | ||||
|           },${json.coord.lon}" rel="noopener noreferrer" target="_blank">${ | ||||
|             json.name | ||||
|           }, ${ | ||||
|             json.sys.country | ||||
|           }</a><br/>⏰ Date et heure : ${showDateTimeValue} <br/>☁️ Météo : ${capitalize( | ||||
|             json.weather[0].description | ||||
|           )}<br/>🌡️ Température : ${json.main.temp} °C<br/> 💧 Humidité : ${ | ||||
|             json.main.humidity | ||||
|           }% <br/> <img src="https://openweathermap.org/img/wn/${ | ||||
|             json.weather[0].icon | ||||
|           }@2x.png"/></p>` | ||||
|           return res.status(200).json({ result: json, resultHTML }) | ||||
|         }) | ||||
|         .catch(() => | ||||
|           errorHandling(next, { | ||||
|             message: | ||||
|               "La ville n'existe pas (dans l'API de openweathermap.org).", | ||||
|             statusCode: 404 | ||||
|           }) | ||||
|         ) | ||||
|     }, | ||||
|     'everyone', | ||||
|     'weatherRequest' | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,8 @@ function showDateTime (timeNow) { | ||||
|   const hour = ('0' + timeNow.getHours()).slice(-2) | ||||
|   const minute = ('0' + timeNow.getMinutes()).slice(-2) | ||||
|   const second = ('0' + timeNow.getSeconds()).slice(-2) | ||||
|   const showDateTimeValue = day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second | ||||
|   const showDateTimeValue = | ||||
|     day + '/' + month + '/' + year + ' - ' + hour + ':' + minute + ':' + second | ||||
|   const objectDateTime = { | ||||
|     year: year, | ||||
|     month: month, | ||||
|   | ||||
| @@ -1,9 +1,14 @@ | ||||
| const Sequelize = require('sequelize') | ||||
| const { DATABASE } = require('../config/config') | ||||
|  | ||||
| const sequelize = new Sequelize(DATABASE.name, DATABASE.user, DATABASE.password, { | ||||
|   dialect: 'mysql', | ||||
|   host: DATABASE.host | ||||
| }) | ||||
| const sequelize = new Sequelize( | ||||
|   DATABASE.name, | ||||
|   DATABASE.user, | ||||
|   DATABASE.password, | ||||
|   { | ||||
|     dialect: 'mysql', | ||||
|     host: DATABASE.host | ||||
|   } | ||||
| ) | ||||
|  | ||||
| module.exports = sequelize | ||||
|   | ||||
| @@ -12,7 +12,11 @@ const DEFAULT_OPTIONS = { | ||||
|  * @param {*} Model Model Sequelize | ||||
|  * @param {Object} options Options avec clause where etc. | ||||
|  */ | ||||
| async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTIONS) { | ||||
| async function getPagesHelper ( | ||||
|   { req, res, next }, | ||||
|   Model, | ||||
|   options = DEFAULT_OPTIONS | ||||
| ) { | ||||
|   const page = helperQueryNumber(req.query.page, 1) | ||||
|   const limit = helperQueryNumber(req.query.limit, 10) | ||||
|   const offset = (page - 1) * limit | ||||
| @@ -23,7 +27,7 @@ async function getPagesHelper ({ req, res, next }, Model, options = DEFAULT_OPTI | ||||
|       ...options | ||||
|     }) | ||||
|     const { count, rows } = result | ||||
|     const hasMore = (page * limit) < count | ||||
|     const hasMore = page * limit < count | ||||
|     return res.status(200).json({ totalItems: count, hasMore, rows }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|   | ||||
| @@ -15,7 +15,12 @@ const { EMAIL_INFO, FRONT_END_HOST } = require('../assets/config/config') | ||||
| const transporter = require('../assets/config/transporter') | ||||
| const { emailQuoteTemplate } = require('../assets/config/emails') | ||||
|  | ||||
| const handleEditFunction = async (res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName = false) => { | ||||
| const handleEditFunction = async ( | ||||
|   res, | ||||
|   resultFunction, | ||||
|   { title, slug, description, type, categorieId, isOnline }, | ||||
|   imageName = false | ||||
| ) => { | ||||
|   resultFunction.title = title | ||||
|   resultFunction.slug = slug | ||||
|   resultFunction.description = description | ||||
| @@ -32,23 +37,41 @@ const handleEditFunction = async (res, resultFunction, { title, slug, descriptio | ||||
| exports.getFunctions = async (req, res, next) => { | ||||
|   const categoryId = helperQueryNumber(req.query.categoryId, 0) | ||||
|   let search = req.query.search | ||||
|   try { search = search.toLowerCase() } catch {}; | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       // Trie par catégorie | ||||
|       ...(categoryId !== 0) && { categorieId: categoryId }, | ||||
|       ...(categoryId !== 0 && { categorieId: categoryId }), | ||||
|       // Recherche | ||||
|       ...(search != null) && { | ||||
|       ...(search != null && { | ||||
|         [Sequelize.Op.or]: [ | ||||
|           { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, | ||||
|           { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, | ||||
|           { description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } | ||||
|           { | ||||
|             title: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('title')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             slug: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('slug')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             description: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('description')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|       }) | ||||
|     }, | ||||
|     include: [ | ||||
|       { model: Categories, attributes: ['name', 'color'] } | ||||
|     ], | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }], | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|     }, | ||||
| @@ -61,18 +84,21 @@ exports.getFunctionBySlug = (req, res, next) => { | ||||
|   const { slug } = req.params | ||||
|   Functions.findOne({ | ||||
|     where: { slug }, | ||||
|     include: [ | ||||
|       { model: Categories, attributes: ['name', 'color'] } | ||||
|     ] | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }] | ||||
|   }) | ||||
|     .then((result) => { | ||||
|     .then(result => { | ||||
|       if (!result) { | ||||
|         return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|         return errorHandling(next, { | ||||
|           message: "La fonction n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|       } | ||||
|       try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {} | ||||
|       try { | ||||
|         result.utilizationForm = JSON.parse(result.utilizationForm) | ||||
|       } catch {} | ||||
|       return res.status(200).json(result) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| @@ -83,28 +109,49 @@ exports.postFunction = (req, res, next) => { | ||||
|   const image = req.files.image | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   if ((!image || image.truncated) && ( | ||||
|     image.mimetype !== 'image/png' || | ||||
|         image.mimetype !== 'image/jpg' || | ||||
|         image.mimetype !== 'image/jpeg' | ||||
|   )) { | ||||
|     return errorHandling(next, { message: 'La fonction doit avoir une image valide.', statusCode: 400 }) | ||||
|   if ( | ||||
|     (!image || image.truncated) && | ||||
|     (image.mimetype !== 'image/png' || | ||||
|       image.mimetype !== 'image/jpg' || | ||||
|       image.mimetype !== 'image/jpeg') | ||||
|   ) { | ||||
|     return errorHandling(next, { | ||||
|       message: 'La fonction doit avoir une image valide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   const splitedImageName = image.name.split('.') | ||||
|   if (splitedImageName.length !== 2) return errorHandling(next, serverError) | ||||
|   const imageName = slug + '.' + splitedImageName[1] | ||||
|   image.mv(path.join(__dirname, '..', 'assets', 'images', 'functions') + '/' + imageName, async (error) => { | ||||
|     if (error) return errorHandling(next, serverError) | ||||
|     try { | ||||
|       const result = await Functions.create({ title, slug, description, type, categorieId, image: `/images/functions/${imageName}` }) | ||||
|       return res.status(201).json({ message: 'La fonction a été correctement ajouté!', result }) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|   image.mv( | ||||
|     path.join(__dirname, '..', 'assets', 'images', 'functions') + | ||||
|       '/' + | ||||
|       imageName, | ||||
|     async error => { | ||||
|       if (error) return errorHandling(next, serverError) | ||||
|       try { | ||||
|         const result = await Functions.create({ | ||||
|           title, | ||||
|           slug, | ||||
|           description, | ||||
|           type, | ||||
|           categorieId, | ||||
|           image: `/images/functions/${imageName}` | ||||
|         }) | ||||
|         return res | ||||
|           .status(201) | ||||
|           .json({ message: 'La fonction a été correctement ajouté!', result }) | ||||
|       } catch (error) { | ||||
|         console.log(error) | ||||
|         return errorHandling(next, serverError) | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
|   ) | ||||
| } | ||||
|  | ||||
| exports.putFunction = async (req, res, next) => { | ||||
| @@ -113,43 +160,74 @@ exports.putFunction = async (req, res, next) => { | ||||
|   const image = req.files.image | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Vérifie si le slug existe déjà | ||||
|     const FunctionSlug = await Functions.findOne({ where: { slug } }) | ||||
|     if (!FunctionSlug && FunctionSlug.id !== resultFunction.id) { | ||||
|       return errorHandling(next, { message: 'Le slug existe déjà...', statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'Le slug existe déjà...', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     // Sauvegarde de la fonction | ||||
|     if (image != null) { | ||||
|       if (image.truncated && ( | ||||
|         image.mimetype !== 'image/png' || | ||||
|                 image.mimetype !== 'image/jpg' || | ||||
|                 image.mimetype !== 'image/jpeg' | ||||
|       )) { | ||||
|         return errorHandling(next, { message: 'La fonction doit avoir une image valide.', statusCode: 400 }) | ||||
|       if ( | ||||
|         image.truncated && | ||||
|         (image.mimetype !== 'image/png' || | ||||
|           image.mimetype !== 'image/jpg' || | ||||
|           image.mimetype !== 'image/jpeg') | ||||
|       ) { | ||||
|         return errorHandling(next, { | ||||
|           message: 'La fonction doit avoir une image valide.', | ||||
|           statusCode: 400 | ||||
|         }) | ||||
|       } | ||||
|       const splitedImageName = image.name.split('.') | ||||
|       if (splitedImageName.length !== 2) return errorHandling(next, serverError) | ||||
|       const imageName = slug + '.' + splitedImageName[1] | ||||
|       // Supprime les anciennes images | ||||
|       const functionPath = path.join(__dirname, '..', 'assets', 'images', 'functions') | ||||
|       const functionPath = path.join( | ||||
|         __dirname, | ||||
|         '..', | ||||
|         'assets', | ||||
|         'images', | ||||
|         'functions' | ||||
|       ) | ||||
|       deleteFilesNameStartWith(slug, functionPath, () => { | ||||
|         image.mv(path.join(functionPath, imageName), async (error) => { | ||||
|         image.mv(path.join(functionPath, imageName), async error => { | ||||
|           if (error) return errorHandling(next, serverError) | ||||
|           return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }, imageName) | ||||
|           return await handleEditFunction( | ||||
|             res, | ||||
|             resultFunction, | ||||
|             { title, slug, description, type, categorieId, isOnline }, | ||||
|             imageName | ||||
|           ) | ||||
|         }) | ||||
|       }) | ||||
|     } else { | ||||
|       return await handleEditFunction(res, resultFunction, { title, slug, description, type, categorieId, isOnline }) | ||||
|       return await handleEditFunction(res, resultFunction, { | ||||
|         title, | ||||
|         slug, | ||||
|         description, | ||||
|         type, | ||||
|         categorieId, | ||||
|         isOnline | ||||
|       }) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
| @@ -165,7 +243,10 @@ exports.putFunctionArticle = async (req, res, next) => { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     resultFunction.article = article | ||||
|     const result = await resultFunction.save() | ||||
| @@ -184,7 +265,10 @@ exports.putFunctionForm = async (req, res, next) => { | ||||
|     // Vérifie si la fonction existe | ||||
|     const resultFunction = await Functions.findOne({ where: { id } }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     resultFunction.utilizationForm = JSON.stringify(form) | ||||
|     const result = await resultFunction.save() | ||||
| @@ -200,14 +284,19 @@ exports.deleteFunction = async (req, res, next) => { | ||||
|   try { | ||||
|     const result = await Functions.findOne({ where: { id } }) | ||||
|     if (!result) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     if (result.image !== '/images/functions/default.png') { | ||||
|       const filePath = path.join(__dirname, '..', 'assets', result.image) | ||||
|       fs.unlinkSync(filePath) // supprime le fichier | ||||
|     } | ||||
|     await result.destroy() | ||||
|     res.status(200).json({ message: 'La fonction a été correctement supprimé!' }) | ||||
|     res | ||||
|       .status(200) | ||||
|       .json({ message: 'La fonction a été correctement supprimé!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -217,11 +306,15 @@ exports.deleteFunction = async (req, res, next) => { | ||||
| exports.postCategory = async (req, res, next) => { | ||||
|   const { name, color } = req.body | ||||
|   if (!(name && color)) { | ||||
|     return errorHandling(next, { message: 'La catégorie doit avoir un nom et une couleur.' }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'La catégorie doit avoir un nom et une couleur.' | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const result = await Categories.create({ name, color }) | ||||
|     return res.status(201).json({ message: 'La catégorie a bien été crée!', result }) | ||||
|     return res | ||||
|       .status(201) | ||||
|       .json({ message: 'La catégorie a bien été crée!', result }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -232,7 +325,9 @@ exports.putCategory = async (req, res, next) => { | ||||
|   const { name, color } = req.body | ||||
|   const { id } = req.params | ||||
|   if (!(name && color && id)) { | ||||
|     return errorHandling(next, { message: 'La catégorie doit avoir un nom, une couleur et un id.' }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'La catégorie doit avoir un nom, une couleur et un id.' | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const category = await Categories.findOne({ where: { id } }) | ||||
| @@ -242,7 +337,9 @@ exports.putCategory = async (req, res, next) => { | ||||
|     category.name = name | ||||
|     category.color = color | ||||
|     const result = await category.save() | ||||
|     return res.status(200).json({ message: 'La catégorie a bien été modifiée!', result }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La catégorie a bien été modifiée!', result }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -257,7 +354,9 @@ exports.deleteCategory = async (req, res, next) => { | ||||
|       return errorHandling(next, { message: "La catégorie n'existe pas." }) | ||||
|     } | ||||
|     await category.destroy() | ||||
|     return res.status(200).json({ message: 'La catégorie a bien été supprimée!' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La catégorie a bien été supprimée!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -269,9 +368,7 @@ exports.getQuotes = async (req, res, next) => { | ||||
|     where: { | ||||
|       isValidated: 0 | ||||
|     }, | ||||
|     include: [ | ||||
|       { model: Users, attributes: ['name', 'logo'] } | ||||
|     ], | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Quotes, options) | ||||
| @@ -282,19 +379,23 @@ exports.putQuote = async (req, res, next) => { | ||||
|   const { isValid } = req.body | ||||
|   try { | ||||
|     if (typeof isValid !== 'boolean') { | ||||
|       return errorHandling(next, { message: 'isValid doit être un booléen.', statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'isValid doit être un booléen.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const quote = await Quotes.findOne({ | ||||
|       where: { | ||||
|         id, | ||||
|         isValidated: 0 | ||||
|       }, | ||||
|       include: [ | ||||
|         { model: Users, attributes: ['name', 'email'] } | ||||
|       ] | ||||
|       include: [{ model: Users, attributes: ['name', 'email'] }] | ||||
|     }) | ||||
|     if (!quote) { | ||||
|       return errorHandling(next, { message: "La citation n'existe pas (ou est déjà validé).", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La citation n'existe pas (ou est déjà validé).", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     await transporter.sendMail({ | ||||
| @@ -307,10 +408,14 @@ exports.putQuote = async (req, res, next) => { | ||||
|     if (isValid) { | ||||
|       quote.isValidated = true | ||||
|       await quote.save() | ||||
|       return res.status(200).json({ message: 'La citation a bien été validée!' }) | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ message: 'La citation a bien été validée!' }) | ||||
|     } else { | ||||
|       await quote.destroy() | ||||
|       return res.status(200).json({ imessage: 'La citation a bien été supprimée!' }) | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ imessage: 'La citation a bien été supprimée!' }) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|   | ||||
| @@ -4,10 +4,10 @@ const { serverError } = require('../assets/config/errors') | ||||
|  | ||||
| exports.getCategories = (_req, res, next) => { | ||||
|   Categories.findAll() | ||||
|     .then((result) => { | ||||
|     .then(result => { | ||||
|       res.status(200).json(result) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
|   | ||||
| @@ -9,9 +9,7 @@ exports.getCommentsByFunctionId = async (req, res, next) => { | ||||
|   const { functionId } = req.params | ||||
|   const options = { | ||||
|     where: { functionId }, | ||||
|     include: [ | ||||
|       { model: Users, attributes: ['name', 'logo'] } | ||||
|     ], | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
|   return await getPagesHelper({ req, res, next }, Comments, options) | ||||
| @@ -21,14 +19,26 @@ exports.postCommentsByFunctionId = async (req, res, next) => { | ||||
|   const { functionId } = req.params | ||||
|   const { message } = req.body | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ where: { id: functionId } }) | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     if (!message) { | ||||
|       return errorHandling(next, { message: 'Vous ne pouvez pas poster de commentaire vide.', statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'Vous ne pouvez pas poster de commentaire vide.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const comment = await Comments.create({ message, userId: req.userId, functionId }) | ||||
|     const comment = await Comments.create({ | ||||
|       message, | ||||
|       userId: req.userId, | ||||
|       functionId | ||||
|     }) | ||||
|     return res.status(201).json(comment) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
| @@ -39,12 +49,19 @@ exports.postCommentsByFunctionId = async (req, res, next) => { | ||||
| exports.deleteCommentById = async (req, res, next) => { | ||||
|   const { commentId } = req.params | ||||
|   try { | ||||
|     const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }) | ||||
|     const comment = await Comments.findOne({ | ||||
|       where: { userId: req.userId, id: parseInt(commentId) } | ||||
|     }) | ||||
|     if (!comment) { | ||||
|       return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le commentaire n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     await comment.destroy() | ||||
|     return res.status(200).json({ message: 'Le commentaire a bien été supprimé.' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le commentaire a bien été supprimé.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -55,16 +72,26 @@ exports.putCommentsById = async (req, res, next) => { | ||||
|   const { commentId } = req.params | ||||
|   const { message } = req.body | ||||
|   if (!message) { | ||||
|     return errorHandling(next, { message: 'Vous ne pouvez pas poster de commentaire vide.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous ne pouvez pas poster de commentaire vide.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const comment = await Comments.findOne({ where: { userId: req.userId, id: parseInt(commentId) } }) | ||||
|     const comment = await Comments.findOne({ | ||||
|       where: { userId: req.userId, id: parseInt(commentId) } | ||||
|     }) | ||||
|     if (!comment) { | ||||
|       return errorHandling(next, { message: "Le commentaire n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le commentaire n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     comment.message = message | ||||
|     await comment.save() | ||||
|     return res.status(200).json({ message: 'Le commentaire a bien été modifié.' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le commentaire a bien été modifié.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   | ||||
| @@ -27,9 +27,14 @@ exports.postFavoriteByFunctionId = async (req, res, next) => { | ||||
|   const { functionId } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ where: { id: functionId } }) | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     const favorite = await Favorites.findOne({ | ||||
|       where: { | ||||
| @@ -41,7 +46,10 @@ exports.postFavoriteByFunctionId = async (req, res, next) => { | ||||
|       await Favorites.create({ userId, functionId }) | ||||
|       return res.status(201).json({ result: 'Le favoris a bien été ajouté!' }) | ||||
|     } | ||||
|     return errorHandling(next, { message: 'La fonction est déjà en favoris.', statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'La fonction est déjà en favoris.', | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -52,9 +60,14 @@ exports.deleteFavoriteByFunctionId = async (req, res, next) => { | ||||
|   const { functionId } = req.params | ||||
|   const { userId } = req | ||||
|   try { | ||||
|     const resultFunction = await Functions.findOne({ where: { id: functionId } }) | ||||
|     const resultFunction = await Functions.findOne({ | ||||
|       where: { id: functionId } | ||||
|     }) | ||||
|     if (!resultFunction) { | ||||
|       return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "La fonction n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     const favorite = await Favorites.findOne({ | ||||
|       where: { | ||||
| @@ -63,10 +76,15 @@ exports.deleteFavoriteByFunctionId = async (req, res, next) => { | ||||
|       } | ||||
|     }) | ||||
|     if (!favorite) { | ||||
|       return errorHandling(next, { message: "Le fonction n'est pas en favoris.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le fonction n'est pas en favoris.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     await favorite.destroy() | ||||
|     return res.status(200).json({ message: 'Le fonction a bien été supprimé des favoris.' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'Le fonction a bien été supprimé des favoris.' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   | ||||
| @@ -10,24 +10,42 @@ const Sequelize = require('sequelize') | ||||
| exports.getFunctions = async (req, res, next) => { | ||||
|   const categoryId = helperQueryNumber(req.query.categoryId, 0) | ||||
|   let { search } = req.query | ||||
|   try { search = search.toLowerCase() } catch {}; | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       isOnline: 1, | ||||
|       // Trie par catégorie | ||||
|       ...(categoryId !== 0) && { categorieId: categoryId }, | ||||
|       ...(categoryId !== 0 && { categorieId: categoryId }), | ||||
|       // Recherche | ||||
|       ...(search != null) && { | ||||
|       ...(search != null && { | ||||
|         [Sequelize.Op.or]: [ | ||||
|           { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', `%${search}%`) }, | ||||
|           { slug: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('slug')), 'LIKE', `%${search}%`) }, | ||||
|           { description: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('description')), 'LIKE', `%${search}%`) } | ||||
|           { | ||||
|             title: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('title')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             slug: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('slug')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           }, | ||||
|           { | ||||
|             description: Sequelize.where( | ||||
|               Sequelize.fn('LOWER', Sequelize.col('description')), | ||||
|               'LIKE', | ||||
|               `%${search}%` | ||||
|             ) | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|       }) | ||||
|     }, | ||||
|     include: [ | ||||
|       { model: Categories, attributes: ['name', 'color'] } | ||||
|     ], | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }], | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|     }, | ||||
| @@ -43,18 +61,21 @@ exports.getFunctionBySlug = (req, res, next) => { | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'isOnline'] | ||||
|     }, | ||||
|     include: [ | ||||
|       { model: Categories, attributes: ['name', 'color'] } | ||||
|     ] | ||||
|     include: [{ model: Categories, attributes: ['name', 'color'] }] | ||||
|   }) | ||||
|     .then((result) => { | ||||
|     .then(result => { | ||||
|       if (!result) { | ||||
|         return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|         return errorHandling(next, { | ||||
|           message: "La fonction n'existe pas.", | ||||
|           statusCode: 404 | ||||
|         }) | ||||
|       } | ||||
|       try { result.utilizationForm = JSON.parse(result.utilizationForm) } catch {} | ||||
|       try { | ||||
|         result.utilizationForm = JSON.parse(result.utilizationForm) | ||||
|       } catch {} | ||||
|       return res.status(200).json(result) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
| @@ -65,5 +86,8 @@ exports.executeFunctionBySlug = (req, res, next) => { | ||||
|   if (functionOutput !== undefined) { | ||||
|     return functionOutput({ res, next }, req.body) | ||||
|   } | ||||
|   return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }) | ||||
|   return errorHandling(next, { | ||||
|     message: "La fonction n'existe pas.", | ||||
|     statusCode: 404 | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -9,9 +9,7 @@ exports.getQuotes = async (req, res, next) => { | ||||
|     where: { | ||||
|       isValidated: 1 | ||||
|     }, | ||||
|     include: [ | ||||
|       { model: Users, attributes: ['name', 'logo'] } | ||||
|     ], | ||||
|     include: [{ model: Users, attributes: ['name', 'logo'] }], | ||||
|     attributes: { | ||||
|       exclude: ['isValidated'] | ||||
|     }, | ||||
| @@ -27,10 +25,15 @@ exports.postQuote = (req, res, next) => { | ||||
|     return errorHandling(next, requiredFields) | ||||
|   } | ||||
|   Quotes.create({ quote, author, userId: req.userId }) | ||||
|     .then((_result) => { | ||||
|       return res.status(200).json({ message: "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." }) | ||||
|     .then(_result => { | ||||
|       return res | ||||
|         .status(200) | ||||
|         .json({ | ||||
|           message: | ||||
|             "La citation a bien été ajoutée, elle est en attente de confirmation d'un administrateur." | ||||
|         }) | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
|   | ||||
| @@ -36,12 +36,20 @@ exports.putTask = async (req, res, next) => { | ||||
|   const { isCompleted } = req.body | ||||
|   try { | ||||
|     if (typeof isCompleted !== 'boolean') { | ||||
|       return errorHandling(next, { message: 'isCompleted doit être un booléen.', statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'isCompleted doit être un booléen.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }) | ||||
|     const taskResult = await Tasks.findOne({ | ||||
|       where: { id, userId: req.userId } | ||||
|     }) | ||||
|     if (!taskResult) { | ||||
|       return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'La "tâche à faire" n\'existe pas.', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     taskResult.isCompleted = isCompleted | ||||
|     const taskSaved = await taskResult.save() | ||||
| @@ -55,12 +63,19 @@ exports.putTask = async (req, res, next) => { | ||||
| exports.deleteTask = async (req, res, next) => { | ||||
|   const { id } = req.params | ||||
|   try { | ||||
|     const taskResult = await Tasks.findOne({ where: { id, userId: req.userId } }) | ||||
|     const taskResult = await Tasks.findOne({ | ||||
|       where: { id, userId: req.userId } | ||||
|     }) | ||||
|     if (!taskResult) { | ||||
|       return errorHandling(next, { message: 'La "tâche à faire" n\'existe pas.', statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: 'La "tâche à faire" n\'existe pas.', | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     await taskResult.destroy() | ||||
|     return res.status(200).json({ message: 'La "tâche à faire" a bien été supprimée!' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ message: 'La "tâche à faire" a bien été supprimée!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
|   | ||||
| @@ -7,7 +7,13 @@ const uuid = require('uuid') | ||||
| const Sequelize = require('sequelize') | ||||
| const errorHandling = require('../assets/utils/errorHandling') | ||||
| const { serverError, generalError } = require('../assets/config/errors') | ||||
| const { JWT_SECRET, FRONT_END_HOST, EMAIL_INFO, HOST, TOKEN_LIFE } = require('../assets/config/config') | ||||
| const { | ||||
|   JWT_SECRET, | ||||
|   FRONT_END_HOST, | ||||
|   EMAIL_INFO, | ||||
|   HOST, | ||||
|   TOKEN_LIFE | ||||
| } = require('../assets/config/config') | ||||
| const transporter = require('../assets/config/transporter') | ||||
| const { emailUserTemplate } = require('../assets/config/emails') | ||||
| const Users = require('../models/users') | ||||
| @@ -19,7 +25,12 @@ const Quotes = require('../models/quotes') | ||||
| const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameStartWith') | ||||
| const getPagesHelper = require('../assets/utils/getPagesHelper') | ||||
|  | ||||
| async function handleEditUser (res, { name, email, biography, isPublicEmail }, userId, logoName) { | ||||
| async function handleEditUser ( | ||||
|   res, | ||||
|   { name, email, biography, isPublicEmail }, | ||||
|   userId, | ||||
|   logoName | ||||
| ) { | ||||
|   const user = await Users.findOne({ where: { id: userId } }) | ||||
|   user.name = name | ||||
|   if (user.email !== email) { | ||||
| @@ -31,7 +42,12 @@ async function handleEditUser (res, { name, email, biography, isPublicEmail }, u | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: "FunctionProject - Confirmer l'email", | ||||
|       html: emailUserTemplate("Veuillez confirmer l'email", 'Oui, je confirme.', `${HOST}/users/confirm-email/${tempToken}`, 'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre email ne serez pas confirmé si vous ne cliquez pas sur le lien de confirmation ci-dessus.') | ||||
|       html: emailUserTemplate( | ||||
|         "Veuillez confirmer l'email", | ||||
|         'Oui, je confirme.', | ||||
|         `${HOST}/users/confirm-email/${tempToken}`, | ||||
|         'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre email ne serez pas confirmé si vous ne cliquez pas sur le lien de confirmation ci-dessus.' | ||||
|       ) | ||||
|     }) | ||||
|   } | ||||
|   if (biography != null) { | ||||
| @@ -42,22 +58,48 @@ async function handleEditUser (res, { name, email, biography, isPublicEmail }, u | ||||
|     user.logo = `/images/users/${logoName}` | ||||
|   } | ||||
|   await user.save() | ||||
|   return res.status(200).json({ id: user.id, name: user.name, email: user.email, biography: user.biography, logo: user.logo, isPublicEmail: user.isPublicEmail, isAdmin: user.isAdmin, createdAt: user.createdAt }) | ||||
|   return res | ||||
|     .status(200) | ||||
|     .json({ | ||||
|       id: user.id, | ||||
|       name: user.name, | ||||
|       email: user.email, | ||||
|       biography: user.biography, | ||||
|       logo: user.logo, | ||||
|       isPublicEmail: user.isPublicEmail, | ||||
|       isAdmin: user.isAdmin, | ||||
|       createdAt: user.createdAt | ||||
|     }) | ||||
| } | ||||
|  | ||||
| exports.getUsers = async (req, res, next) => { | ||||
|   let { search } = req.query | ||||
|   try { search = search.toLowerCase() } catch {}; | ||||
|   try { | ||||
|     search = search.toLowerCase() | ||||
|   } catch {} | ||||
|   const options = { | ||||
|     where: { | ||||
|       isConfirmed: true, | ||||
|       // Recherche | ||||
|       ...(search != null) && { | ||||
|         name: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', `%${search}%`) | ||||
|       } | ||||
|       ...(search != null && { | ||||
|         name: Sequelize.where( | ||||
|           Sequelize.fn('LOWER', Sequelize.col('name')), | ||||
|           'LIKE', | ||||
|           `%${search}%` | ||||
|         ) | ||||
|       }) | ||||
|     }, | ||||
|     attributes: { | ||||
|       exclude: ['updatedAt', 'isAdmin', 'isConfirmed', 'password', 'tempToken', 'tempExpirationToken', 'isPublicEmail', 'email'] | ||||
|       exclude: [ | ||||
|         'updatedAt', | ||||
|         'isAdmin', | ||||
|         'isConfirmed', | ||||
|         'password', | ||||
|         'tempToken', | ||||
|         'tempExpirationToken', | ||||
|         'isPublicEmail', | ||||
|         'email' | ||||
|       ] | ||||
|     }, | ||||
|     order: [['createdAt', 'DESC']] | ||||
|   } | ||||
| @@ -69,35 +111,60 @@ exports.putUser = async (req, res, next) => { | ||||
|   const logo = req.files.logo | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   if (logo != null) { | ||||
|     if ((!logo || logo.truncated) && ( | ||||
|       logo.mimetype !== 'image/png' || | ||||
|             logo.mimetype !== 'image/jpg' || | ||||
|             logo.mimetype !== 'image/jpeg' || | ||||
|             logo.mimetype !== 'image/gif' | ||||
|     )) { | ||||
|       return errorHandling(next, { message: 'Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.', statusCode: 400 }) | ||||
|     if ( | ||||
|       (!logo || logo.truncated) && | ||||
|       (logo.mimetype !== 'image/png' || | ||||
|         logo.mimetype !== 'image/jpg' || | ||||
|         logo.mimetype !== 'image/jpeg' || | ||||
|         logo.mimetype !== 'image/gif') | ||||
|     ) { | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           'Le profil doit avoir une image valide (PNG, JPG, GIF) et moins de 5mo.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const splitedLogoName = logo.name.split('.') | ||||
|     if (splitedLogoName.length !== 2) return errorHandling(next, serverError) | ||||
|     const logoName = name + req.userId + '.' + splitedLogoName[1] | ||||
|     // Supprime les anciens logo | ||||
|     try { | ||||
|       deleteFilesNameStartWith(`${name + req.userId}`, path.join(__dirname, '..', 'assets', 'images', 'users'), async () => { | ||||
|         logo.mv(path.join(__dirname, '..', 'assets', 'images', 'users', logoName), async (error) => { | ||||
|           if (error) return errorHandling(next, serverError) | ||||
|           return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, logoName) | ||||
|         }) | ||||
|       }) | ||||
|       deleteFilesNameStartWith( | ||||
|         `${name + req.userId}`, | ||||
|         path.join(__dirname, '..', 'assets', 'images', 'users'), | ||||
|         async () => { | ||||
|           logo.mv( | ||||
|             path.join(__dirname, '..', 'assets', 'images', 'users', logoName), | ||||
|             async error => { | ||||
|               if (error) return errorHandling(next, serverError) | ||||
|               return await handleEditUser( | ||||
|                 res, | ||||
|                 { name, email, biography, isPublicEmail }, | ||||
|                 req.userId, | ||||
|                 logoName | ||||
|               ) | ||||
|             } | ||||
|           ) | ||||
|         } | ||||
|       ) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     } | ||||
|   } else { | ||||
|     try { | ||||
|       return await handleEditUser(res, { name, email, biography, isPublicEmail }, req.userId, null) | ||||
|       return await handleEditUser( | ||||
|         res, | ||||
|         { name, email, biography, isPublicEmail }, | ||||
|         req.userId, | ||||
|         null | ||||
|       ) | ||||
|     } catch (error) { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
| @@ -109,7 +176,10 @@ exports.register = async (req, res, next) => { | ||||
|   const { name, email, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const hashedPassword = await bcrypt.hash(password, 12) | ||||
| @@ -119,9 +189,19 @@ exports.register = async (req, res, next) => { | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: "FunctionProject - Confirmer l'inscription", | ||||
|       html: emailUserTemplate("Veuillez confirmer l'inscription", "Oui, je m'inscris.", `${HOST}/users/confirm-email/${tempToken}`, 'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Vous ne serez pas inscrit si vous ne cliquez pas sur le lien de confirmation ci-dessus.') | ||||
|       html: emailUserTemplate( | ||||
|         "Veuillez confirmer l'inscription", | ||||
|         "Oui, je m'inscris.", | ||||
|         `${HOST}/users/confirm-email/${tempToken}`, | ||||
|         'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Vous ne serez pas inscrit si vous ne cliquez pas sur le lien de confirmation ci-dessus.' | ||||
|       ) | ||||
|     }) | ||||
|     return res.status(201).json({ result: "Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription." }) | ||||
|     return res | ||||
|       .status(201) | ||||
|       .json({ | ||||
|         result: | ||||
|           "Vous y êtes presque, veuillez vérifier vos emails pour confirmer l'inscription." | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -132,24 +212,55 @@ exports.login = async (req, res, next) => { | ||||
|   const { email, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { email } }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const isEqual = await bcrypt.compare(password, user.password) | ||||
|     if (!isEqual) { | ||||
|       return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     if (!user.isConfirmed) { | ||||
|       return errorHandling(next, { message: 'Vous devez valider votre adresse email pour votre première connexion.', statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           'Vous devez valider votre adresse email pour votre première connexion.', | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const token = jwt.sign({ | ||||
|       email: user.email, userId: user.id | ||||
|     }, JWT_SECRET, { expiresIn: TOKEN_LIFE }) | ||||
|     return res.status(200).json({ token, id: user.id, name: user.name, email: user.email, biography: user.biography, logo: user.logo, isPublicEmail: user.isPublicEmail, isAdmin: user.isAdmin, createdAt: user.createdAt, expiresIn: Math.round(ms(TOKEN_LIFE) / 1000) }) | ||||
|     const token = jwt.sign( | ||||
|       { | ||||
|         email: user.email, | ||||
|         userId: user.id | ||||
|       }, | ||||
|       JWT_SECRET, | ||||
|       { expiresIn: TOKEN_LIFE } | ||||
|     ) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ | ||||
|         token, | ||||
|         id: user.id, | ||||
|         name: user.name, | ||||
|         email: user.email, | ||||
|         biography: user.biography, | ||||
|         logo: user.logo, | ||||
|         isPublicEmail: user.isPublicEmail, | ||||
|         isAdmin: user.isAdmin, | ||||
|         createdAt: user.createdAt, | ||||
|         expiresIn: Math.round(ms(TOKEN_LIFE) / 1000) | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -162,9 +273,14 @@ exports.confirmEmail = async (req, res, next) => { | ||||
|     return errorHandling(next, generalError) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { tempToken, isConfirmed: false } }) | ||||
|     const user = await Users.findOne({ | ||||
|       where: { tempToken, isConfirmed: false } | ||||
|     }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le token n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     user.tempToken = null | ||||
|     user.isConfirmed = true | ||||
| @@ -180,12 +296,19 @@ exports.resetPassword = async (req, res, next) => { | ||||
|   const { email } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { email, tempToken: null } }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { message: "L'adresse email n'existe pas ou une demande est déjà en cours.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: | ||||
|           "L'adresse email n'existe pas ou une demande est déjà en cours.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const tempToken = uuid.v4() | ||||
|     user.tempExpirationToken = Date.now() + 3600000 // 1 heure | ||||
| @@ -195,9 +318,19 @@ exports.resetPassword = async (req, res, next) => { | ||||
|       from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, | ||||
|       to: email, | ||||
|       subject: 'FunctionProject - Réinitialisation du mot de passe', | ||||
|       html: emailUserTemplate('Veuillez confirmer la réinitialisation du mot de passe', 'Oui, je change mon mot de passe.', `${FRONT_END_HOST}/users/newPassword?token=${tempToken}`, 'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.') | ||||
|       html: emailUserTemplate( | ||||
|         'Veuillez confirmer la réinitialisation du mot de passe', | ||||
|         'Oui, je change mon mot de passe.', | ||||
|         `${FRONT_END_HOST}/users/newPassword?token=${tempToken}`, | ||||
|         'Si vous avez reçu ce message par erreur, il suffit de le supprimer. Votre mot de passe ne sera pas réinitialiser si vous ne cliquez pas sur le lien ci-dessus. Par ailleurs, pour la sécurité de votre compte, la réinitialisation du mot de passe est disponible pendant un délai de 1 heure, passez ce temps, la réinitialisation ne sera plus valide.' | ||||
|       ) | ||||
|     }) | ||||
|     return res.status(200).json({ result: 'Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ | ||||
|         result: | ||||
|           'Demande de réinitialisation du mot de passe réussi, veuillez vérifier vos emails!' | ||||
|       }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -208,19 +341,27 @@ exports.newPassword = async (req, res, next) => { | ||||
|   const { tempToken, password } = req.body | ||||
|   const errors = validationResult(req) | ||||
|   if (!errors.isEmpty()) { | ||||
|     return errorHandling(next, { message: errors.array()[0].msg, statusCode: 400 }) | ||||
|     return errorHandling(next, { | ||||
|       message: errors.array()[0].msg, | ||||
|       statusCode: 400 | ||||
|     }) | ||||
|   } | ||||
|   try { | ||||
|     const user = await Users.findOne({ where: { tempToken } }) | ||||
|     if (!user && parseInt(user.tempExpirationToken) < Date.now()) { | ||||
|       return errorHandling(next, { message: "Le token n'est pas valide.", statusCode: 400 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "Le token n'est pas valide.", | ||||
|         statusCode: 400 | ||||
|       }) | ||||
|     } | ||||
|     const hashedPassword = await bcrypt.hash(password, 12) | ||||
|     user.password = hashedPassword | ||||
|     user.tempToken = null | ||||
|     user.tempExpirationToken = null | ||||
|     await user.save() | ||||
|     return res.status(200).json({ result: 'Le mot de passe a bien été modifié!' }) | ||||
|     return res | ||||
|       .status(200) | ||||
|       .json({ result: 'Le mot de passe a bien été modifié!' }) | ||||
|   } catch (error) { | ||||
|     console.log(error) | ||||
|     return errorHandling(next, serverError) | ||||
| @@ -233,30 +374,51 @@ exports.getUserInfo = async (req, res, next) => { | ||||
|     const user = await Users.findOne({ | ||||
|       where: { name, isConfirmed: true }, | ||||
|       attributes: { | ||||
|         exclude: ['updatedAt', 'isAdmin', 'isConfirmed', 'password', 'tempToken', 'tempExpirationToken'] | ||||
|         exclude: [ | ||||
|           'updatedAt', | ||||
|           'isAdmin', | ||||
|           'isConfirmed', | ||||
|           'password', | ||||
|           'tempToken', | ||||
|           'tempExpirationToken' | ||||
|         ] | ||||
|       } | ||||
|     }) | ||||
|     if (!user) { | ||||
|       return errorHandling(next, { message: "L'utilisateur n'existe pas.", statusCode: 404 }) | ||||
|       return errorHandling(next, { | ||||
|         message: "L'utilisateur n'existe pas.", | ||||
|         statusCode: 404 | ||||
|       }) | ||||
|     } | ||||
|     const favorites = await Favorites.findAll({ | ||||
|       where: { userId: user.id }, | ||||
|       include: [ | ||||
|         { model: Functions, attributes: { exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] }, include: { model: Categories, attributes: ['name', 'color'] } } | ||||
|         { | ||||
|           model: Functions, | ||||
|           attributes: { | ||||
|             exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|           }, | ||||
|           include: { model: Categories, attributes: ['name', 'color'] } | ||||
|         } | ||||
|       ], | ||||
|       order: [['createdAt', 'DESC']], | ||||
|       limit: 5 | ||||
|     }) | ||||
|     const favoritesArray = favorites.map((favorite) => favorite.function) | ||||
|     const favoritesArray = favorites.map(favorite => favorite.function) | ||||
|     const comments = await Comments.findAll({ | ||||
|       where: { userId: user.id }, | ||||
|       include: [ | ||||
|         { model: Functions, attributes: { exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] } } | ||||
|         { | ||||
|           model: Functions, | ||||
|           attributes: { | ||||
|             exclude: ['updatedAt', 'utilizationForm', 'article', 'isOnline'] | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       order: [['createdAt', 'DESC']], | ||||
|       limit: 5 | ||||
|     }) | ||||
|     const commentsArray = comments.map((commentObject) => { | ||||
|     const commentsArray = comments.map(commentObject => { | ||||
|       return { | ||||
|         id: commentObject.id, | ||||
|         message: commentObject.message, | ||||
| @@ -274,7 +436,7 @@ exports.getUserInfo = async (req, res, next) => { | ||||
|     }) | ||||
|     const userObject = { | ||||
|       // Si Public Email | ||||
|       ...(user.isPublicEmail) && { email: user.email }, | ||||
|       ...(user.isPublicEmail && { email: user.email }), | ||||
|       isPublicEmail: user.isPublicEmail, | ||||
|       name: user.name, | ||||
|       biography: user.biography, | ||||
|   | ||||
| @@ -4,19 +4,28 @@ const Users = require('../models/users') | ||||
|  | ||||
| module.exports = (req, _res, next) => { | ||||
|   if (!req.userId) { | ||||
|     return errorHandling(next, { message: "Vous n'êtes pas connecté.", statusCode: 403 }) | ||||
|     return errorHandling(next, { | ||||
|       message: "Vous n'êtes pas connecté.", | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|   Users.findOne({ where: { id: req.userId } }) | ||||
|     .then((user) => { | ||||
|     .then(user => { | ||||
|       if (!user) { | ||||
|         return errorHandling(next, { message: "Le mot de passe ou l'adresse email n'est pas valide.", statusCode: 403 }) | ||||
|         return errorHandling(next, { | ||||
|           message: "Le mot de passe ou l'adresse email n'est pas valide.", | ||||
|           statusCode: 403 | ||||
|         }) | ||||
|       } | ||||
|       if (!user.isAdmin) { | ||||
|         return errorHandling(next, { message: "Vous n'êtes pas administrateur.", statusCode: 403 }) | ||||
|         return errorHandling(next, { | ||||
|           message: "Vous n'êtes pas administrateur.", | ||||
|           statusCode: 403 | ||||
|         }) | ||||
|       } | ||||
|       next() | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|     .catch(error => { | ||||
|       console.log(error) | ||||
|       return errorHandling(next, serverError) | ||||
|     }) | ||||
|   | ||||
| @@ -5,18 +5,27 @@ const { JWT_SECRET } = require('../assets/config/config') | ||||
| module.exports = (req, _res, next) => { | ||||
|   const token = req.get('Authorization') | ||||
|   if (!token) { | ||||
|     return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   let decodedToken | ||||
|   try { | ||||
|     decodedToken = jwt.verify(token, JWT_SECRET) | ||||
|   } catch (error) { | ||||
|     return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   if (!decodedToken) { | ||||
|     return errorHandling(next, { message: 'Vous devez être connecter pour effectuer cette opération.', statusCode: 403 }) | ||||
|     return errorHandling(next, { | ||||
|       message: 'Vous devez être connecter pour effectuer cette opération.', | ||||
|       statusCode: 403 | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   req.userId = decodedToken.userId | ||||
|   | ||||
							
								
								
									
										453
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										453
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "api", | ||||
|   "version": "2.0.0", | ||||
|   "version": "2.1.0", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -89,9 +89,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@types/node": { | ||||
|       "version": "13.9.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz", | ||||
|       "integrity": "sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg==" | ||||
|       "version": "14.0.27", | ||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", | ||||
|       "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" | ||||
|     }, | ||||
|     "abab": { | ||||
|       "version": "2.0.3", | ||||
| @@ -114,9 +114,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "acorn": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", | ||||
|       "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" | ||||
|       "version": "7.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", | ||||
|       "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==" | ||||
|     }, | ||||
|     "acorn-globals": { | ||||
|       "version": "6.0.0", | ||||
| @@ -134,14 +134,14 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "acorn-walk": { | ||||
|       "version": "7.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", | ||||
|       "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==" | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", | ||||
|       "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" | ||||
|     }, | ||||
|     "ajv": { | ||||
|       "version": "6.12.2", | ||||
|       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", | ||||
|       "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", | ||||
|       "version": "6.12.3", | ||||
|       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", | ||||
|       "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", | ||||
|       "requires": { | ||||
|         "fast-deep-equal": "^3.1.1", | ||||
|         "fast-json-stable-stringify": "^2.0.0", | ||||
| @@ -279,9 +279,9 @@ | ||||
|       "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" | ||||
|     }, | ||||
|     "aws4": { | ||||
|       "version": "1.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", | ||||
|       "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" | ||||
|       "version": "1.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", | ||||
|       "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" | ||||
|     }, | ||||
|     "axios": { | ||||
|       "version": "0.19.2", | ||||
| @@ -301,7 +301,6 @@ | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", | ||||
|       "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "safe-buffer": "5.1.2" | ||||
|       } | ||||
| @@ -345,6 +344,21 @@ | ||||
|         "qs": "6.7.0", | ||||
|         "raw-body": "2.4.0", | ||||
|         "type-is": "~1.6.17" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "bowser": { | ||||
| @@ -739,9 +753,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "debug": { | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", | ||||
|       "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", | ||||
|       "requires": { | ||||
|         "ms": "2.0.0" | ||||
|       }, | ||||
| @@ -849,11 +863,6 @@ | ||||
|         "streamsearch": "0.1.2" | ||||
|       } | ||||
|     }, | ||||
|     "dns-prefetch-control": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", | ||||
|       "integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q==" | ||||
|     }, | ||||
|     "doctrine": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", | ||||
| @@ -895,8 +904,7 @@ | ||||
|     "dotenv": { | ||||
|       "version": "8.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", | ||||
|       "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" | ||||
|     }, | ||||
|     "dottie": { | ||||
|       "version": "2.0.2", | ||||
| @@ -1008,9 +1016,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "escodegen": { | ||||
|       "version": "1.14.1", | ||||
|       "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", | ||||
|       "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", | ||||
|       "version": "1.14.3", | ||||
|       "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", | ||||
|       "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", | ||||
|       "requires": { | ||||
|         "esprima": "^4.0.1", | ||||
|         "estraverse": "^4.2.0", | ||||
| @@ -1142,6 +1150,23 @@ | ||||
|       "requires": { | ||||
|         "debug": "^2.6.9", | ||||
|         "resolve": "^1.13.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "eslint-module-utils": { | ||||
| @@ -1152,6 +1177,23 @@ | ||||
|       "requires": { | ||||
|         "debug": "^2.6.9", | ||||
|         "pkg-dir": "^2.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "eslint-plugin-es": { | ||||
| @@ -1191,6 +1233,15 @@ | ||||
|         "resolve": "^1.11.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "doctrine": { | ||||
|           "version": "1.5.0", | ||||
|           "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", | ||||
| @@ -1200,6 +1251,12 @@ | ||||
|             "esutils": "^2.0.2", | ||||
|             "isarray": "^1.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @@ -1353,11 +1410,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | ||||
|       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" | ||||
|     }, | ||||
|     "expect-ct": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", | ||||
|       "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" | ||||
|     }, | ||||
|     "express": { | ||||
|       "version": "4.17.1", | ||||
|       "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", | ||||
| @@ -1393,12 +1445,27 @@ | ||||
|         "type-is": "~1.6.18", | ||||
|         "utils-merge": "1.0.1", | ||||
|         "vary": "~1.1.2" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "express-fileupload": { | ||||
|       "version": "1.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.6.tgz", | ||||
|       "integrity": "sha512-w24zPWT8DkoIxSVkbxYPo9hkTiLpCQQzNsLRTCnecBhfbYv+IkIC5uLw2MIUAxBZ+7UMmXPjGxlhzUXo4RcbZw==", | ||||
|       "version": "1.1.9", | ||||
|       "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.1.9.tgz", | ||||
|       "integrity": "sha512-f2w0aoe7lj3NeD8a4MXmYQsqir3Z66I08l9AKq04QbFUAjeZNmPwTlR5Lx2NGwSu/PslsAjGC38MWzo5tTjoBg==", | ||||
|       "requires": { | ||||
|         "busboy": "^0.3.1" | ||||
|       } | ||||
| @@ -1412,19 +1479,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "express-validator": { | ||||
|       "version": "6.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.4.0.tgz", | ||||
|       "integrity": "sha512-Fs+x0yDOSiUV+o5jIRloMyBxqpSzJiMM8KQW1IRVv2l49F6ATU0F9uPa+3K6vXNlLlhUjauv2FCGLFPMaNr24w==", | ||||
|       "version": "6.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.6.0.tgz", | ||||
|       "integrity": "sha512-xcephfzFbUssJph/nOSKIdx+I+8GRz5by/8rOIKL6gJikKKKJjnwYH5TG1nIDB6kEalUtZMbOFuSNOp/HHY84Q==", | ||||
|       "requires": { | ||||
|         "lodash": "^4.17.15", | ||||
|         "validator": "^12.1.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "validator": { | ||||
|           "version": "12.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", | ||||
|           "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" | ||||
|         } | ||||
|         "validator": "^13.1.1" | ||||
|       } | ||||
|     }, | ||||
|     "extend": { | ||||
| @@ -1449,9 +1509,9 @@ | ||||
|       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" | ||||
|     }, | ||||
|     "fast-deep-equal": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", | ||||
|       "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||
|       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" | ||||
|     }, | ||||
|     "fast-json-stable-stringify": { | ||||
|       "version": "2.1.0", | ||||
| @@ -1507,6 +1567,21 @@ | ||||
|         "parseurl": "~1.3.3", | ||||
|         "statuses": "~1.5.0", | ||||
|         "unpipe": "~1.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "find-root": { | ||||
| @@ -1547,23 +1622,6 @@ | ||||
|       "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", | ||||
|       "requires": { | ||||
|         "debug": "=3.1.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", | ||||
|           "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "ms": { | ||||
|               "version": "2.0.0", | ||||
|               "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|               "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "forever-agent": { | ||||
| @@ -1586,11 +1644,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", | ||||
|       "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" | ||||
|     }, | ||||
|     "frameguard": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", | ||||
|       "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" | ||||
|     }, | ||||
|     "fresh": { | ||||
|       "version": "0.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | ||||
| @@ -1724,11 +1777,11 @@ | ||||
|       "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" | ||||
|     }, | ||||
|     "har-validator": { | ||||
|       "version": "5.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", | ||||
|       "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", | ||||
|       "version": "5.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", | ||||
|       "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", | ||||
|       "requires": { | ||||
|         "ajv": "^6.5.5", | ||||
|         "ajv": "^6.12.3", | ||||
|         "har-schema": "^2.0.0" | ||||
|       } | ||||
|     }, | ||||
| @@ -1760,22 +1813,18 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "helmet": { | ||||
|       "version": "3.21.3", | ||||
|       "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.3.tgz", | ||||
|       "integrity": "sha512-8OjGNdpG3WQhPO71fSy2fT4X3FSNutU1LDeAf+YS+Vil6r+fE7w8per5mNed6egGYbZl3QhKXgFzMYSwys+YQw==", | ||||
|       "version": "3.23.3", | ||||
|       "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", | ||||
|       "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", | ||||
|       "requires": { | ||||
|         "depd": "2.0.0", | ||||
|         "dns-prefetch-control": "0.2.0", | ||||
|         "dont-sniff-mimetype": "1.1.0", | ||||
|         "expect-ct": "0.2.0", | ||||
|         "feature-policy": "0.3.0", | ||||
|         "frameguard": "3.1.0", | ||||
|         "helmet-crossdomain": "0.4.0", | ||||
|         "helmet-csp": "2.9.5", | ||||
|         "helmet-csp": "2.10.0", | ||||
|         "hide-powered-by": "1.1.0", | ||||
|         "hpkp": "2.0.0", | ||||
|         "hsts": "2.2.0", | ||||
|         "ienoopen": "1.1.0", | ||||
|         "nocache": "2.1.0", | ||||
|         "referrer-policy": "1.2.0", | ||||
|         "x-xss-protection": "1.3.0" | ||||
| @@ -1794,9 +1843,9 @@ | ||||
|       "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" | ||||
|     }, | ||||
|     "helmet-csp": { | ||||
|       "version": "2.9.5", | ||||
|       "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.5.tgz", | ||||
|       "integrity": "sha512-w9nps5adqFQwgktVPDbXkARmZot/nr8aegzQas9AXdBSwBFBBefPpDSTV0wtgHlAUdDwY6MZo7qAl9yts3ppJg==", | ||||
|       "version": "2.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", | ||||
|       "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", | ||||
|       "requires": { | ||||
|         "bowser": "2.9.0", | ||||
|         "camelize": "1.0.0", | ||||
| @@ -1879,11 +1928,6 @@ | ||||
|         "safer-buffer": ">= 2.1.2 < 3" | ||||
|       } | ||||
|     }, | ||||
|     "ienoopen": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", | ||||
|       "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" | ||||
|     }, | ||||
|     "ignore": { | ||||
|       "version": "4.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", | ||||
| @@ -2197,9 +2241,9 @@ | ||||
|       "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" | ||||
|     }, | ||||
|     "jsdom": { | ||||
|       "version": "16.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz", | ||||
|       "integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==", | ||||
|       "version": "16.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz", | ||||
|       "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==", | ||||
|       "requires": { | ||||
|         "abab": "^2.0.3", | ||||
|         "acorn": "^7.1.1", | ||||
| @@ -2221,7 +2265,7 @@ | ||||
|         "tough-cookie": "^3.0.1", | ||||
|         "w3c-hr-time": "^1.0.2", | ||||
|         "w3c-xmlserializer": "^2.0.0", | ||||
|         "webidl-conversions": "^6.0.0", | ||||
|         "webidl-conversions": "^6.1.0", | ||||
|         "whatwg-encoding": "^1.0.5", | ||||
|         "whatwg-mimetype": "^2.3.0", | ||||
|         "whatwg-url": "^8.0.0", | ||||
| @@ -2277,13 +2321,6 @@ | ||||
|         "lodash.once": "^4.0.0", | ||||
|         "ms": "^2.1.1", | ||||
|         "semver": "^5.6.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ms": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||
|           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "jsprim": { | ||||
| @@ -2441,12 +2478,11 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "lru-cache": { | ||||
|       "version": "4.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", | ||||
|       "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | ||||
|       "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", | ||||
|       "requires": { | ||||
|         "pseudomap": "^1.0.2", | ||||
|         "yallist": "^2.1.2" | ||||
|         "yallist": "^3.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "make-dir": { | ||||
| @@ -2487,16 +2523,16 @@ | ||||
|       "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" | ||||
|     }, | ||||
|     "mime-db": { | ||||
|       "version": "1.43.0", | ||||
|       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", | ||||
|       "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" | ||||
|       "version": "1.44.0", | ||||
|       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", | ||||
|       "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" | ||||
|     }, | ||||
|     "mime-types": { | ||||
|       "version": "2.1.26", | ||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", | ||||
|       "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", | ||||
|       "version": "2.1.27", | ||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", | ||||
|       "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", | ||||
|       "requires": { | ||||
|         "mime-db": "1.43.0" | ||||
|         "mime-db": "1.44.0" | ||||
|       } | ||||
|     }, | ||||
|     "mimic-fn": { | ||||
| @@ -2536,29 +2572,48 @@ | ||||
|       } | ||||
|     }, | ||||
|     "moment": { | ||||
|       "version": "2.24.0", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", | ||||
|       "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" | ||||
|       "version": "2.27.0", | ||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", | ||||
|       "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" | ||||
|     }, | ||||
|     "moment-timezone": { | ||||
|       "version": "0.5.28", | ||||
|       "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", | ||||
|       "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", | ||||
|       "version": "0.5.31", | ||||
|       "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", | ||||
|       "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", | ||||
|       "requires": { | ||||
|         "moment": ">= 2.9.0" | ||||
|       } | ||||
|     }, | ||||
|     "morgan": { | ||||
|       "version": "1.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", | ||||
|       "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", | ||||
|       "dev": true, | ||||
|       "version": "1.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", | ||||
|       "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", | ||||
|       "requires": { | ||||
|         "basic-auth": "~2.0.0", | ||||
|         "basic-auth": "~2.0.1", | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "~1.1.2", | ||||
|         "depd": "~2.0.0", | ||||
|         "on-finished": "~2.3.0", | ||||
|         "on-headers": "~1.0.1" | ||||
|         "on-headers": "~1.0.2" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "depd": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", | ||||
|           "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "ms": { | ||||
| @@ -2589,25 +2644,12 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "iconv-lite": { | ||||
|           "version": "0.5.1", | ||||
|           "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", | ||||
|           "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", | ||||
|           "version": "0.5.2", | ||||
|           "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", | ||||
|           "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", | ||||
|           "requires": { | ||||
|             "safer-buffer": ">= 2.1.2 < 3" | ||||
|           } | ||||
|         }, | ||||
|         "lru-cache": { | ||||
|           "version": "5.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | ||||
|           "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", | ||||
|           "requires": { | ||||
|             "yallist": "^3.0.2" | ||||
|           } | ||||
|         }, | ||||
|         "yallist": { | ||||
|           "version": "3.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", | ||||
|           "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @@ -2617,6 +2659,22 @@ | ||||
|       "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", | ||||
|       "requires": { | ||||
|         "lru-cache": "^4.1.3" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "lru-cache": { | ||||
|           "version": "4.1.5", | ||||
|           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", | ||||
|           "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", | ||||
|           "requires": { | ||||
|             "pseudomap": "^1.0.2", | ||||
|             "yallist": "^2.1.2" | ||||
|           } | ||||
|         }, | ||||
|         "yallist": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", | ||||
|           "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "natural-compare": { | ||||
| @@ -2642,9 +2700,9 @@ | ||||
|       "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" | ||||
|     }, | ||||
|     "nodemailer": { | ||||
|       "version": "6.4.6", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.6.tgz", | ||||
|       "integrity": "sha512-/kJ+FYVEm2HuUlw87hjSqTss+GU35D4giOpdSfGp7DO+5h6RlJj7R94YaYHOkoxu1CSaM0d3WRBtCzwXrY6MKA==" | ||||
|       "version": "6.4.11", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.11.tgz", | ||||
|       "integrity": "sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==" | ||||
|     }, | ||||
|     "nodemon": { | ||||
|       "version": "2.0.4", | ||||
| @@ -2793,8 +2851,7 @@ | ||||
|     "on-headers": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", | ||||
|       "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" | ||||
|     }, | ||||
|     "once": { | ||||
|       "version": "1.4.0", | ||||
| @@ -3319,19 +3376,19 @@ | ||||
|       } | ||||
|     }, | ||||
|     "request-promise-core": { | ||||
|       "version": "1.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", | ||||
|       "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", | ||||
|       "version": "1.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", | ||||
|       "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", | ||||
|       "requires": { | ||||
|         "lodash": "^4.17.15" | ||||
|         "lodash": "^4.17.19" | ||||
|       } | ||||
|     }, | ||||
|     "request-promise-native": { | ||||
|       "version": "1.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", | ||||
|       "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", | ||||
|       "version": "1.0.9", | ||||
|       "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", | ||||
|       "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", | ||||
|       "requires": { | ||||
|         "request-promise-core": "1.1.3", | ||||
|         "request-promise-core": "1.1.4", | ||||
|         "stealthy-require": "^1.1.1", | ||||
|         "tough-cookie": "^2.3.3" | ||||
|       }, | ||||
| @@ -3479,6 +3536,21 @@ | ||||
|         "statuses": "~1.5.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "ms": { | ||||
|               "version": "2.0.0", | ||||
|               "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|               "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", | ||||
| @@ -3492,9 +3564,9 @@ | ||||
|       "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" | ||||
|     }, | ||||
|     "sequelize": { | ||||
|       "version": "5.21.5", | ||||
|       "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.5.tgz", | ||||
|       "integrity": "sha512-n9hR5K4uQGmBGK/Y/iqewCeSFmKVsd0TRnh0tfoLoAkmXbKC4tpeK96RhKs7d+TTMtrJlgt2TNLVBaAxEwC4iw==", | ||||
|       "version": "5.22.3", | ||||
|       "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.22.3.tgz", | ||||
|       "integrity": "sha512-+nxf4TzdrB+PRmoWhR05TP9ukLAurK7qtKcIFv5Vhxm5Z9v+d2PcTT6Ea3YAoIQVkZ47QlT9XWAIUevMT/3l8Q==", | ||||
|       "requires": { | ||||
|         "bluebird": "^3.5.0", | ||||
|         "cls-bluebird": "^2.1.0", | ||||
| @@ -3521,11 +3593,6 @@ | ||||
|             "ms": "^2.1.1" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||
|           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" | ||||
|         }, | ||||
|         "semver": { | ||||
|           "version": "6.3.0", | ||||
|           "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", | ||||
| @@ -3644,11 +3711,6 @@ | ||||
|             "ms": "^2.1.1" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||
|           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" | ||||
|         }, | ||||
|         "uuid": { | ||||
|           "version": "3.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||
| @@ -3768,9 +3830,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "sqlstring": { | ||||
|       "version": "2.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", | ||||
|       "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" | ||||
|       "version": "2.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", | ||||
|       "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" | ||||
|     }, | ||||
|     "sshpk": { | ||||
|       "version": "1.16.1", | ||||
| @@ -4121,6 +4183,23 @@ | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "debug": "^2.2.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "debug": { | ||||
|           "version": "2.6.9", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|           "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "uniq": { | ||||
| @@ -4193,9 +4272,9 @@ | ||||
|       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" | ||||
|     }, | ||||
|     "uuid": { | ||||
|       "version": "7.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", | ||||
|       "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==" | ||||
|       "version": "7.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", | ||||
|       "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" | ||||
|     }, | ||||
|     "v8-compile-cache": { | ||||
|       "version": "2.1.1", | ||||
| @@ -4214,9 +4293,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "validator": { | ||||
|       "version": "13.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", | ||||
|       "integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==" | ||||
|       "version": "13.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz", | ||||
|       "integrity": "sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw==" | ||||
|     }, | ||||
|     "vary": { | ||||
|       "version": "1.1.2", | ||||
| @@ -4343,9 +4422,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "ws": { | ||||
|       "version": "7.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", | ||||
|       "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" | ||||
|       "version": "7.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", | ||||
|       "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" | ||||
|     }, | ||||
|     "x-xss-protection": { | ||||
|       "version": "1.3.0", | ||||
| @@ -4375,9 +4454,9 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "yallist": { | ||||
|       "version": "2.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", | ||||
|       "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", | ||||
|       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -25,11 +25,11 @@ | ||||
|     "sequelize": "^5.21.5", | ||||
|     "smart-request-balancer": "^2.1.1", | ||||
|     "uuid": "^7.0.2", | ||||
|     "validator": "^13.0.0" | ||||
|     "validator": "^13.0.0", | ||||
|     "dotenv": "^8.2.0", | ||||
|     "morgan": "^1.9.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "dotenv": "^8.2.0", | ||||
|     "morgan": "^1.9.1", | ||||
|     "nodemon": "^2.0.4", | ||||
|     "snazzy": "^8.0.0", | ||||
|     "standard": "^14.3.4" | ||||
|   | ||||
| @@ -9,197 +9,215 @@ const AdminRouter = Router() | ||||
|  | ||||
| AdminRouter.route('/functions') | ||||
|  | ||||
| // Récupère les fonctions | ||||
|   // Récupère les fonctions | ||||
|   .get(adminController.getFunctions) | ||||
|  | ||||
| // Permet de créé une fonction | ||||
|   .post(fileUpload({ | ||||
|     useTempFiles: true, | ||||
|     safeFileNames: true, | ||||
|     preserveExtension: Number, | ||||
|     limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|     parseNested: true | ||||
|   }), | ||||
|   [ | ||||
|     body('title') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir un titre.') | ||||
|       .isLength({ max: 100 }) | ||||
|       .withMessage('Le titre est trop long.') | ||||
|       .custom((title) => { | ||||
|         if (title === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('slug') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir un slug.') | ||||
|       .isLength({ max: 100 }) | ||||
|       .withMessage('Le slug est trop long.') | ||||
|       .custom((slug) => { | ||||
|         if (slug === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|       .custom(async (slug) => { | ||||
|         try { | ||||
|           const FunctionSlug = await Functions.findOne({ where: { slug } }) | ||||
|           if (FunctionSlug) { | ||||
|             return Promise.reject(new Error('Le slug existe déjà...')) | ||||
|   // Permet de créé une fonction | ||||
|   .post( | ||||
|     fileUpload({ | ||||
|       useTempFiles: true, | ||||
|       safeFileNames: true, | ||||
|       preserveExtension: Number, | ||||
|       limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|       parseNested: true | ||||
|     }), | ||||
|     [ | ||||
|       body('title') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un titre.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le titre est trop long.') | ||||
|         .custom(title => { | ||||
|           if (title === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|           } | ||||
|         } catch (error) { | ||||
|           console.log(error) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('description') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir une description.') | ||||
|       .isLength({ max: 255, min: 1 }) | ||||
|       .withMessage('La description est trop longue.') | ||||
|       .custom((description) => { | ||||
|         if (description === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir une description.')) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('categorieId') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir une catégorie.') | ||||
|       .custom(async (categorieId) => { | ||||
|         try { | ||||
|           const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }) | ||||
|           if (!categorieFound) { | ||||
|             return Promise.reject(new Error("La catégorie n'existe pas!")) | ||||
|           return true | ||||
|         }), | ||||
|       body('slug') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un slug.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le slug est trop long.') | ||||
|         .custom(slug => { | ||||
|           if (slug === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|           } | ||||
|         } catch (error) { | ||||
|           console.log(error) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('type') | ||||
|       .custom((type) => { | ||||
|           return true | ||||
|         }) | ||||
|         .custom(async slug => { | ||||
|           try { | ||||
|             const FunctionSlug = await Functions.findOne({ where: { slug } }) | ||||
|             if (FunctionSlug) { | ||||
|               return Promise.reject(new Error('Le slug existe déjà...')) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('description') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une description.') | ||||
|         .isLength({ max: 255, min: 1 }) | ||||
|         .withMessage('La description est trop longue.') | ||||
|         .custom(description => { | ||||
|           if (description === 'undefined') { | ||||
|             return Promise.reject( | ||||
|               new Error('La fonction doit avoir une description.') | ||||
|             ) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('categorieId') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une catégorie.') | ||||
|         .custom(async categorieId => { | ||||
|           try { | ||||
|             const categorieFound = await Categories.findOne({ | ||||
|               where: { id: parseInt(categorieId) } | ||||
|             }) | ||||
|             if (!categorieFound) { | ||||
|               return Promise.reject(new Error("La catégorie n'existe pas!")) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('type').custom(type => { | ||||
|         if (!(type === 'article' || type === 'form' || type === 'page')) { | ||||
|           return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.')) | ||||
|           return Promise.reject( | ||||
|             new Error( | ||||
|               'Le type de la fonction peut être : article, form ou page.' | ||||
|             ) | ||||
|           ) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|   ], adminController.postFunction) | ||||
|     ], | ||||
|     adminController.postFunction | ||||
|   ) | ||||
|  | ||||
| AdminRouter.route('/functions/:slug') | ||||
|  | ||||
| // Récupère les informations d'une fonction | ||||
|   // Récupère les informations d'une fonction | ||||
|   .get(adminController.getFunctionBySlug) | ||||
|  | ||||
| AdminRouter.route('/functions/:id') | ||||
|  | ||||
| // Modifie information basique d'une fonction | ||||
|   .put(fileUpload({ | ||||
|     useTempFiles: true, | ||||
|     safeFileNames: true, | ||||
|     preserveExtension: Number, | ||||
|     limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|     parseNested: true | ||||
|   }), | ||||
|   [ | ||||
|     body('title') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir un titre.') | ||||
|       .isLength({ max: 100 }) | ||||
|       .withMessage('Le titre est trop long.') | ||||
|       .custom((title) => { | ||||
|         if (title === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('slug') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir un slug.') | ||||
|       .isLength({ max: 100 }) | ||||
|       .withMessage('Le slug est trop long.') | ||||
|       .custom((slug) => { | ||||
|         if (slug === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('description') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir une description.') | ||||
|       .isLength({ max: 255, min: 1 }) | ||||
|       .withMessage('La description est trop longue.') | ||||
|       .custom((description) => { | ||||
|         if (description === 'undefined') { | ||||
|           return Promise.reject(new Error('La fonction doit avoir une description.')) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('categorieId') | ||||
|       .not() | ||||
|       .isEmpty() | ||||
|       .withMessage('La fonction doit avoir une catégorie.') | ||||
|       .custom(async (categorieId) => { | ||||
|         try { | ||||
|           const categorieFound = await Categories.findOne({ where: { id: parseInt(categorieId) } }) | ||||
|           if (!categorieFound) { | ||||
|             return Promise.reject(new Error("La catégorie n'existe pas!")) | ||||
|   // Modifie information basique d'une fonction | ||||
|   .put( | ||||
|     fileUpload({ | ||||
|       useTempFiles: true, | ||||
|       safeFileNames: true, | ||||
|       preserveExtension: Number, | ||||
|       limits: { fileSize: 5 * 1024 * 1024 }, // 5mb, | ||||
|       parseNested: true | ||||
|     }), | ||||
|     [ | ||||
|       body('title') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un titre.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le titre est trop long.') | ||||
|         .custom(title => { | ||||
|           if (title === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un titre.')) | ||||
|           } | ||||
|         } catch (error) { | ||||
|           console.log(error) | ||||
|         } | ||||
|         return true | ||||
|       }), | ||||
|     body('type') | ||||
|       .custom((type) => { | ||||
|           return true | ||||
|         }), | ||||
|       body('slug') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir un slug.') | ||||
|         .isLength({ max: 100 }) | ||||
|         .withMessage('Le slug est trop long.') | ||||
|         .custom(slug => { | ||||
|           if (slug === 'undefined') { | ||||
|             return Promise.reject(new Error('La fonction doit avoir un slug.')) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('description') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une description.') | ||||
|         .isLength({ max: 255, min: 1 }) | ||||
|         .withMessage('La description est trop longue.') | ||||
|         .custom(description => { | ||||
|           if (description === 'undefined') { | ||||
|             return Promise.reject( | ||||
|               new Error('La fonction doit avoir une description.') | ||||
|             ) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('categorieId') | ||||
|         .not() | ||||
|         .isEmpty() | ||||
|         .withMessage('La fonction doit avoir une catégorie.') | ||||
|         .custom(async categorieId => { | ||||
|           try { | ||||
|             const categorieFound = await Categories.findOne({ | ||||
|               where: { id: parseInt(categorieId) } | ||||
|             }) | ||||
|             if (!categorieFound) { | ||||
|               return Promise.reject(new Error("La catégorie n'existe pas!")) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.log(error) | ||||
|           } | ||||
|           return true | ||||
|         }), | ||||
|       body('type').custom(type => { | ||||
|         if (!(type === 'article' || type === 'form' || type === 'page')) { | ||||
|           return Promise.reject(new Error('Le type de la fonction peut être : article, form ou page.')) | ||||
|           return Promise.reject( | ||||
|             new Error( | ||||
|               'Le type de la fonction peut être : article, form ou page.' | ||||
|             ) | ||||
|           ) | ||||
|         } | ||||
|         return true | ||||
|       }) | ||||
|   ], adminController.putFunction) | ||||
|     ], | ||||
|     adminController.putFunction | ||||
|   ) | ||||
|  | ||||
| // Supprime une fonction avec son id | ||||
|   // Supprime une fonction avec son id | ||||
|   .delete(adminController.deleteFunction) | ||||
|  | ||||
| AdminRouter.route('/functions/article/:id') | ||||
|  | ||||
|   .put(adminController.putFunctionArticle) | ||||
|  | ||||
| AdminRouter.route('/functions/form/:id') | ||||
|  | ||||
|   .put(adminController.putFunctionForm) | ||||
|  | ||||
| AdminRouter.route('/categories') | ||||
|  | ||||
| // Crée une catégorie | ||||
|   // Crée une catégorie | ||||
|   .post(adminController.postCategory) | ||||
|  | ||||
| AdminRouter.route('/categories/:id') | ||||
|  | ||||
| // Modifier une catégorie avec son id | ||||
|   // Modifier une catégorie avec son id | ||||
|   .put(adminController.putCategory) | ||||
|  | ||||
| // Supprime une catégorie avec son id | ||||
|   // Supprime une catégorie avec son id | ||||
|   .delete(adminController.deleteCategory) | ||||
|  | ||||
| AdminRouter.route('/quotes') | ||||
|  | ||||
| // Récupère les citations pas encore validées | ||||
|   // Récupère les citations pas encore validées | ||||
|   .get(adminController.getQuotes) | ||||
|  | ||||
| AdminRouter.route('/quotes/:id') | ||||
|  | ||||
| // Valide ou supprime une citation | ||||
|   // Valide ou supprime une citation | ||||
|   .put(adminController.putQuote) | ||||
|  | ||||
| module.exports = AdminRouter | ||||
|   | ||||
| @@ -5,7 +5,7 @@ const CategoriesRouter = Router() | ||||
|  | ||||
| CategoriesRouter.route('/') | ||||
|  | ||||
| // Récupère les catégories | ||||
|   // Récupère les catégories | ||||
|   .get(categoriesController.getCategories) | ||||
|  | ||||
| module.exports = CategoriesRouter | ||||
|   | ||||
| @@ -6,18 +6,18 @@ const CommentsRouter = Router() | ||||
|  | ||||
| CommentsRouter.route('/:commentId') | ||||
|  | ||||
| // Modifier un commentaire | ||||
|   // Modifier un commentaire | ||||
|   .put(isAuth, commentsController.putCommentsById) | ||||
|  | ||||
| // Supprime un commentaire | ||||
|   // Supprime un commentaire | ||||
|   .delete(isAuth, commentsController.deleteCommentById) | ||||
|  | ||||
| CommentsRouter.route('/:functionId') | ||||
|  | ||||
| // Récupère les commentaires | ||||
|   // Récupère les commentaires | ||||
|   .get(commentsController.getCommentsByFunctionId) | ||||
|  | ||||
| // Permet à un utilisateur de poster un commentaire sur une fonction | ||||
|   // Permet à un utilisateur de poster un commentaire sur une fonction | ||||
|   .post(isAuth, commentsController.postCommentsByFunctionId) | ||||
|  | ||||
| module.exports = CommentsRouter | ||||
|   | ||||
| @@ -6,13 +6,13 @@ const FavoritesRouter = Router() | ||||
|  | ||||
| FavoritesRouter.route('/:functionId') | ||||
|  | ||||
| // Récupère si une fonction est en favoris (d'un utilisateur) | ||||
|   // Récupère si une fonction est en favoris (d'un utilisateur) | ||||
|   .get(isAuth, favoritesController.getFavoriteByFunctionId) | ||||
|  | ||||
| // Permet à un utilisateur d'ajouter une fonction aux favoris | ||||
|   // Permet à un utilisateur d'ajouter une fonction aux favoris | ||||
|   .post(isAuth, favoritesController.postFavoriteByFunctionId) | ||||
|  | ||||
| // Supprime une fonction des favoris d'un utilisateur | ||||
|   // Supprime une fonction des favoris d'un utilisateur | ||||
|   .delete(isAuth, favoritesController.deleteFavoriteByFunctionId) | ||||
|  | ||||
| module.exports = FavoritesRouter | ||||
|   | ||||
| @@ -5,15 +5,15 @@ const FunctionsRouter = Router() | ||||
|  | ||||
| FunctionsRouter.route('/') | ||||
|  | ||||
| // Récupère les fonctions | ||||
|   // Récupère les fonctions | ||||
|   .get(functionsController.getFunctions) | ||||
|  | ||||
| FunctionsRouter.route('/:slug') | ||||
|  | ||||
| // Récupère les informations de la fonction par son slug | ||||
|   // Récupère les informations de la fonction par son slug | ||||
|   .get(functionsController.getFunctionBySlug) | ||||
|  | ||||
| // Exécute la fonction demandée en paramètre | ||||
|   // Exécute la fonction demandée en paramètre | ||||
|   .post(functionsController.executeFunctionBySlug) | ||||
|  | ||||
| module.exports = FunctionsRouter | ||||
|   | ||||
| @@ -6,10 +6,10 @@ const QuotesRouter = Router() | ||||
|  | ||||
| QuotesRouter.route('/') | ||||
|  | ||||
| // Récupère les citations | ||||
|   // Récupère les citations | ||||
|   .get(quotesController.getQuotes) | ||||
|  | ||||
| // Proposer une citation | ||||
|   // Proposer une citation | ||||
|   .post(isAuth, quotesController.postQuote) | ||||
|  | ||||
| module.exports = QuotesRouter | ||||
|   | ||||
| @@ -6,18 +6,18 @@ const TasksRouter = Router() | ||||
|  | ||||
| TasksRouter.route('/') | ||||
|  | ||||
| // Récupère les tâches à faire d'un user | ||||
|   // Récupère les tâches à faire d'un user | ||||
|   .get(isAuth, tasksController.getTasks) | ||||
|  | ||||
| // Poster une nouvelle tâche à faire | ||||
|   // Poster une nouvelle tâche à faire | ||||
|   .post(isAuth, tasksController.postTask) | ||||
|  | ||||
| TasksRouter.route('/:id') | ||||
|  | ||||
| // Permet de mettre une tâche à faire en isCompleted ou !isCompleted | ||||
|   // Permet de mettre une tâche à faire en isCompleted ou !isCompleted | ||||
|   .put(isAuth, tasksController.putTask) | ||||
|  | ||||
| // Supprimer une tâche à faire | ||||
|   // Supprimer une tâche à faire | ||||
|   .delete(isAuth, tasksController.deleteTask) | ||||
|  | ||||
| module.exports = TasksRouter | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| .footer { | ||||
|     border-top: var(--border-header-footer); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   border-top: var(--border-header-footer); | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| .footer-text { | ||||
|     line-height: 2.5; | ||||
| } | ||||
|   line-height: 2.5; | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,11 @@ export default function Footer () { | ||||
|         <Link href='/about'> | ||||
|           <a>FunctionProject</a> | ||||
|         </Link> | ||||
|                  - Version 2.1 <br /> | ||||
|         <a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>Divlo</a> | Tous droits réservés | ||||
|          - Version 2.1 <br /> | ||||
|         <a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'> | ||||
|           Divlo | ||||
|         </a>{' '} | ||||
|         | Tous droits réservés | ||||
|       </p> | ||||
|     </footer> | ||||
|   ) | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import useAPI from '../../hooks/useAPI' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const AddEditFunction = (props) => { | ||||
| const AddEditFunction = props => { | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [inputState, setInputState] = useState(props.defaultInputState) | ||||
|   const [message, setMessage] = useState('') | ||||
| @@ -22,18 +22,29 @@ const AddEditFunction = (props) => { | ||||
|     } | ||||
|   }, [categories]) | ||||
|  | ||||
|   const apiCallFunction = (formData) => { | ||||
|     if (props.isEditing) return api.put(`/admin/functions/${inputState.id}`, formData, { headers: { Authorization: props.user.token } }) | ||||
|     return api.post('/admin/functions', formData, { headers: { Authorization: props.user.token } }) | ||||
|   const apiCallFunction = formData => { | ||||
|     if (props.isEditing) { | ||||
|       return api.put(`/admin/functions/${inputState.id}`, formData, { | ||||
|         headers: { Authorization: props.user.token } | ||||
|       }) | ||||
|     } | ||||
|     return api.post('/admin/functions', formData, { | ||||
|       headers: { Authorization: props.user.token } | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event, isTypeCheck = false) => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = (event.target.files != null) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value | ||||
|     inputStateNew[event.target.name] = | ||||
|       event.target.files != null | ||||
|         ? event.target.files[0] | ||||
|         : isTypeCheck | ||||
|           ? event.target.checked | ||||
|           : event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = (event) => { | ||||
|   const handleSubmit = event => { | ||||
|     event.preventDefault() | ||||
|     setIsLoading(true) | ||||
|     const formData = new window.FormData() | ||||
| @@ -53,8 +64,10 @@ const AddEditFunction = (props) => { | ||||
|         setIsLoading(false) | ||||
|         window.location.reload(true) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) | ||||
|       .catch(error => { | ||||
|         setMessage( | ||||
|           `<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>` | ||||
|         ) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|   } | ||||
| @@ -63,23 +76,61 @@ const AddEditFunction = (props) => { | ||||
|     <> | ||||
|       <form onSubmit={handleSubmit}> | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='title'>Titre :</label> | ||||
|           <input value={inputState.title} onChange={handleChange} type='text' name='title' id='title' className='form-control' placeholder='(e.g : Nombre aléatoire)' /> | ||||
|           <label className='form-label' htmlFor='title'> | ||||
|             Titre : | ||||
|           </label> | ||||
|           <input | ||||
|             value={inputState.title} | ||||
|             onChange={handleChange} | ||||
|             type='text' | ||||
|             name='title' | ||||
|             id='title' | ||||
|             className='form-control' | ||||
|             placeholder='(e.g : Nombre aléatoire)' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='slug'>Slug :</label> | ||||
|           <input value={inputState.slug} onChange={handleChange} type='text' name='slug' id='slug' className='form-control' placeholder='(e.g : randomNumber)' /> | ||||
|           <label className='form-label' htmlFor='slug'> | ||||
|             Slug : | ||||
|           </label> | ||||
|           <input | ||||
|             value={inputState.slug} | ||||
|             onChange={handleChange} | ||||
|             type='text' | ||||
|             name='slug' | ||||
|             id='slug' | ||||
|             className='form-control' | ||||
|             placeholder='(e.g : randomNumber)' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='description'>Description :</label> | ||||
|           <textarea style={{ height: 'auto' }} value={inputState.description} onChange={handleChange} name='description' id='description' className='form-control' rows='5' /> | ||||
|           <label className='form-label' htmlFor='description'> | ||||
|             Description : | ||||
|           </label> | ||||
|           <textarea | ||||
|             style={{ height: 'auto' }} | ||||
|             value={inputState.description} | ||||
|             onChange={handleChange} | ||||
|             name='description' | ||||
|             id='description' | ||||
|             className='form-control' | ||||
|             rows='5' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='type'>Type :</label> | ||||
|           <select onChange={handleChange} name='type' id='type' className='form-control' {...(props.isEditing) && { value: inputState.type }}> | ||||
|           <label className='form-label' htmlFor='type'> | ||||
|             Type : | ||||
|           </label> | ||||
|           <select | ||||
|             onChange={handleChange} | ||||
|             name='type' | ||||
|             id='type' | ||||
|             className='form-control' | ||||
|             {...(props.isEditing && { value: inputState.type })} | ||||
|           > | ||||
|             <option value='form'>Formulaire</option> | ||||
|             <option value='article'>Article</option> | ||||
|             <option value='page'>Page</option> | ||||
| @@ -87,37 +138,68 @@ const AddEditFunction = (props) => { | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='categorieId'>Catégorie :</label> | ||||
|           <select onChange={handleChange} name='categorieId' id='categorieId' className='form-control' {...(props.isEditing) && { value: inputState.categorieId }}> | ||||
|             {categories.map((category) => ( | ||||
|               <option key={category.id} value={category.id} className='Admin__Modal-select-option' style={{ backgroundColor: category.color }}>{category.name}</option> | ||||
|           <label className='form-label' htmlFor='categorieId'> | ||||
|             Catégorie : | ||||
|           </label> | ||||
|           <select | ||||
|             onChange={handleChange} | ||||
|             name='categorieId' | ||||
|             id='categorieId' | ||||
|             className='form-control' | ||||
|             {...(props.isEditing && { value: inputState.categorieId })} | ||||
|           > | ||||
|             {categories.map(category => ( | ||||
|               <option | ||||
|                 key={category.id} | ||||
|                 value={category.id} | ||||
|                 className='Admin__Modal-select-option' | ||||
|                 style={{ backgroundColor: category.color }} | ||||
|               > | ||||
|                 {category.name} | ||||
|               </option> | ||||
|             ))} | ||||
|           </select> | ||||
|         </div> | ||||
|  | ||||
|         <div className='form-group'> | ||||
|           <label className='form-label' htmlFor='image'>Image <em>(150x150 recommandé)</em> :</label> | ||||
|           <label className='form-label' htmlFor='image'> | ||||
|             Image <em>(150x150 recommandé)</em> : | ||||
|           </label> | ||||
|           <br /> | ||||
|           <input onChange={handleChange} accept='image/jpeg,image/jpg,image/png' type='file' name='image' id='image' /> | ||||
|           <input | ||||
|             onChange={handleChange} | ||||
|             accept='image/jpeg,image/jpg,image/png' | ||||
|             type='file' | ||||
|             name='image' | ||||
|             id='image' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         {(props.isEditing) && | ||||
|         {props.isEditing && ( | ||||
|           <div className='form-group custom-control custom-switch'> | ||||
|             <input onChange={(event) => handleChange(event, true)} type='checkbox' name='isOnline' checked={inputState.isOnline} className='custom-control-input' id='isOnline' /> | ||||
|             <label className='custom-control-label' htmlFor='isOnline'>isOnline</label> | ||||
|           </div>} | ||||
|             <input | ||||
|               onChange={event => handleChange(event, true)} | ||||
|               type='checkbox' | ||||
|               name='isOnline' | ||||
|               checked={inputState.isOnline} | ||||
|               className='custom-control-input' | ||||
|               id='isOnline' | ||||
|             /> | ||||
|             <label className='custom-control-label' htmlFor='isOnline'> | ||||
|               isOnline | ||||
|             </label> | ||||
|           </div> | ||||
|         )} | ||||
|  | ||||
|         <div className='form-group text-center'> | ||||
|           <button type='submit' className='btn btn-dark'>Envoyer</button> | ||||
|           <button type='submit' className='btn btn-dark'> | ||||
|             Envoyer | ||||
|           </button> | ||||
|         </div> | ||||
|       </form> | ||||
|  | ||||
|       <div className='form-result text-center'> | ||||
|         { | ||||
|           (isLoading) | ||||
|             ? <Loader /> | ||||
|             : htmlParser(message) | ||||
|         } | ||||
|         {isLoading ? <Loader /> : htmlParser(message)} | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
|   | ||||
| @@ -6,19 +6,16 @@ import FunctionArticle from '../FunctionPage/FunctionArticle' | ||||
| import 'notyf/notyf.min.css' | ||||
| import '../../public/css/suneditor.min.css' | ||||
|  | ||||
| const SunEditor = dynamic( | ||||
|   () => import('suneditor-react'), | ||||
|   { ssr: false } | ||||
| ) | ||||
| const SunEditor = dynamic(() => import('suneditor-react'), { ssr: false }) | ||||
|  | ||||
| const EditArticleFunction = (props) => { | ||||
| const EditArticleFunction = props => { | ||||
|   const [htmlContent, setHtmlContent] = useState('') | ||||
|  | ||||
|   const handleEditorChange = (content) => { | ||||
|   const handleEditorChange = content => { | ||||
|     setHtmlContent(content) | ||||
|   } | ||||
|  | ||||
|   const handleSave = async (content) => { | ||||
|   const handleSave = async content => { | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
|       Notyf = require('notyf') | ||||
| @@ -27,7 +24,11 @@ const EditArticleFunction = (props) => { | ||||
|       duration: 5000 | ||||
|     }) | ||||
|     try { | ||||
|       await api.put(`/admin/functions/article/${props.functionInfo.id}`, { article: content }, { headers: { Authorization: props.user.token } }) | ||||
|       await api.put( | ||||
|         `/admin/functions/article/${props.functionInfo.id}`, | ||||
|         { article: content }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|       notyf.success('Sauvegardé!') | ||||
|     } catch { | ||||
|       notyf.error('Erreur!') | ||||
| @@ -36,7 +37,12 @@ const EditArticleFunction = (props) => { | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <SunEditor setContents={props.functionInfo.article} lang='fr' onChange={handleEditorChange} setOptions={{ buttonList: complex, callBackSave: handleSave }} /> | ||||
|       <SunEditor | ||||
|         setContents={props.functionInfo.article} | ||||
|         lang='fr' | ||||
|         onChange={handleEditorChange} | ||||
|         setOptions={{ buttonList: complex, callBackSave: handleSave }} | ||||
|       /> | ||||
|       <FunctionArticle article={htmlContent} /> | ||||
|     </div> | ||||
|   ) | ||||
|   | ||||
| @@ -2,8 +2,10 @@ import { useState } from 'react' | ||||
| import api from '../../utils/api' | ||||
| import 'notyf/notyf.min.css' | ||||
|  | ||||
| const EditFormFunction = (props) => { | ||||
|   const [inputsArray, setInputsArray] = useState(props.functionInfo.utilizationForm || []) | ||||
| const EditFormFunction = props => { | ||||
|   const [inputsArray, setInputsArray] = useState( | ||||
|     props.functionInfo.utilizationForm || [] | ||||
|   ) | ||||
|  | ||||
|   const addInput = () => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
| @@ -11,7 +13,7 @@ const EditFormFunction = (props) => { | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const addOption = (event) => { | ||||
|   const addOption = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     const inputObject = newInputsArray[index] | ||||
| @@ -27,7 +29,7 @@ const EditFormFunction = (props) => { | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleChangeInput = (event) => { | ||||
|   const handleChangeInput = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     const inputObject = newInputsArray[index] | ||||
| @@ -38,7 +40,7 @@ const EditFormFunction = (props) => { | ||||
|     setInputsArray(newInputsArray) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async (event) => { | ||||
|   const handleSubmit = async event => { | ||||
|     event.preventDefault() | ||||
|     let Notyf | ||||
|     if (typeof window !== 'undefined') { | ||||
| @@ -48,14 +50,18 @@ const EditFormFunction = (props) => { | ||||
|       duration: 5000 | ||||
|     }) | ||||
|     try { | ||||
|       await api.put(`/admin/functions/form/${props.functionInfo.id}`, { form: inputsArray }, { headers: { Authorization: props.user.token } }) | ||||
|       await api.put( | ||||
|         `/admin/functions/form/${props.functionInfo.id}`, | ||||
|         { form: inputsArray }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|       notyf.success('Sauvegardé!') | ||||
|     } catch (error) { | ||||
|       notyf.error('Erreur!') | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleRemoveInput = (event) => { | ||||
|   const handleRemoveInput = event => { | ||||
|     const newInputsArray = [...inputsArray] | ||||
|     const index = event.target.id.split('-')[1] | ||||
|     newInputsArray.splice(index, 1) | ||||
| @@ -72,39 +78,88 @@ const EditFormFunction = (props) => { | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|  | ||||
|       <form onSubmit={handleSubmit}> | ||||
|  | ||||
|         {(inputsArray.length > 0) && | ||||
|         {inputsArray.length > 0 && ( | ||||
|           <div className='form-group text-center'> | ||||
|             <button type='submit' className='btn btn-dark'>Sauvegarder</button> | ||||
|           </div>} | ||||
|             <button type='submit' className='btn btn-dark'> | ||||
|               Sauvegarder | ||||
|             </button> | ||||
|           </div> | ||||
|         )} | ||||
|  | ||||
|         {inputsArray.map((input, index) => { | ||||
|           return ( | ||||
|             <div key={index} className='form-group Admin__Input-group'> | ||||
|  | ||||
|               <div className='text-center'> | ||||
|                 <button type='button' onClick={handleRemoveInput} id={`remove-${index}`} className='btn btn-dark'>Supprimer l'input</button> | ||||
|                 <button | ||||
|                   type='button' | ||||
|                   onClick={handleRemoveInput} | ||||
|                   id={`remove-${index}`} | ||||
|                   className='btn btn-dark' | ||||
|                 > | ||||
|                   Supprimer l'input | ||||
|                 </button> | ||||
|               </div> | ||||
|  | ||||
|               <label className='form-label' htmlFor={`name-${index}`}>Nom de l'input :</label> | ||||
|               <input value={input.name} onChange={handleChangeInput} type='text' name='name' id={`name-${index}`} className='form-control' placeholder='(e.g : cityName)' /> | ||||
|               <label className='form-label' htmlFor={`name-${index}`}> | ||||
|                 Nom de l'input : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={input.name} | ||||
|                 onChange={handleChangeInput} | ||||
|                 type='text' | ||||
|                 name='name' | ||||
|                 id={`name-${index}`} | ||||
|                 className='form-control' | ||||
|                 placeholder='(e.g : cityName)' | ||||
|               /> | ||||
|               <br /> | ||||
|  | ||||
|               <label className='form-label' htmlFor={`label-${index}`}>Label :</label> | ||||
|               <input value={input.label} onChange={handleChangeInput} type='text' name='label' id={`label-${index}`} className='form-control' placeholder="(e.g : Entrez le nom d'une ville :)" /> | ||||
|               <label className='form-label' htmlFor={`label-${index}`}> | ||||
|                 Label : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={input.label} | ||||
|                 onChange={handleChangeInput} | ||||
|                 type='text' | ||||
|                 name='label' | ||||
|                 id={`label-${index}`} | ||||
|                 className='form-control' | ||||
|                 placeholder="(e.g : Entrez le nom d'une ville :)" | ||||
|               /> | ||||
|               <br /> | ||||
|  | ||||
|               {(input.type !== 'select') && | ||||
|               {input.type !== 'select' && ( | ||||
|                 <> | ||||
|                   <label className='form-label' htmlFor={`placeholder-${index}`}>Placeholder :</label> | ||||
|                   <input value={input.placeholder} onChange={handleChangeInput} type='text' name='placeholder' id={`placeholder-${index}`} className='form-control' placeholder='(e.g : Paris, FR)' /> | ||||
|                   <label | ||||
|                     className='form-label' | ||||
|                     htmlFor={`placeholder-${index}`} | ||||
|                   > | ||||
|                     Placeholder : | ||||
|                   </label> | ||||
|                   <input | ||||
|                     value={input.placeholder} | ||||
|                     onChange={handleChangeInput} | ||||
|                     type='text' | ||||
|                     name='placeholder' | ||||
|                     id={`placeholder-${index}`} | ||||
|                     className='form-control' | ||||
|                     placeholder='(e.g : Paris, FR)' | ||||
|                   /> | ||||
|                   <br /> | ||||
|                 </>} | ||||
|                 </> | ||||
|               )} | ||||
|  | ||||
|               <label className='form-label' htmlFor={`type-${index}`}>Type :</label> | ||||
|               <select value={input.type} onChange={handleChangeInput} name='type' id={`type-${index}`} className='form-control'> | ||||
|               <label className='form-label' htmlFor={`type-${index}`}> | ||||
|                 Type : | ||||
|               </label> | ||||
|               <select | ||||
|                 value={input.type} | ||||
|                 onChange={handleChangeInput} | ||||
|                 name='type' | ||||
|                 id={`type-${index}`} | ||||
|                 className='form-control' | ||||
|               > | ||||
|                 <option value='text'>text</option> | ||||
|                 <option value='integer'>Number integer</option> | ||||
|                 <option value='float'>Number float</option> | ||||
| @@ -112,40 +167,87 @@ const EditFormFunction = (props) => { | ||||
|                 <option value='select'>select</option> | ||||
|               </select> | ||||
|  | ||||
|               {(input.type === 'select') && | ||||
|               {input.type === 'select' && ( | ||||
|                 <div style={{ marginTop: '50px' }}> | ||||
|  | ||||
|                   <label className='form-label'>Options :</label> | ||||
|  | ||||
|                   {input.options.map((option, optionIndex) => { | ||||
|                     return ( | ||||
|                       <div key={optionIndex} style={{ margin: '0 0 30px 0' }} className='form-group Admin__Input-group'> | ||||
|                       <div | ||||
|                         key={optionIndex} | ||||
|                         style={{ margin: '0 0 30px 0' }} | ||||
|                         className='form-group Admin__Input-group' | ||||
|                       > | ||||
|                         <div className='text-center'> | ||||
|                           <button type='button' onClick={() => handleRemoveOption(index, optionIndex)} className='btn btn-dark'>Supprimer l'option</button> | ||||
|                           <button | ||||
|                             type='button' | ||||
|                             onClick={() => | ||||
|                               handleRemoveOption(index, optionIndex)} | ||||
|                             className='btn btn-dark' | ||||
|                           > | ||||
|                             Supprimer l'option | ||||
|                           </button> | ||||
|                         </div> | ||||
|  | ||||
|                         <label className='form-label' htmlFor={`optionName-${optionIndex}-${index}`}>Nom de l'option</label> | ||||
|                         <input onChange={(event) => handleChangeOption(index, optionIndex, event)} value={option.name} id={`optionName-${optionIndex}-${index}`} name='name' type='text' className='form-control' placeholder="Nom de l'option" /> | ||||
|                         <label | ||||
|                           className='form-label' | ||||
|                           htmlFor={`optionName-${optionIndex}-${index}`} | ||||
|                         > | ||||
|                           Nom de l'option | ||||
|                         </label> | ||||
|                         <input | ||||
|                           onChange={event => | ||||
|                             handleChangeOption(index, optionIndex, event)} | ||||
|                           value={option.name} | ||||
|                           id={`optionName-${optionIndex}-${index}`} | ||||
|                           name='name' | ||||
|                           type='text' | ||||
|                           className='form-control' | ||||
|                           placeholder="Nom de l'option" | ||||
|                         /> | ||||
|  | ||||
|                         <br /> | ||||
|                         <label className='form-label' htmlFor={`optionValue-${optionIndex}-${index}`}>Valeur de l'option</label> | ||||
|                         <input onChange={(event) => handleChangeOption(index, optionIndex, event)} value={option.value} id={`optionValue-${optionIndex}-${index}`} name='value' type='text' className='form-control' placeholder="Valeur de l'option" /> | ||||
|                         <label | ||||
|                           className='form-label' | ||||
|                           htmlFor={`optionValue-${optionIndex}-${index}`} | ||||
|                         > | ||||
|                           Valeur de l'option | ||||
|                         </label> | ||||
|                         <input | ||||
|                           onChange={event => | ||||
|                             handleChangeOption(index, optionIndex, event)} | ||||
|                           value={option.value} | ||||
|                           id={`optionValue-${optionIndex}-${index}`} | ||||
|                           name='value' | ||||
|                           type='text' | ||||
|                           className='form-control' | ||||
|                           placeholder="Valeur de l'option" | ||||
|                         /> | ||||
|                       </div> | ||||
|                     ) | ||||
|                   })} | ||||
|  | ||||
|                   <div className='form-group text-center'> | ||||
|                     <button id={`optionAdd-${index}`} onClick={addOption} type='button' className='btn btn-dark'>Ajouter une option</button> | ||||
|                     <button | ||||
|                       id={`optionAdd-${index}`} | ||||
|                       onClick={addOption} | ||||
|                       type='button' | ||||
|                       className='btn btn-dark' | ||||
|                     > | ||||
|                       Ajouter une option | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </div>} | ||||
|                 </div> | ||||
|               )} | ||||
|             </div> | ||||
|           ) | ||||
|         })} | ||||
|  | ||||
|       </form> | ||||
|  | ||||
|       <div style={{ marginBottom: '30px' }} className='form-group text-center'> | ||||
|         <button type='button' onClick={addInput} className='btn btn-dark'>Ajouter un input</button> | ||||
|         <button type='button' onClick={addInput} className='btn btn-dark'> | ||||
|           Ajouter un input | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
|   | ||||
| @@ -1,67 +1,67 @@ | ||||
| .FunctionCard { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     position: relative; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin: 0 0 50px 0; | ||||
|     cursor: pointer; | ||||
|     transition: all .3s; | ||||
|     color: var(--text-color); | ||||
|     text-decoration: none !important; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin: 0 0 50px 0; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s; | ||||
|   color: var(--text-color); | ||||
|   text-decoration: none !important; | ||||
| } | ||||
| .FunctionCard__container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| .FunctionCard:hover { | ||||
|     transform: translateY(-7px); | ||||
|   transform: translateY(-7px); | ||||
| } | ||||
| /* col-md */ | ||||
| @media (min-width: 768px) { | ||||
|     .FunctionCard { | ||||
|         margin: 0 30px 50px 30px; | ||||
|     }  | ||||
|   .FunctionCard { | ||||
|     margin: 0 30px 50px 30px; | ||||
|   } | ||||
| } | ||||
| /* col-xl */ | ||||
| @media (min-width: 1200px) { | ||||
|     .FunctionCard { | ||||
|         margin: 0 20px 50px 20px; | ||||
|     }    | ||||
|   .FunctionCard { | ||||
|     margin: 0 20px 50px 20px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .FunctionCard__top { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     flex-grow: 1; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   flex-grow: 1; | ||||
| } | ||||
| .FunctionCard__image { | ||||
|     width: 150px; | ||||
|   width: 150px; | ||||
| } | ||||
| .FunctionCard__title { | ||||
|     font-size: 1.4em; | ||||
|     margin: 0; | ||||
|     color: var(--important); | ||||
|     font-weight: 300; | ||||
|   font-size: 1.4em; | ||||
|   margin: 0; | ||||
|   color: var(--important); | ||||
|   font-weight: 300; | ||||
| } | ||||
| .FunctionCard__description { | ||||
|     margin: 20px 0 10px 0; | ||||
|   margin: 20px 0 10px 0; | ||||
| } | ||||
| .FunctionCard__info { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     width: 100%; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   width: 100%; | ||||
| } | ||||
| .FunctionCard__category { | ||||
|     border-radius: 0.5em; | ||||
|     padding: 0.5em; | ||||
|     margin-right: 20px; | ||||
|     font-size: 16.4px; | ||||
| } | ||||
|   border-radius: 0.5em; | ||||
|   padding: 0.5em; | ||||
|   margin-right: 20px; | ||||
|   font-size: 16.4px; | ||||
| } | ||||
|   | ||||
| @@ -5,51 +5,76 @@ import Loader from '../Loader' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import './FunctionCard.css' | ||||
|  | ||||
| const FunctionCard = memo(forwardRef((props, ref) => { | ||||
|   const [isLoading, setIsLoading] = useState(true) | ||||
| const FunctionCard = memo( | ||||
|   forwardRef((props, ref) => { | ||||
|     const [isLoading, setIsLoading] = useState(true) | ||||
|  | ||||
|   const handleLoad = () => { | ||||
|     setIsLoading(false) | ||||
|   } | ||||
|     const handleLoad = () => { | ||||
|       setIsLoading(false) | ||||
|     } | ||||
|  | ||||
|   const handleError = (event) => { | ||||
|     event.target.src = API_URL + '/images/functions/default.png' | ||||
|   } | ||||
|     const handleError = event => { | ||||
|       event.target.src = API_URL + '/images/functions/default.png' | ||||
|     } | ||||
|  | ||||
|   const isFormOrArticle = (props.type === 'form' || props.type === 'article') | ||||
|     const isFormOrArticle = props.type === 'form' || props.type === 'article' | ||||
|  | ||||
|   return ( | ||||
|     <Link | ||||
|       { | ||||
|         ...(props.isAdmin) | ||||
|     return ( | ||||
|       <Link | ||||
|         {...(props.isAdmin | ||||
|           ? { | ||||
|             href: '/admin/[slug]', | ||||
|             as: `/admin/${props.slug}` | ||||
|           } | ||||
|           : { | ||||
|             href: (isFormOrArticle) ? '/functions/[slug]' : `/functions/${props.slug}`, | ||||
|             href: isFormOrArticle | ||||
|               ? '/functions/[slug]' | ||||
|               : `/functions/${props.slug}`, | ||||
|             as: `/functions/${props.slug}` | ||||
|           })} | ||||
|       > | ||||
|         {/* FunctionCard a une hauteur pendant chargement */} | ||||
|         <a | ||||
|           ref={ref} | ||||
|           style={ | ||||
|             isLoading ? { height: '360px', justifyContent: 'center' } : null | ||||
|           } | ||||
|       } | ||||
|     > | ||||
|       {/* FunctionCard a une hauteur pendant chargement */} | ||||
|       <a ref={ref} style={isLoading ? { height: '360px', justifyContent: 'center' } : null} className='FunctionCard col-sm-24 col-md-10 col-xl-7'> | ||||
|         {isLoading && <Loader width='125px' height='125px' />} | ||||
|           className='FunctionCard col-sm-24 col-md-10 col-xl-7' | ||||
|         > | ||||
|           {isLoading && <Loader width='125px' height='125px' />} | ||||
|  | ||||
|         <div className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}> | ||||
|           <div className='FunctionCard__top'> | ||||
|             <img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} /> | ||||
|             <h2 className='FunctionCard__title'>{props.title}</h2> | ||||
|             <p className='FunctionCard__description text-center'>{props.description}</p> | ||||
|           <div | ||||
|             className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`} | ||||
|           > | ||||
|             <div className='FunctionCard__top'> | ||||
|               <img | ||||
|                 onLoad={handleLoad} | ||||
|                 onError={handleError} | ||||
|                 className='FunctionCard__image' | ||||
|                 alt={props.title} | ||||
|                 src={API_URL + props.image} | ||||
|               /> | ||||
|               <h2 className='FunctionCard__title'>{props.title}</h2> | ||||
|               <p className='FunctionCard__description text-center'> | ||||
|                 {props.description} | ||||
|               </p> | ||||
|             </div> | ||||
|             <div className='FunctionCard__info'> | ||||
|               <p | ||||
|                 className='FunctionCard__category' | ||||
|                 style={{ backgroundColor: props.categorie.color }} | ||||
|               > | ||||
|                 {props.categorie.name} | ||||
|               </p> | ||||
|               <p className='FunctionCard__publication-date'> | ||||
|                 {date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)} | ||||
|               </p> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className='FunctionCard__info'> | ||||
|             <p className='FunctionCard__category' style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p> | ||||
|             <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </a> | ||||
|     </Link> | ||||
|   ) | ||||
| })) | ||||
|         </a> | ||||
|       </Link> | ||||
|     ) | ||||
|   }) | ||||
| ) | ||||
|  | ||||
| export default FunctionCard | ||||
|   | ||||
| @@ -1,33 +1,33 @@ | ||||
| .CommentCard { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: .7em; | ||||
|     margin: 15px 0 15px 0; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 0.7em; | ||||
|   margin: 15px 0 15px 0; | ||||
| } | ||||
| .CommentCard__container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding: 20px; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding: 20px; | ||||
| } | ||||
| .CommentCard__user-logo { | ||||
|     border-radius: 50%; | ||||
|     object-fit: cover; | ||||
|     width: 50px; | ||||
|     height: 50px; | ||||
|     cursor: pointer; | ||||
|   border-radius: 50%; | ||||
|   object-fit: cover; | ||||
|   width: 50px; | ||||
|   height: 50px; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .CommentCard__message-info { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     margin-left: 10px; | ||||
|     font-size: 16px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   margin-left: 10px; | ||||
|   font-size: 16px; | ||||
| } | ||||
| .CommentCard__message { | ||||
|     line-height: 1.8; | ||||
|     margin: 15px 0 0 0; | ||||
| } | ||||
|   line-height: 1.8; | ||||
|   margin: 15px 0 0 0; | ||||
| } | ||||
|   | ||||
| @@ -23,28 +23,42 @@ const CommentCard = forwardRef((props, ref) => { | ||||
|     props.manageComment.setLoadingComments(true) | ||||
|     if (isAuth && user.token != null) { | ||||
|       try { | ||||
|         await api.delete(`/comments/${props.id}`, { headers: { Authorization: user.token } }) | ||||
|         await api.delete(`/comments/${props.id}`, { | ||||
|           headers: { Authorization: user.token } | ||||
|         }) | ||||
|         const newCommentsData = { ...props.manageComment.commentsData } | ||||
|         const commentIndex = newCommentsData.rows.findIndex((value) => value.id === props.id) | ||||
|         const commentIndex = newCommentsData.rows.findIndex( | ||||
|           value => value.id === props.id | ||||
|         ) | ||||
|         newCommentsData.rows.splice(commentIndex, 1) | ||||
|         props.manageComment.setCommentsData({ hasMore: props.manageComment.commentsData.hasMore, rows: newCommentsData.rows }) | ||||
|         props.manageComment.setCommentsData({ | ||||
|           hasMore: props.manageComment.commentsData.hasMore, | ||||
|           rows: newCommentsData.rows | ||||
|         }) | ||||
|       } catch {} | ||||
|     } | ||||
|     props.manageComment.setLoadingComments(false) | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     setEditInput(event.target.value) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = (event) => { | ||||
|   const handleSubmit = event => { | ||||
|     event.preventDefault() | ||||
|     api.put(`/comments/${props.id}`, { message: editInput }, { headers: { Authorization: user.token } }) | ||||
|       .then((_response) => { | ||||
|     api | ||||
|       .put( | ||||
|         `/comments/${props.id}`, | ||||
|         { message: editInput }, | ||||
|         { headers: { Authorization: user.token } } | ||||
|       ) | ||||
|       .then(_response => { | ||||
|         setEditing(false) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         setMessage(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) | ||||
|       .catch(error => { | ||||
|         setMessage( | ||||
|           `<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>` | ||||
|         ) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
| @@ -58,47 +72,73 @@ const CommentCard = forwardRef((props, ref) => { | ||||
|       <div className='CommentCard__container'> | ||||
|         <div className='row'> | ||||
|           <Link href='/users/[name]' as={`/users/${props.user.name}`}> | ||||
|             <img className='CommentCard__user-logo' src={API_URL + props.user.logo} alt={props.user.name} /> | ||||
|             <img | ||||
|               className='CommentCard__user-logo' | ||||
|               src={API_URL + props.user.logo} | ||||
|               alt={props.user.name} | ||||
|             /> | ||||
|           </Link> | ||||
|           <span className='CommentCard__message-info'> | ||||
|             <Link href='/users/[name]' as={`/users/${props.user.name}`}> | ||||
|               <a>{props.user.name}</a> | ||||
|             </Link> | ||||
|                          - {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)} | ||||
|              -{' '} | ||||
|             {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)} | ||||
|           </span> | ||||
|         </div> | ||||
|         <div className='row'> | ||||
|           <div className='col-24'> | ||||
|             { | ||||
|               (!isEditing) | ||||
|                 ? ( | ||||
|                   <> | ||||
|                     <div className='CommentCard__message'> | ||||
|                       <ReactMarkdown source={editInput} renderers={{ code: CodeBlock }} /> | ||||
|                     </div> | ||||
|                     {(isAuth && user.name === props.user.name) && | ||||
|                       <p style={{ fontSize: '15px', margin: '15px 0 0 0', fontStyle: 'italic' }}> | ||||
|                         <a onClick={deleteCommentById} href='#'>supprimer</a> | ||||
|                                                -  | ||||
|                         <a style={{ cursor: 'pointer' }} onClick={editComment}>modifier</a> | ||||
|                       </p>} | ||||
|                   </> | ||||
|                 ) | ||||
|                 : ( | ||||
|                   <form onSubmit={handleSubmit}> | ||||
|                     <div className='form-group FunctionComments__post-group'> | ||||
|                       <label className='form-label' htmlFor='commentEdit'>Modifier le commentaire :</label> | ||||
|                       <textarea style={{ height: 'auto' }} value={editInput} onChange={handleChange} name='commentEdit' id='commentEdit' className='form-control' rows='5' placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" /> | ||||
|                     </div> | ||||
|                     <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                       <button type='submit' className='btn btn-dark'>Envoyer</button> | ||||
|                     </div> | ||||
|                     <div className='text-center'> | ||||
|                       {htmlParser(message)} | ||||
|                     </div> | ||||
|                   </form> | ||||
|                 ) | ||||
|             } | ||||
|             {!isEditing ? ( | ||||
|               <> | ||||
|                 <div className='CommentCard__message'> | ||||
|                   <ReactMarkdown | ||||
|                     source={editInput} | ||||
|                     renderers={{ code: CodeBlock }} | ||||
|                   /> | ||||
|                 </div> | ||||
|                 {isAuth && user.name === props.user.name && ( | ||||
|                   <p | ||||
|                     style={{ | ||||
|                       fontSize: '15px', | ||||
|                       margin: '15px 0 0 0', | ||||
|                       fontStyle: 'italic' | ||||
|                     }} | ||||
|                   > | ||||
|                     <a onClick={deleteCommentById} href='#'> | ||||
|                       supprimer | ||||
|                     </a> | ||||
|                      -  | ||||
|                     <a style={{ cursor: 'pointer' }} onClick={editComment}> | ||||
|                       modifier | ||||
|                     </a> | ||||
|                   </p> | ||||
|                 )} | ||||
|               </> | ||||
|             ) : ( | ||||
|               <form onSubmit={handleSubmit}> | ||||
|                 <div className='form-group FunctionComments__post-group'> | ||||
|                   <label className='form-label' htmlFor='commentEdit'> | ||||
|                     Modifier le commentaire : | ||||
|                   </label> | ||||
|                   <textarea | ||||
|                     style={{ height: 'auto' }} | ||||
|                     value={editInput} | ||||
|                     onChange={handleChange} | ||||
|                     name='commentEdit' | ||||
|                     id='commentEdit' | ||||
|                     className='form-control' | ||||
|                     rows='5' | ||||
|                     placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                   <button type='submit' className='btn btn-dark'> | ||||
|                     Envoyer | ||||
|                   </button> | ||||
|                 </div> | ||||
|                 <div className='text-center'>{htmlParser(message)}</div> | ||||
|               </form> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -3,7 +3,11 @@ import htmlParser from 'html-react-parser' | ||||
| const FunctionArticle = ({ article }) => { | ||||
|   return ( | ||||
|     <div style={{ marginBottom: '50px' }} className='container-fluid'> | ||||
|       {(article != null) ? htmlParser(article) : <p className='text-center'>L'article n'est pas encore disponible.</p>} | ||||
|       {article != null ? ( | ||||
|         htmlParser(article) | ||||
|       ) : ( | ||||
|         <p className='text-center'>L'article n'est pas encore disponible.</p> | ||||
|       )} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| .FunctionComments__row { | ||||
|     margin-bottom: 20px; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .FunctionComments__textarea { | ||||
|     height: auto; | ||||
|     resize: vertical; | ||||
| } | ||||
|   height: auto; | ||||
|   resize: vertical; | ||||
| } | ||||
|   | ||||
| @@ -18,49 +18,69 @@ const FunctionComments = ({ functionId }) => { | ||||
|  | ||||
|   // Récupère les commentaires si la page change | ||||
|   useEffect(() => { | ||||
|     getCommentsData().then((data) => setCommentsData({ | ||||
|       hasMore: data.hasMore, | ||||
|       rows: [...commentsData.rows, ...data.rows] | ||||
|     })) | ||||
|     getCommentsData().then(data => | ||||
|       setCommentsData({ | ||||
|         hasMore: data.hasMore, | ||||
|         rows: [...commentsData.rows, ...data.rows] | ||||
|       }) | ||||
|     ) | ||||
|   }, [pageComments]) | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastCommentCardRef = useCallback((node) => { | ||||
|     if (isLoadingComments) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && commentsData.hasMore) { | ||||
|         setPageComments(pageComments + 1) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingComments, commentsData.hasMore]) | ||||
|   const lastCommentCardRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingComments) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && commentsData.hasMore) { | ||||
|             setPageComments(pageComments + 1) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingComments, commentsData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   const getCommentsData = async () => { | ||||
|     setLoadingComments(true) | ||||
|     const { data } = await api.get(`/comments/${functionId}/?page=${pageComments}&limit=10`) | ||||
|     const { data } = await api.get( | ||||
|       `/comments/${functionId}/?page=${pageComments}&limit=10` | ||||
|     ) | ||||
|     setLoadingComments(false) | ||||
|     return data | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async (event) => { | ||||
|   const handleSubmit = async event => { | ||||
|     setLoadingComments(true) | ||||
|     event.preventDefault() | ||||
|     const token = user.token | ||||
|     if (isAuth && token != null && inputState.commentPost != null) { | ||||
|       try { | ||||
|         const response = await api.post(`/comments/${functionId}`, { message: inputState.commentPost }, { headers: { Authorization: token } }) | ||||
|         const comment = { ...response.data, user: { name: user.name, logo: user.logo } } | ||||
|         setCommentsData({ hasMore: commentsData.hasMore, rows: [comment, ...commentsData.rows] }) | ||||
|         const response = await api.post( | ||||
|           `/comments/${functionId}`, | ||||
|           { message: inputState.commentPost }, | ||||
|           { headers: { Authorization: token } } | ||||
|         ) | ||||
|         const comment = { | ||||
|           ...response.data, | ||||
|           user: { name: user.name, logo: user.logo } | ||||
|         } | ||||
|         setCommentsData({ | ||||
|           hasMore: commentsData.hasMore, | ||||
|           rows: [comment, ...commentsData.rows] | ||||
|         }) | ||||
|         setInputState({ commentPost: '' }) | ||||
|       } catch { } | ||||
|       } catch {} | ||||
|     } | ||||
|     setLoadingComments(false) | ||||
|   } | ||||
| @@ -70,40 +90,73 @@ const FunctionComments = ({ functionId }) => { | ||||
|       <div className='FunctionComments__post container-fluid'> | ||||
|         <div className='row FunctionComments__row'> | ||||
|           <div className='col-24'> | ||||
|             { | ||||
|               (isAuth) | ||||
|                 ? ( | ||||
|                   <form onSubmit={handleSubmit}> | ||||
|                     <div className='form-group FunctionComments__post-group'> | ||||
|                       <label className='form-label' htmlFor='commentPost'>Ajouter un commentaire :</label> | ||||
|                       <textarea className='FunctionComments__textarea form-control' value={inputState.commentPost} onChange={handleChange} name='commentPost' id='commentPost' placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" /> | ||||
|                     </div> | ||||
|                     <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                       <button type='submit' className='btn btn-dark'>Envoyer</button> | ||||
|                     </div> | ||||
|                   </form> | ||||
|                 ) | ||||
|                 : ( | ||||
|                   <p className='text-center'> | ||||
|                                       Vous devez être <Link href='/users/login'><a>connecté</a></Link> pour poster un commentaire. | ||||
|                   </p> | ||||
|                 ) | ||||
|             } | ||||
|             {isAuth ? ( | ||||
|               <form onSubmit={handleSubmit}> | ||||
|                 <div className='form-group FunctionComments__post-group'> | ||||
|                   <label className='form-label' htmlFor='commentPost'> | ||||
|                     Ajouter un commentaire : | ||||
|                   </label> | ||||
|                   <textarea | ||||
|                     className='FunctionComments__textarea form-control' | ||||
|                     value={inputState.commentPost} | ||||
|                     onChange={handleChange} | ||||
|                     name='commentPost' | ||||
|                     id='commentPost' | ||||
|                     placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div className='form-group' style={{ marginTop: '0.7em' }}> | ||||
|                   <button type='submit' className='btn btn-dark'> | ||||
|                     Envoyer | ||||
|                   </button> | ||||
|                 </div> | ||||
|               </form> | ||||
|             ) : ( | ||||
|               <p className='text-center'> | ||||
|                 Vous devez être{' '} | ||||
|                 <Link href='/users/login'> | ||||
|                   <a>connecté</a> | ||||
|                 </Link>{' '} | ||||
|                 pour poster un commentaire. | ||||
|               </p> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className='container-fluid'> | ||||
|         {isLoadingComments && | ||||
|         {isLoadingComments && ( | ||||
|           <div className='row justify-content-center'> | ||||
|             <Loader /> | ||||
|           </div>} | ||||
|           </div> | ||||
|         )} | ||||
|         <div className='row justify-content-center'> | ||||
|           {commentsData.rows.map((comment, index) => { | ||||
|             // Si c'est le dernier élément | ||||
|             if (commentsData.rows.length === index + 1) { | ||||
|               return <CommentCard key={comment.id} ref={lastCommentCardRef} {...comment} manageComment={{ setCommentsData, commentsData, setLoadingComments }} /> | ||||
|               return ( | ||||
|                 <CommentCard | ||||
|                   key={comment.id} | ||||
|                   ref={lastCommentCardRef} | ||||
|                   {...comment} | ||||
|                   manageComment={{ | ||||
|                     setCommentsData, | ||||
|                     commentsData, | ||||
|                     setLoadingComments | ||||
|                   }} | ||||
|                 /> | ||||
|               ) | ||||
|             } | ||||
|             return <CommentCard key={comment.id} {...comment} manageComment={{ setCommentsData, commentsData, setLoadingComments }} /> | ||||
|             return ( | ||||
|               <CommentCard | ||||
|                 key={comment.id} | ||||
|                 {...comment} | ||||
|                 manageComment={{ | ||||
|                   setCommentsData, | ||||
|                   commentsData, | ||||
|                   setLoadingComments | ||||
|                 }} | ||||
|               /> | ||||
|             ) | ||||
|           })} | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import api from '../../utils/api' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import '../FunctionCard/FunctionCard.css' | ||||
|  | ||||
| const FunctionComponentTop = (props) => { | ||||
| const FunctionComponentTop = props => { | ||||
|   const { isAuth, user } = useContext(UserContext) | ||||
|   const [isFavorite, setIsFavorite] = useState(false) | ||||
|  | ||||
| @@ -21,7 +21,9 @@ const FunctionComponentTop = (props) => { | ||||
|  | ||||
|   const fetchFavorite = async () => { | ||||
|     try { | ||||
|       const favoriteResponse = await api.get(`/favorites/${props.id}`, { headers: { Authorization: user.token } }) | ||||
|       const favoriteResponse = await api.get(`/favorites/${props.id}`, { | ||||
|         headers: { Authorization: user.token } | ||||
|       }) | ||||
|       setIsFavorite(favoriteResponse.data.isFavorite) | ||||
|     } catch {} | ||||
|   } | ||||
| @@ -30,16 +32,22 @@ const FunctionComponentTop = (props) => { | ||||
|     if (isAuth && user.token != null) { | ||||
|       try { | ||||
|         if (isFavorite) { | ||||
|           const response = await api.delete(`/favorites/${props.id}`, { headers: { Authorization: user.token } }) | ||||
|           const response = await api.delete(`/favorites/${props.id}`, { | ||||
|             headers: { Authorization: user.token } | ||||
|           }) | ||||
|           if (response.status === 200) return setIsFavorite(false) | ||||
|         } | ||||
|         const response = await api.post(`/favorites/${props.id}`, {}, { headers: { Authorization: user.token } }) | ||||
|         const response = await api.post( | ||||
|           `/favorites/${props.id}`, | ||||
|           {}, | ||||
|           { headers: { Authorization: user.token } } | ||||
|         ) | ||||
|         if (response.status === 201) return setIsFavorite(true) | ||||
|       } catch {} | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleError = (event) => { | ||||
|   const handleError = event => { | ||||
|     event.target.src = API_URL + '/images/functions/default.png' | ||||
|   } | ||||
|  | ||||
| @@ -47,18 +55,44 @@ const FunctionComponentTop = (props) => { | ||||
|     <div className='container-fluid'> | ||||
|       <div className='row justify-content-center text-center'> | ||||
|         <div className='FunctionComponent__top col-24'> | ||||
|           {isAuth && ( | ||||
|             <FontAwesomeIcon | ||||
|               onClick={toggleFavorite} | ||||
|               {...(isFavorite ? { icon: faStar } : { icon: farStar })} | ||||
|               title={ | ||||
|                 isFavorite | ||||
|                   ? 'Retirer la fonction des favoris' | ||||
|                   : 'Ajouter la fonction aux favoris' | ||||
|               } | ||||
|               className='FunctionComponent__star-favorite' | ||||
|             /> | ||||
|           )} | ||||
|  | ||||
|           {(isAuth) && | ||||
|             <FontAwesomeIcon onClick={toggleFavorite} {...(isFavorite) ? { icon: faStar } : { icon: farStar }} title={(isFavorite) ? 'Retirer la fonction des favoris' : 'Ajouter la fonction aux favoris'} className='FunctionComponent__star-favorite' />} | ||||
|  | ||||
|           <img onError={handleError} className='FunctionComponent__image' src={API_URL + props.image} alt={props.title} /> | ||||
|           <h1 className='FunctionComponent__title title-important'>{props.title}</h1> | ||||
|           <img | ||||
|             onError={handleError} | ||||
|             className='FunctionComponent__image' | ||||
|             src={API_URL + props.image} | ||||
|             alt={props.title} | ||||
|           /> | ||||
|           <h1 className='FunctionComponent__title title-important'> | ||||
|             {props.title} | ||||
|           </h1> | ||||
|           <p className='FunctionComponent__description'>{props.description}</p> | ||||
|           <div className='FunctionCard__info'> | ||||
|             <Link href={`/functions?categoryId=${props.categorieId}`}> | ||||
|               <a className='FunctionCard__category' style={{ backgroundColor: props.categorie.color, color: 'inherit' }}>{props.categorie.name}</a> | ||||
|               <a | ||||
|                 className='FunctionCard__category' | ||||
|                 style={{ | ||||
|                   backgroundColor: props.categorie.color, | ||||
|                   color: 'inherit' | ||||
|                 }} | ||||
|               > | ||||
|                 {props.categorie.name} | ||||
|               </a> | ||||
|             </Link> | ||||
|             <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|             <p className='FunctionCard__publication-date'> | ||||
|               {date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)} | ||||
|             </p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import 'react-datepicker/dist/react-datepicker.css' | ||||
|  | ||||
| registerLocale('fr', fr) | ||||
|  | ||||
| const FunctionForm = (props) => { | ||||
| const FunctionForm = props => { | ||||
|   const [inputState, setInputState] = useState({}) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
| @@ -18,7 +18,7 @@ const FunctionForm = (props) => { | ||||
|   // inputState par défaut | ||||
|   useEffect(() => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     props.inputsArray.forEach((input) => { | ||||
|     props.inputsArray.forEach(input => { | ||||
|       if (input.type === 'select' && input.options.length > 0) { | ||||
|         inputStateNew[input.name] = input.options[0].value | ||||
|       } | ||||
| @@ -26,21 +26,22 @@ const FunctionForm = (props) => { | ||||
|     setInputState(inputStateNew) | ||||
|   }, []) | ||||
|  | ||||
|   const handleSubmit = (event) => { | ||||
|   const handleSubmit = event => { | ||||
|     setIsLoading(true) | ||||
|     event.preventDefault() | ||||
|     api.post(`/functions/${props.slug}`, inputState) | ||||
|       .then((response) => { | ||||
|     api | ||||
|       .post(`/functions/${props.slug}`, inputState) | ||||
|       .then(response => { | ||||
|         setMessage(response.data.resultHTML) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|       .catch(error => { | ||||
|         setMessage(error.response.data.message) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = event.target.value | ||||
|     setInputState(inputStateNew) | ||||
| @@ -61,16 +62,35 @@ const FunctionForm = (props) => { | ||||
|             case 'text': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <input onChange={handleChange} type='text' name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> | ||||
|                   <label className='form-label' htmlFor={input.name}> | ||||
|                     {input.label} | ||||
|                   </label> | ||||
|                   <input | ||||
|                     onChange={handleChange} | ||||
|                     type='text' | ||||
|                     name={input.name} | ||||
|                     id={input.name} | ||||
|                     placeholder={input.placeholder} | ||||
|                     className='form-control' | ||||
|                   /> | ||||
|                 </div> | ||||
|               ) | ||||
|             case 'integer': | ||||
|             case 'float': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <input onChange={handleChange} type='number' step={(input.type === 'integer') ? '1' : '0.01'} name={input.name} id={input.name} placeholder={input.placeholder} className='form-control' /> | ||||
|                   <label className='form-label' htmlFor={input.name}> | ||||
|                     {input.label} | ||||
|                   </label> | ||||
|                   <input | ||||
|                     onChange={handleChange} | ||||
|                     type='number' | ||||
|                     step={input.type === 'integer' ? '1' : '0.01'} | ||||
|                     name={input.name} | ||||
|                     id={input.name} | ||||
|                     placeholder={input.placeholder} | ||||
|                     className='form-control' | ||||
|                   /> | ||||
|                 </div> | ||||
|               ) | ||||
|             case 'calendar': | ||||
| @@ -81,13 +101,14 @@ const FunctionForm = (props) => { | ||||
|                 document.body.appendChild(newScript) | ||||
|               } | ||||
|               // eslint-disable-next-line | ||||
|               const DatePicker = dynamic( | ||||
|                 () => import('react-datepicker'), | ||||
|                 { ssr: false } | ||||
|               ) | ||||
|               const DatePicker = dynamic(() => import('react-datepicker'), { | ||||
|                 ssr: false | ||||
|               }) | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <label className='form-label' htmlFor={input.name}> | ||||
|                     {input.label} | ||||
|                   </label> | ||||
|                   <br /> | ||||
|                   <DatePicker | ||||
|                     selected={(() => { | ||||
| @@ -97,7 +118,11 @@ const FunctionForm = (props) => { | ||||
|                           const year = dateArray[2] | ||||
|                           const month = dateArray[1] | ||||
|                           const day = dateArray[0] | ||||
|                           return new Date(year, parseInt(month) - 1, parseInt(day) + 1) | ||||
|                           return new Date( | ||||
|                             year, | ||||
|                             parseInt(month) - 1, | ||||
|                             parseInt(day) + 1 | ||||
|                           ) | ||||
|                         } | ||||
|                         throw new Error('Not a valid date') | ||||
|                       } catch { | ||||
| @@ -108,9 +133,13 @@ const FunctionForm = (props) => { | ||||
|                     dateFormat='dd/MM/yyyy' | ||||
|                     fixedHeight | ||||
|                     placeholderText={input.placeholder} | ||||
|                     onChange={(dateObject) => { | ||||
|                     onChange={dateObject => { | ||||
|                       try { | ||||
|                         const formattedDate = date.format(dateObject, 'DD/MM/YYYY', true) | ||||
|                         const formattedDate = date.format( | ||||
|                           dateObject, | ||||
|                           'DD/MM/YYYY', | ||||
|                           true | ||||
|                         ) | ||||
|                         handleChange({ | ||||
|                           target: { | ||||
|                             name: input.name, | ||||
| @@ -125,33 +154,39 @@ const FunctionForm = (props) => { | ||||
|             case 'select': | ||||
|               return ( | ||||
|                 <div key={index} className='form-group'> | ||||
|                   <label className='form-label' htmlFor={input.name}>{input.label}</label> | ||||
|                   <select onChange={handleChange} name={input.name} id={input.name} value={inputState[input.name] || input.options[0]} className='form-control'> | ||||
|                   <label className='form-label' htmlFor={input.name}> | ||||
|                     {input.label} | ||||
|                   </label> | ||||
|                   <select | ||||
|                     onChange={handleChange} | ||||
|                     name={input.name} | ||||
|                     id={input.name} | ||||
|                     value={inputState[input.name] || input.options[0]} | ||||
|                     className='form-control' | ||||
|                   > | ||||
|                     {input.options.map((option, optionIndex) => { | ||||
|                       return ( | ||||
|                         <option key={optionIndex} value={option.value}>{option.name}</option> | ||||
|                         <option key={optionIndex} value={option.value}> | ||||
|                           {option.name} | ||||
|                         </option> | ||||
|                       ) | ||||
|                     })} | ||||
|                   </select> | ||||
|                 </div> | ||||
|               ) | ||||
|             default: | ||||
|               return ( | ||||
|                 <p>Erreur, l'input n'est pas valide...</p> | ||||
|               ) | ||||
|               return <p>Erreur, l'input n'est pas valide...</p> | ||||
|           } | ||||
|         })} | ||||
|  | ||||
|         <div className='form-group text-center'> | ||||
|           <button type='submit' className='btn btn-dark'>Envoyer</button> | ||||
|           <button type='submit' className='btn btn-dark'> | ||||
|             Envoyer | ||||
|           </button> | ||||
|         </div> | ||||
|       </form> | ||||
|       <div className='form-result text-center'> | ||||
|         { | ||||
|           (isLoading) | ||||
|             ? <Loader /> | ||||
|             : htmlParser(message) | ||||
|         } | ||||
|         {isLoading ? <Loader /> : htmlParser(message)} | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
|   | ||||
| @@ -4,17 +4,29 @@ import HeadTag from '../HeadTag' | ||||
| import FunctionTabsTop from './FunctionTabsTop' | ||||
| import FunctionComponentTop from './FunctionComponentTop' | ||||
|  | ||||
| const FunctionPage = (props) => { | ||||
| const FunctionPage = props => { | ||||
|   const [slideIndex, setSlideIndex] = useState(0) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> | ||||
|       <HeadTag | ||||
|         title={props.title} | ||||
|         description={props.description} | ||||
|         image={API_URL + props.image} | ||||
|       /> | ||||
|  | ||||
|       <div className='container-fluid'> | ||||
|         <FunctionTabsTop slideIndex={slideIndex} setSlideIndex={setSlideIndex} tabNames={props.tabNames} /> | ||||
|         <FunctionTabsTop | ||||
|           slideIndex={slideIndex} | ||||
|           setSlideIndex={setSlideIndex} | ||||
|           tabNames={props.tabNames} | ||||
|         /> | ||||
|         <FunctionComponentTop {...props} /> | ||||
|         <props.FunctionTabManager {...props} slideIndex={slideIndex} setSlideIndex={setSlideIndex} /> | ||||
|         <props.FunctionTabManager | ||||
|           {...props} | ||||
|           slideIndex={slideIndex} | ||||
|           setSlideIndex={setSlideIndex} | ||||
|         /> | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
|   | ||||
| @@ -1,41 +1,41 @@ | ||||
| .FunctionTabs__nav { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     padding-left: 0; | ||||
|     margin-bottom: 0; | ||||
|     list-style: none; | ||||
|     border-bottom: 1px solid #d9e2ef; | ||||
|     margin-bottom: -1px; | ||||
|     margin-top: 30px; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   padding-left: 0; | ||||
|   margin-bottom: 0; | ||||
|   list-style: none; | ||||
|   border-bottom: 1px solid #d9e2ef; | ||||
|   margin-bottom: -1px; | ||||
|   margin-top: 30px; | ||||
| } | ||||
| .FunctionTabs__nav-item { | ||||
|     margin-bottom: -1px; | ||||
|     cursor: pointer; | ||||
|   margin-bottom: -1px; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .FunctionTabs__nav-link { | ||||
|     color: var(--text-color); | ||||
|     border: 1px solid #0c0b0b38; | ||||
|     border-bottom: 0px; | ||||
|     border-top-left-radius: .375rem; | ||||
|     border-top-right-radius: .375rem; | ||||
|     display: block; | ||||
|     padding: .5rem 1rem; | ||||
|     transition: .2s; | ||||
|   color: var(--text-color); | ||||
|   border: 1px solid #0c0b0b38; | ||||
|   border-bottom: 0px; | ||||
|   border-top-left-radius: 0.375rem; | ||||
|   border-top-right-radius: 0.375rem; | ||||
|   display: block; | ||||
|   padding: 0.5rem 1rem; | ||||
|   transition: 0.2s; | ||||
| } | ||||
| .FunctionTabs__nav-link-active { | ||||
|     border-color: #d9e2ef #d9e2ef #fff; | ||||
|     color: var(--important); | ||||
|   border-color: #d9e2ef #d9e2ef #fff; | ||||
|   color: var(--important); | ||||
| } | ||||
| .FunctionTabs__nav-link:hover { | ||||
|     border-color: #f1f4f8 #f1f4f8 #d9e2ef; | ||||
|     text-decoration: none; | ||||
|   border-color: #f1f4f8 #f1f4f8 #d9e2ef; | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | ||||
| @media (max-width: 490px) { | ||||
|     .FunctionTabs__nav { | ||||
|         flex-direction: column; | ||||
|     } | ||||
|     .FunctionTabs__nav-link { | ||||
|         border-color: #f1f4f8 #f1f4f8 #d9e2ef; | ||||
|     } | ||||
| } | ||||
|   .FunctionTabs__nav { | ||||
|     flex-direction: column; | ||||
|   } | ||||
|   .FunctionTabs__nav-link { | ||||
|     border-color: #f1f4f8 #f1f4f8 #d9e2ef; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| import SwipeableViews from 'react-swipeable-views' | ||||
| import './FunctionTabs.css' | ||||
|  | ||||
| const FunctionTabs = (props) => { | ||||
| const FunctionTabs = props => { | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <SwipeableViews onChangeIndex={(index) => props.setSlideIndex(index)} index={props.slideIndex}> | ||||
|       <SwipeableViews | ||||
|         onChangeIndex={index => props.setSlideIndex(index)} | ||||
|         index={props.slideIndex} | ||||
|       > | ||||
|         {props.children} | ||||
|       </SwipeableViews> | ||||
|     </div> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| const FunctionTabsTop = (props) => { | ||||
| const FunctionTabsTop = props => { | ||||
|   return ( | ||||
|     <div className='container'> | ||||
|       <div className='row justify-content-center'> | ||||
| @@ -7,7 +7,11 @@ const FunctionTabsTop = (props) => { | ||||
|             return ( | ||||
|               <li key={index} className='FunctionTabs__nav-item'> | ||||
|                 <a | ||||
|                   className={`FunctionTabs__nav-link ${(props.slideIndex === index) ? 'FunctionTabs__nav-link-active' : ''}`} | ||||
|                   className={`FunctionTabs__nav-link ${ | ||||
|                     props.slideIndex === index | ||||
|                       ? 'FunctionTabs__nav-link-active' | ||||
|                       : '' | ||||
|                   }`} | ||||
|                   onClick={() => props.setSlideIndex(index)} | ||||
|                 > | ||||
|                   {tabName} | ||||
|   | ||||
| @@ -1,40 +1,40 @@ | ||||
| .Functions__title { | ||||
|     padding: 20px 0 20px 0; | ||||
|     margin-bottom: 0; | ||||
|   padding: 20px 0 20px 0; | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| .Functions__form-control { | ||||
|     display: block; | ||||
|     height: calc(1.5em + .75rem + 2px); | ||||
|     padding: .375rem .75rem; | ||||
|     font-size: 1rem; | ||||
|     font-weight: 400; | ||||
|     line-height: 1.5; | ||||
|     color: #495057; | ||||
|     background-color: #fff; | ||||
|     background-clip: padding-box; | ||||
|     border: 1px solid #ced4da; | ||||
|     border-radius: .5em; | ||||
|   display: block; | ||||
|   height: calc(1.5em + 0.75rem + 2px); | ||||
|   padding: 0.375rem 0.75rem; | ||||
|   font-size: 1rem; | ||||
|   font-weight: 400; | ||||
|   line-height: 1.5; | ||||
|   color: #495057; | ||||
|   background-color: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid #ced4da; | ||||
|   border-radius: 0.5em; | ||||
| } | ||||
| .Functions__search-container { | ||||
|     margin-bottom: 50px; | ||||
|   margin-bottom: 50px; | ||||
| } | ||||
| .Functions__select-option { | ||||
|     color: rgb(221, 220, 220); | ||||
|   color: rgb(221, 220, 220); | ||||
| } | ||||
| .Functions__search-input { | ||||
|     width: 40%; | ||||
|   width: 40%; | ||||
| } | ||||
| /* col-sm */ | ||||
| @media (max-width: 576px) { | ||||
|     .Functions__search-container { | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|     } | ||||
|     .Functions__select { | ||||
|         width: 90%; | ||||
|         margin-bottom: 5px; | ||||
|     } | ||||
|     .Functions__search-input { | ||||
|         width: 90%; | ||||
|     } | ||||
| } | ||||
|   .Functions__search-container { | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|   } | ||||
|   .Functions__select { | ||||
|     width: 90%; | ||||
|     margin-bottom: 5px; | ||||
|   } | ||||
|   .Functions__search-input { | ||||
|     width: 90%; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -7,15 +7,21 @@ import useAPI from '../../hooks/useAPI' | ||||
| import './FunctionsList.css' | ||||
|  | ||||
| let pageFunctions = 1 | ||||
| const FunctionsList = (props) => { | ||||
| const FunctionsList = props => { | ||||
|   const { categoryId } = useRouter().query | ||||
|  | ||||
|   // State de recherche et de catégories | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [inputSearch, setInputSearch] = useState({ search: '', selectedCategory: categoryId || '0' }) | ||||
|   const [inputSearch, setInputSearch] = useState({ | ||||
|     search: '', | ||||
|     selectedCategory: categoryId || '0' | ||||
|   }) | ||||
|  | ||||
|   // State pour afficher les fonctions | ||||
|   const [functionsData, setFunctionsData] = useState({ hasMore: true, rows: [] }) | ||||
|   const [functionsData, setFunctionsData] = useState({ | ||||
|     hasMore: true, | ||||
|     rows: [] | ||||
|   }) | ||||
|   const [isLoadingFunctions, setLoadingFunctions] = useState(true) | ||||
|  | ||||
|   // Récupère la catégorie avec la query categoryId | ||||
| @@ -28,43 +34,54 @@ const FunctionsList = (props) => { | ||||
|   // Récupère les fonctions si la catégorie/recherche change | ||||
|   useEffect(() => { | ||||
|     pageFunctions = 1 | ||||
|     getFunctionsData().then((data) => setFunctionsData(data)) | ||||
|     getFunctionsData().then(data => setFunctionsData(data)) | ||||
|   }, [inputSearch]) | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastFunctionCardRef = useCallback((node) => { | ||||
|     if (isLoadingFunctions) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && functionsData.hasMore) { | ||||
|         pageFunctions += 1 | ||||
|         getFunctionsData().then((data) => { | ||||
|           setFunctionsData((oldData) => { | ||||
|             return { | ||||
|               hasMore: data.hasMore, | ||||
|               rows: [...oldData.rows, ...data.rows] | ||||
|             } | ||||
|           }) | ||||
|         }) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingFunctions, functionsData.hasMore]) | ||||
|   const lastFunctionCardRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingFunctions) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && functionsData.hasMore) { | ||||
|             pageFunctions += 1 | ||||
|             getFunctionsData().then(data => { | ||||
|               setFunctionsData(oldData => { | ||||
|                 return { | ||||
|                   hasMore: data.hasMore, | ||||
|                   rows: [...oldData.rows, ...data.rows] | ||||
|                 } | ||||
|               }) | ||||
|             }) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingFunctions, functionsData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   const getFunctionsData = async () => { | ||||
|     setLoadingFunctions(true) | ||||
|     const URL = `${(props.isAdmin) ? '/admin/functions' : '/functions'}?page=${pageFunctions}&limit=10&categoryId=${inputSearch.selectedCategory}&search=${inputSearch.search}` | ||||
|     const URL = `${ | ||||
|       props.isAdmin ? '/admin/functions' : '/functions' | ||||
|     }?page=${pageFunctions}&limit=10&categoryId=${ | ||||
|       inputSearch.selectedCategory | ||||
|     }&search=${inputSearch.search}` | ||||
|     const { data } = await api.get(URL, { | ||||
|       headers: { | ||||
|         ...(props.isAdmin && props.token != null) && { Authorization: props.token } | ||||
|         ...(props.isAdmin && | ||||
|           props.token != null && { Authorization: props.token }) | ||||
|       } | ||||
|     }) | ||||
|     setLoadingFunctions(false) | ||||
|     return data | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     const inputSearchNew = { ...inputSearch } | ||||
|     inputSearchNew[event.target.name] = event.target.value | ||||
|     setInputSearch(inputSearchNew) | ||||
| @@ -72,27 +89,58 @@ const FunctionsList = (props) => { | ||||
|  | ||||
|   return ( | ||||
|     <div className='container text-center'> | ||||
|       <div className='row justify-content-center'> | ||||
|         {props.children} | ||||
|       </div> | ||||
|       <div className='row justify-content-center'>{props.children}</div> | ||||
|  | ||||
|       <div className='Functions__search-container row justify-content-center'> | ||||
|         <select name='selectedCategory' value={inputSearch.selectedCategory} onChange={handleChange} className='Functions__select Functions__form-control'> | ||||
|         <select | ||||
|           name='selectedCategory' | ||||
|           value={inputSearch.selectedCategory} | ||||
|           onChange={handleChange} | ||||
|           className='Functions__select Functions__form-control' | ||||
|         > | ||||
|           <option value='0'>Toutes catégories</option> | ||||
|           {categories.map((category) => ( | ||||
|             <option key={category.id} value={category.id} className='Functions__select-option' style={{ backgroundColor: category.color }}>{category.name}</option> | ||||
|           {categories.map(category => ( | ||||
|             <option | ||||
|               key={category.id} | ||||
|               value={category.id} | ||||
|               className='Functions__select-option' | ||||
|               style={{ backgroundColor: category.color }} | ||||
|             > | ||||
|               {category.name} | ||||
|             </option> | ||||
|           ))} | ||||
|         </select> | ||||
|         <input value={inputSearch.search} onChange={handleChange} type='search' className='Functions__form-control Functions__search-input' name='search' id='search' placeholder='🔎 Rechercher...' /> | ||||
|         <input | ||||
|           value={inputSearch.search} | ||||
|           onChange={handleChange} | ||||
|           type='search' | ||||
|           className='Functions__form-control Functions__search-input' | ||||
|           name='search' | ||||
|           id='search' | ||||
|           placeholder='🔎 Rechercher...' | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|       <div className='row justify-content-center'> | ||||
|         {functionsData.rows.map((currentFunction, index) => { | ||||
|           // Si c'est le dernier élément | ||||
|           if (functionsData.rows.length === index + 1) { | ||||
|             return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} ref={lastFunctionCardRef} {...currentFunction} /> | ||||
|             return ( | ||||
|               <FunctionCard | ||||
|                 isAdmin={props.isAdmin} | ||||
|                 key={currentFunction.id} | ||||
|                 ref={lastFunctionCardRef} | ||||
|                 {...currentFunction} | ||||
|               /> | ||||
|             ) | ||||
|           } | ||||
|           return <FunctionCard isAdmin={props.isAdmin} key={currentFunction.id} {...currentFunction} /> | ||||
|           return ( | ||||
|             <FunctionCard | ||||
|               isAdmin={props.isAdmin} | ||||
|               key={currentFunction.id} | ||||
|               {...currentFunction} | ||||
|             /> | ||||
|           ) | ||||
|         })} | ||||
|       </div> | ||||
|       {isLoadingFunctions && <Loader />} | ||||
|   | ||||
| @@ -36,7 +36,8 @@ const HeadTag = ({ title, image, description }) => ( | ||||
|  | ||||
| HeadTag.defaultProps = { | ||||
|   title: 'FunctionProject', | ||||
|   description: "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", | ||||
|   description: | ||||
|     "Apprenez la programmation grâce à l'apprentissage par projet alias fonction.", | ||||
|   image: '/images/FunctionProject_icon_small.png' | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,161 +1,167 @@ | ||||
| /* HEADER */ | ||||
| .Header { | ||||
|     position: fixed; | ||||
|     width: 100%; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     z-index: 100; | ||||
|   position: fixed; | ||||
|   width: 100%; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   z-index: 100; | ||||
|  | ||||
|     display: flex; | ||||
|     flex-flow: row wrap; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|     padding: .5rem 1rem; | ||||
|   display: flex; | ||||
|   flex-flow: row wrap; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
|   padding: 0.5rem 1rem; | ||||
|  | ||||
|     border-bottom: var(--border-header-footer); | ||||
|     background-color: var(--background-color); | ||||
|   border-bottom: var(--border-header-footer); | ||||
|   background-color: var(--background-color); | ||||
| } | ||||
| @media (min-width: 992px) { | ||||
|     .Header { | ||||
|         flex-flow: row nowrap; | ||||
|         justify-content: flex-start; | ||||
|     } | ||||
|   .Header { | ||||
|     flex-flow: row nowrap; | ||||
|     justify-content: flex-start; | ||||
|   } | ||||
| } | ||||
| .Header > .container { | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
| } | ||||
| @media (min-width: 992px) { | ||||
|     .Header > .container { | ||||
|         flex-wrap: nowrap; | ||||
|     } | ||||
|   .Header > .container { | ||||
|     flex-wrap: nowrap; | ||||
|   } | ||||
| } | ||||
| /* Brand */ | ||||
| .Header__brand-link { | ||||
|     display: inline-block; | ||||
|     padding-top: .3125rem; | ||||
|     padding-bottom: .3125rem; | ||||
|     margin-right: 1rem; | ||||
|     font-size: 1.25rem; | ||||
|     line-height: inherit; | ||||
|     white-space: nowrap; | ||||
|   display: inline-block; | ||||
|   padding-top: 0.3125rem; | ||||
|   padding-bottom: 0.3125rem; | ||||
|   margin-right: 1rem; | ||||
|   font-size: 1.25rem; | ||||
|   line-height: inherit; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| #brand-link__logo-small-screen { | ||||
|     display: none; | ||||
|   display: none; | ||||
| } | ||||
| @media (max-width: 496px) { | ||||
|     #brand-link__logo { | ||||
|         display: none; | ||||
|     } | ||||
|     .Header__brand-link { | ||||
|         width: 30%; | ||||
|     } | ||||
|     #brand-link__logo-small-screen { | ||||
|         display: inline-block; | ||||
|     } | ||||
|   #brand-link__logo { | ||||
|     display: none; | ||||
|   } | ||||
|   .Header__brand-link { | ||||
|     width: 30%; | ||||
|   } | ||||
|   #brand-link__logo-small-screen { | ||||
|     display: inline-block; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (min-width: 992px) { | ||||
|     .Header .Header__navbar { | ||||
|         display: flex; | ||||
|         flex-basis: auto; | ||||
|     } | ||||
|   .Header .Header__navbar { | ||||
|     display: flex; | ||||
|     flex-basis: auto; | ||||
|   } | ||||
| } | ||||
| .Header__navbar { | ||||
|     flex-basis: 100%; | ||||
|     flex-grow: 1; | ||||
|     align-items: center; | ||||
|   flex-basis: 100%; | ||||
|   flex-grow: 1; | ||||
|   align-items: center; | ||||
| } | ||||
| .navbar__list { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     margin-left: auto; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   margin-left: auto; | ||||
| } | ||||
| .navbar__list.navbar__list-active { | ||||
|     margin: 0 !important; | ||||
|     display: flex; | ||||
|   margin: 0 !important; | ||||
|   display: flex; | ||||
| } | ||||
| @media (max-width: 992px) { | ||||
|     .navbar__list { | ||||
|         display: none; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|         padding-left: 0; | ||||
|         list-style: none; | ||||
|     } | ||||
|   .navbar__list { | ||||
|     display: none; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     padding-left: 0; | ||||
|     list-style: none; | ||||
|   } | ||||
| } | ||||
| .navbar-link { | ||||
|     display: block; | ||||
|     padding: .5rem 1rem; | ||||
|   display: block; | ||||
|   padding: 0.5rem 1rem; | ||||
| } | ||||
|  | ||||
| /* Details Styling */ | ||||
| .navbar-link:hover { | ||||
|     text-decoration: none; | ||||
|     color: rgba(255, 255, 255, .75); | ||||
|   text-decoration: none; | ||||
|   color: rgba(255, 255, 255, 0.75); | ||||
| } | ||||
| .navbar-link, .navbar-link-active { | ||||
|     color: rgba(255, 255, 255, .5); | ||||
| .navbar-link, | ||||
| .navbar-link-active { | ||||
|   color: rgba(255, 255, 255, 0.5); | ||||
| } | ||||
| .navbar-link-active, .navbar-link-active:hover, .Header__brand-link { | ||||
|     color: var(--text-color); | ||||
| .navbar-link-active, | ||||
| .navbar-link-active:hover, | ||||
| .Header__brand-link { | ||||
|   color: var(--text-color); | ||||
| } | ||||
| .navbar-item { | ||||
|     list-style: none; | ||||
|   list-style: none; | ||||
| } | ||||
| .navbar-link { | ||||
|     font-size: 16px; | ||||
|     padding: .5rem; | ||||
|   font-size: 16px; | ||||
|   padding: 0.5rem; | ||||
| } | ||||
|  | ||||
| /* Hamburger Icon */ | ||||
| .Header__hamburger { | ||||
|     display: none; | ||||
|     width: 56px; | ||||
|     height: 40px; | ||||
|     cursor: pointer; | ||||
|     background-color: transparent; | ||||
|     border: 1px solid rgba(255, 255, 255, .1); | ||||
|     border-radius: .25rem; | ||||
|     position: relative; | ||||
|   display: none; | ||||
|   width: 56px; | ||||
|   height: 40px; | ||||
|   cursor: pointer; | ||||
|   background-color: transparent; | ||||
|   border: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 0.25rem; | ||||
|   position: relative; | ||||
| } | ||||
| .Header__hamburger > span, .Header__hamburger > span::before, .Header__hamburger > span::after { | ||||
|     position: absolute; | ||||
|     width: 22px; | ||||
|     height: 1.3px; | ||||
|     background-color: rgba(255, 255, 255); | ||||
| .Header__hamburger > span, | ||||
| .Header__hamburger > span::before, | ||||
| .Header__hamburger > span::after { | ||||
|   position: absolute; | ||||
|   width: 22px; | ||||
|   height: 1.3px; | ||||
|   background-color: rgba(255, 255, 255); | ||||
| } | ||||
| .Header__hamburger > span { | ||||
|     top: 50%; | ||||
|     left: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     transition: background-color .3s ease-in-out; | ||||
|   top: 50%; | ||||
|   left: 50%; | ||||
|   transform: translate(-50%, -50%); | ||||
|   transition: background-color 0.3s ease-in-out; | ||||
| } | ||||
| .Header__hamburger > span::before, .Header__hamburger > span::after { | ||||
|     content: ''; | ||||
|     transition: transform .3s ease-in-out; | ||||
| .Header__hamburger > span::before, | ||||
| .Header__hamburger > span::after { | ||||
|   content: ''; | ||||
|   transition: transform 0.3s ease-in-out; | ||||
| } | ||||
| .Header__hamburger > span::before { | ||||
|     transform: translateY(-8px); | ||||
|   transform: translateY(-8px); | ||||
| } | ||||
| .Header__hamburger > span::after { | ||||
|     transform: translateY(8px); | ||||
|   transform: translateY(8px); | ||||
| } | ||||
| .Header__hamburger-active span { | ||||
|     background-color: transparent; | ||||
|   background-color: transparent; | ||||
| } | ||||
| .Header__hamburger-active > span::before { | ||||
|     transform: translateY(0px) rotateZ(45deg); | ||||
|   transform: translateY(0px) rotateZ(45deg); | ||||
| } | ||||
| .Header__hamburger-active > span::after { | ||||
|     transform: translateY(0px) rotateZ(-45deg); | ||||
|   transform: translateY(0px) rotateZ(-45deg); | ||||
| } | ||||
| /* Apparition du hamburger */ | ||||
| @media (max-width: 992px) { | ||||
|     .Header__hamburger { | ||||
|         display: flex; | ||||
|     } | ||||
| } | ||||
|   .Header__hamburger { | ||||
|     display: flex; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -17,56 +17,74 @@ export default function Header () { | ||||
|   return ( | ||||
|     <header className='Header'> | ||||
|       <div className='container'> | ||||
|  | ||||
|         {/* Brand */} | ||||
|         <Link href='/'> | ||||
|           <a className='Header__brand-link'> | ||||
|             <img id='brand-link__logo' src='/images/FunctionProject_brand-logo.png' alt='FunctionProject' /> | ||||
|             <img id='brand-link__logo-small-screen' src='/images/FunctionProject_icon_small.png' alt='FunctionProject' /> | ||||
|             <img | ||||
|               id='brand-link__logo' | ||||
|               src='/images/FunctionProject_brand-logo.png' | ||||
|               alt='FunctionProject' | ||||
|             /> | ||||
|             <img | ||||
|               id='brand-link__logo-small-screen' | ||||
|               src='/images/FunctionProject_icon_small.png' | ||||
|               alt='FunctionProject' | ||||
|             /> | ||||
|           </a> | ||||
|         </Link> | ||||
|  | ||||
|         {/* Hamburger icon on Mobile */} | ||||
|         <div onClick={toggleNavbar} className={`Header__hamburger ${(isActive) ? 'Header__hamburger-active' : ''}`}> | ||||
|         <div | ||||
|           onClick={toggleNavbar} | ||||
|           className={`Header__hamburger ${ | ||||
|             isActive ? 'Header__hamburger-active' : '' | ||||
|           }`} | ||||
|         > | ||||
|           <span /> | ||||
|         </div> | ||||
|  | ||||
|         {/* Navigation */} | ||||
|         <nav className='Header__navbar'> | ||||
|           <ul className={`navbar__list ${(isActive) ? 'navbar__list-active' : ''}`}> | ||||
|           <ul | ||||
|             className={`navbar__list ${isActive ? 'navbar__list-active' : ''}`} | ||||
|           > | ||||
|             <NavigationLink name='Accueil' path='/' /> | ||||
|             <NavigationLink name='Fonctions' path='/functions' /> | ||||
|             <NavigationLink name='Utilisateurs' path='/users' /> | ||||
|             { | ||||
|               (!isAuth) | ||||
|                 ? ( | ||||
|                   <> | ||||
|                     <NavigationLink name="S'inscrire" path='/users/register' /> | ||||
|                     <NavigationLink name='Connexion' path='/users/login' /> | ||||
|                   </> | ||||
|                 ) | ||||
|                 : ( | ||||
|                   <> | ||||
|                     <li className='navbar-item'> | ||||
|                       <Link href='/users/[name]' as={`/users/${user.name}`}> | ||||
|                         <a className={`navbar-link ${pathname === '/users/[name]' ? 'navbar-link-active' : null}`}>Mon Profil</a> | ||||
|                       </Link> | ||||
|                     </li> | ||||
|                     <li className='navbar-item'> | ||||
|                       <Link href='/'> | ||||
|                         <a onClick={logoutUser} className='navbar-link'>Se déconnecter</a> | ||||
|                       </Link> | ||||
|                     </li> | ||||
|                   </> | ||||
|                 ) | ||||
|             } | ||||
|             { | ||||
|               (isAuth && user.isAdmin) && | ||||
|                 <NavigationLink name='Admin' path='/admin' /> | ||||
|             } | ||||
|             {!isAuth ? ( | ||||
|               <> | ||||
|                 <NavigationLink name="S'inscrire" path='/users/register' /> | ||||
|                 <NavigationLink name='Connexion' path='/users/login' /> | ||||
|               </> | ||||
|             ) : ( | ||||
|               <> | ||||
|                 <li className='navbar-item'> | ||||
|                   <Link href='/users/[name]' as={`/users/${user.name}`}> | ||||
|                     <a | ||||
|                       className={`navbar-link ${ | ||||
|                         pathname === '/users/[name]' | ||||
|                           ? 'navbar-link-active' | ||||
|                           : null | ||||
|                       }`} | ||||
|                     > | ||||
|                       Mon Profil | ||||
|                     </a> | ||||
|                   </Link> | ||||
|                 </li> | ||||
|                 <li className='navbar-item'> | ||||
|                   <Link href='/'> | ||||
|                     <a onClick={logoutUser} className='navbar-link'> | ||||
|                       Se déconnecter | ||||
|                     </a> | ||||
|                   </Link> | ||||
|                 </li> | ||||
|               </> | ||||
|             )} | ||||
|             {isAuth && user.isAdmin && ( | ||||
|               <NavigationLink name='Admin' path='/admin' /> | ||||
|             )} | ||||
|           </ul> | ||||
|         </nav> | ||||
|  | ||||
|       </div> | ||||
|     </header> | ||||
|   ) | ||||
|   | ||||
| @@ -8,7 +8,11 @@ export default function NavigationLink (props) { | ||||
|   return ( | ||||
|     <li className='navbar-item'> | ||||
|       <Link href={props.path}> | ||||
|         <a className={`navbar-link ${pathname === props.path ? 'navbar-link-active' : null}`}> | ||||
|         <a | ||||
|           className={`navbar-link ${ | ||||
|             pathname === props.path ? 'navbar-link-active' : null | ||||
|           }`} | ||||
|         > | ||||
|           {props.name} | ||||
|         </a> | ||||
|       </Link> | ||||
|   | ||||
| @@ -1,7 +1,17 @@ | ||||
| const Loader = ({ width, height, speed }) => ( | ||||
|   <svg width={width} height={height} viewBox='0 0 100 100'> | ||||
|     <g transform='translate(50 50) rotate(0) scale(1 1) translate(-50 -50)'> | ||||
|       <image style={{ transformOrigin: '50% 50%', animation: `${speed} linear 0s infinite normal forwards running Loader__spin` }} x='0' y='0' width='100' height='100' href='/images/FunctionProject_icon.png' /> | ||||
|       <image | ||||
|         style={{ | ||||
|           transformOrigin: '50% 50%', | ||||
|           animation: `${speed} linear 0s infinite normal forwards running Loader__spin` | ||||
|         }} | ||||
|         x='0' | ||||
|         y='0' | ||||
|         width='100' | ||||
|         height='100' | ||||
|         href='/images/FunctionProject_icon.png' | ||||
|       /> | ||||
|     </g> | ||||
|   </svg> | ||||
| ) | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| const Modal = (props) => ( | ||||
| const Modal = props => ( | ||||
|   <div className='Modal container-fluid'> | ||||
|     <div className='Modal__content'> | ||||
|       {props.children} | ||||
|     </div> | ||||
|     <div className='Modal__content'>{props.children}</div> | ||||
|   </div> | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,9 @@ function UserContextProvider (props) { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isAuth) { | ||||
|       setMessageLogin('<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>') | ||||
|       setMessageLogin( | ||||
|         '<p class="form-error"><b>Erreur:</b> Vous devez être déconnecter avant de vous connecter.</p>' | ||||
|       ) | ||||
|     } else { | ||||
|       setMessageLogin('') | ||||
|     } | ||||
| @@ -43,10 +45,14 @@ function UserContextProvider (props) { | ||||
|       cookies.set('user', newUser, { path: '/', maxAge: newUser.expiresIn }) | ||||
|       setUser(newUser) | ||||
|       setIsAuth(true) | ||||
|       setMessageLogin('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>') | ||||
|       setMessageLogin( | ||||
|         '<p class="form-success"><b>Succès:</b> Connexion réussi!</p>' | ||||
|       ) | ||||
|       setLoginLoading(false) | ||||
|     } catch (error) { | ||||
|       setMessageLogin(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`) | ||||
|       setMessageLogin( | ||||
|         `<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>` | ||||
|       ) | ||||
|       setLoginLoading(false) | ||||
|       setIsAuth(false) | ||||
|       setUser(null) | ||||
| @@ -54,7 +60,17 @@ function UserContextProvider (props) { | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, setMessageLogin }}> | ||||
|     <UserContext.Provider | ||||
|       value={{ | ||||
|         user, | ||||
|         loginUser, | ||||
|         logoutUser, | ||||
|         loginLoading, | ||||
|         messageLogin, | ||||
|         isAuth, | ||||
|         setMessageLogin | ||||
|       }} | ||||
|     > | ||||
|       {props.children} | ||||
|     </UserContext.Provider> | ||||
|   ) | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { useContext } from 'react' | ||||
| import { UserContext } from '../contexts/UserContext' | ||||
| import redirect from '../utils/redirect' | ||||
|  | ||||
| const withoutAuth = (WrappedComponent) => { | ||||
|   const Component = (props) => { | ||||
| const withoutAuth = WrappedComponent => { | ||||
|   const Component = props => { | ||||
|     const { isAuth, user } = useContext(UserContext) | ||||
|  | ||||
|     if (isAuth) return redirect({}, `/users/${user.name}`) | ||||
|   | ||||
| @@ -14,11 +14,11 @@ function useAPI (url, defaultData = [], method = 'get', options = {}) { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     api[method](url, options) | ||||
|       .then((result) => { | ||||
|       .then(result => { | ||||
|         setData(result.data) | ||||
|         setIsLoading(false) | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|       .catch(error => { | ||||
|         setHasError(true) | ||||
|         console.error(error) | ||||
|       }) | ||||
|   | ||||
							
								
								
									
										550
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										550
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,236 +1,244 @@ | ||||
| /* GENERAL */ | ||||
| :root { | ||||
|     --border-header-footer: 3px rgba(255, 255, 255, .7) solid; | ||||
|     --background-color: #181818; | ||||
|     --text-color: rgb(222, 222, 222); | ||||
|     --important: #ffd800; | ||||
|   --border-header-footer: 3px rgba(255, 255, 255, 0.7) solid; | ||||
|   --background-color: #181818; | ||||
|   --text-color: rgb(222, 222, 222); | ||||
|   --important: #ffd800; | ||||
| } | ||||
| html { | ||||
|     line-height: initial; | ||||
|   line-height: initial; | ||||
| } | ||||
| body { | ||||
|     background-color: var(--background-color); | ||||
|   background-color: var(--background-color); | ||||
| } | ||||
| #__next { | ||||
|     min-height: 100vh; | ||||
|     display: flex; | ||||
|     flex-flow: column wrap; | ||||
|     background-color: var(--background-color); | ||||
|     color: var(--text-color); | ||||
|     font-family: 'Montserrat', sans-serif; | ||||
|     font-weight: 400; | ||||
|     font-size: 18px; | ||||
|     padding-top: 99px;  /* Height of the Header */  | ||||
|   min-height: 100vh; | ||||
|   display: flex; | ||||
|   flex-flow: column wrap; | ||||
|   background-color: var(--background-color); | ||||
|   color: var(--text-color); | ||||
|   font-family: 'Montserrat', sans-serif; | ||||
|   font-weight: 400; | ||||
|   font-size: 18px; | ||||
|   padding-top: 99px; /* Height of the Header */ | ||||
| } | ||||
| p { | ||||
|     font-size: 18px; | ||||
|     line-height: 1.9; | ||||
|   font-size: 18px; | ||||
|   line-height: 1.9; | ||||
| } | ||||
| a:hover { | ||||
|     text-decoration: underline; | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| /* CONTENT */ | ||||
| .content { | ||||
|     flex-grow: 1; | ||||
|     display: flex; | ||||
|   flex-grow: 1; | ||||
|   display: flex; | ||||
| } | ||||
| .centeredContainer { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| div[aria-hidden="true"] > * { | ||||
|     display: none; | ||||
| div[aria-hidden='true'] > * { | ||||
|   display: none; | ||||
| } | ||||
| .notyf__message { | ||||
|     font-family: sans-serif; | ||||
|   font-family: sans-serif; | ||||
| } | ||||
|  | ||||
| /* LOADING */ | ||||
| .isLoading { | ||||
|     display: none; | ||||
|   display: none; | ||||
| } | ||||
| #preloader { | ||||
|     min-height: 100vh; | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   min-height: 100vh; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| @keyframes Loader__spin { | ||||
|     0% { | ||||
|         animation-timing-function: cubic-bezier(0.5856, 0.0703, 0.4143, 0.9297); | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
|   0% { | ||||
|     animation-timing-function: cubic-bezier(0.5856, 0.0703, 0.4143, 0.9297); | ||||
|     transform: rotate(0deg); | ||||
|   } | ||||
|   100% { | ||||
|     transform: rotate(360deg); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* UTILITIES */ | ||||
| .text-center { | ||||
|     text-align: center; | ||||
|     word-break: break-word; | ||||
|   text-align: center; | ||||
|   word-break: break-word; | ||||
| } | ||||
| .justify-content-center { | ||||
|     justify-content: center; | ||||
|   justify-content: center; | ||||
| } | ||||
| .align-items-center { | ||||
|     align-items: center; | ||||
|   align-items: center; | ||||
| } | ||||
| .title-important { | ||||
|     color: var(--important); | ||||
|     font-weight: 500; | ||||
|   color: var(--important); | ||||
|   font-weight: 500; | ||||
| } | ||||
| a, .important { | ||||
|     color: var(--important); | ||||
|     text-decoration: none; | ||||
| a, | ||||
| .important { | ||||
|   color: var(--important); | ||||
|   text-decoration: none; | ||||
| } | ||||
| .d-none { | ||||
|     display: none !important; | ||||
|   display: none !important; | ||||
| } | ||||
| .form-group { | ||||
|     margin-top: 1.4rem; | ||||
|   margin-top: 1.4rem; | ||||
| } | ||||
| .form-control { | ||||
|     display: block; | ||||
|     width: 100%; | ||||
|     height: calc(1.5em + .75rem + 2px); | ||||
|     padding: .375rem .75rem; | ||||
|     font-size: 1rem; | ||||
|     font-weight: 400; | ||||
|     line-height: 1.5; | ||||
|     color: #495057; | ||||
|     background-color: #fff; | ||||
|     background-clip: padding-box; | ||||
|     border: 1px solid #ced4da; | ||||
|     border-radius: .25rem; | ||||
|   display: block; | ||||
|   width: 100%; | ||||
|   height: calc(1.5em + 0.75rem + 2px); | ||||
|   padding: 0.375rem 0.75rem; | ||||
|   font-size: 1rem; | ||||
|   font-weight: 400; | ||||
|   line-height: 1.5; | ||||
|   color: #495057; | ||||
|   background-color: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid #ced4da; | ||||
|   border-radius: 0.25rem; | ||||
| } | ||||
| .form-label { | ||||
|     display: inline-block; | ||||
|     margin-bottom: .5em; | ||||
|   display: inline-block; | ||||
|   margin-bottom: 0.5em; | ||||
| } | ||||
| .form-result { | ||||
|     margin: 30px; | ||||
|   margin: 30px; | ||||
| } | ||||
| .form-success { | ||||
|     color: #90EE90; | ||||
|   color: #90ee90; | ||||
| } | ||||
| .form-error { | ||||
|     color: #ff7f7f; | ||||
|   color: #ff7f7f; | ||||
| } | ||||
| .btn { | ||||
|     cursor: pointer; | ||||
|     border: 1px solid transparent; | ||||
|     padding: .375rem .75rem; | ||||
|     font-size: 1rem; | ||||
|     line-height: 1.5; | ||||
|     border-radius: .25rem; | ||||
|     transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; | ||||
|   cursor: pointer; | ||||
|   border: 1px solid transparent; | ||||
|   padding: 0.375rem 0.75rem; | ||||
|   font-size: 1rem; | ||||
|   line-height: 1.5; | ||||
|   border-radius: 0.25rem; | ||||
|   transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, | ||||
|     border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | ||||
| } | ||||
| .btn-lg { | ||||
|     padding: .5rem 1rem; | ||||
|     font-size: 1.25rem; | ||||
|     line-height: 1.5; | ||||
|     border-radius: .3rem; | ||||
|   padding: 0.5rem 1rem; | ||||
|   font-size: 1.25rem; | ||||
|   line-height: 1.5; | ||||
|   border-radius: 0.3rem; | ||||
| } | ||||
| .btn-dark:hover { | ||||
|     color: #fff; | ||||
|     background-color: #23272b; | ||||
|     border-color: #1d2124; | ||||
|   color: #fff; | ||||
|   background-color: #23272b; | ||||
|   border-color: #1d2124; | ||||
| } | ||||
| .btn-dark { | ||||
|     color: #fff; | ||||
|     background-color: #343a40; | ||||
|     border-color: #343a40; | ||||
|   color: #fff; | ||||
|   background-color: #343a40; | ||||
|   border-color: #343a40; | ||||
| } | ||||
| .btn-primary:hover { | ||||
|     color: #fff; | ||||
|     background-color: #0069d9; | ||||
|     border-color: #0062cc; | ||||
|   color: #fff; | ||||
|   background-color: #0069d9; | ||||
|   border-color: #0062cc; | ||||
| } | ||||
| .btn-primary { | ||||
|     color: #fff; | ||||
|     background-color: #007bff; | ||||
|     border-color: #007bff; | ||||
|   color: #fff; | ||||
|   background-color: #007bff; | ||||
|   border-color: #007bff; | ||||
| } | ||||
| .custom-control { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
| } | ||||
| .custom-control-input { | ||||
|     position: absolute; | ||||
|     z-index: -1; | ||||
|     opacity: 0; | ||||
|   position: absolute; | ||||
|   z-index: -1; | ||||
|   opacity: 0; | ||||
| } | ||||
| .custom-control-label { | ||||
|     position: relative; | ||||
|     margin-bottom: 0; | ||||
|     vertical-align: top; | ||||
|   position: relative; | ||||
|   margin-bottom: 0; | ||||
|   vertical-align: top; | ||||
| } | ||||
| .custom-control-input:checked~.custom-control-label::before { | ||||
|     color: #fff; | ||||
|     border-color: #007bff; | ||||
|     background-color: #007bff; | ||||
| .custom-control-input:checked ~ .custom-control-label::before { | ||||
|   color: #fff; | ||||
|   border-color: #007bff; | ||||
|   background-color: #007bff; | ||||
| } | ||||
| .custom-switch .custom-control-label::before { | ||||
|     left: -2.25rem; | ||||
|     width: 1.75rem; | ||||
|     pointer-events: all; | ||||
|     border-radius: .5rem; | ||||
|   left: -2.25rem; | ||||
|   width: 1.75rem; | ||||
|   pointer-events: all; | ||||
|   border-radius: 0.5rem; | ||||
| } | ||||
| .custom-control-label::before { | ||||
|     position: absolute; | ||||
|     top: .25rem; | ||||
|     left: -1.5rem; | ||||
|     display: block; | ||||
|     width: 1rem; | ||||
|     height: 1rem; | ||||
|     pointer-events: none; | ||||
|     content: ""; | ||||
|     background-color: #fff; | ||||
|     border: #adb5bd solid 1px; | ||||
|   position: absolute; | ||||
|   top: 0.25rem; | ||||
|   left: -1.5rem; | ||||
|   display: block; | ||||
|   width: 1rem; | ||||
|   height: 1rem; | ||||
|   pointer-events: none; | ||||
|   content: ''; | ||||
|   background-color: #fff; | ||||
|   border: #adb5bd solid 1px; | ||||
| } | ||||
| .custom-switch .custom-control-input:checked~.custom-control-label::after { | ||||
|     background-color: #fff; | ||||
|     -webkit-transform: translateX(.75rem); | ||||
|     transform: translateX(.75rem); | ||||
| .custom-switch .custom-control-input:checked ~ .custom-control-label::after { | ||||
|   background-color: #fff; | ||||
|   -webkit-transform: translateX(0.75rem); | ||||
|   transform: translateX(0.75rem); | ||||
| } | ||||
| .custom-switch .custom-control-label::after { | ||||
|     top: calc(.25rem + 2px); | ||||
|     left: calc(-2.25rem + 2px); | ||||
|     width: calc(1rem - 4px); | ||||
|     height: calc(1rem - 4px); | ||||
|     background-color: #adb5bd; | ||||
|     border-radius: .5rem; | ||||
|     transition: background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out; | ||||
|     transition: transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; | ||||
|     transition: transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out; | ||||
|   top: calc(0.25rem + 2px); | ||||
|   left: calc(-2.25rem + 2px); | ||||
|   width: calc(1rem - 4px); | ||||
|   height: calc(1rem - 4px); | ||||
|   background-color: #adb5bd; | ||||
|   border-radius: 0.5rem; | ||||
|   transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, | ||||
|     box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; | ||||
|   transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, | ||||
|     border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | ||||
|   transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, | ||||
|     border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, | ||||
|     -webkit-transform 0.15s ease-in-out; | ||||
| } | ||||
| .custom-control-label::after { | ||||
|     position: absolute; | ||||
|     top: .25rem; | ||||
|     left: -1.5rem; | ||||
|     display: block; | ||||
|     width: 1rem; | ||||
|     height: 1rem; | ||||
|     content: ""; | ||||
|     background: no-repeat 50%/50% 50%; | ||||
|   position: absolute; | ||||
|   top: 0.25rem; | ||||
|   left: -1.5rem; | ||||
|   display: block; | ||||
|   width: 1rem; | ||||
|   height: 1rem; | ||||
|   content: ''; | ||||
|   background: no-repeat 50%/50% 50%; | ||||
| } | ||||
| .table-column { | ||||
|     display: grid; | ||||
|   display: grid; | ||||
| } | ||||
| .table, th, td { | ||||
|     border: 1px solid var(--text-color); | ||||
|     border-collapse: collapse; | ||||
| .table, | ||||
| th, | ||||
| td { | ||||
|   border: 1px solid var(--text-color); | ||||
|   border-collapse: collapse; | ||||
| } | ||||
| .table-row { | ||||
|     padding: 15px; | ||||
|   padding: 15px; | ||||
| } | ||||
| .vscode-editor { | ||||
|     padding: 15px; | ||||
|     border-radius: 10px; | ||||
|     overflow: auto; | ||||
|     white-space: normal !important; | ||||
| } | ||||
|   padding: 15px; | ||||
|   border-radius: 10px; | ||||
|   overflow: auto; | ||||
|   white-space: normal !important; | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										36
									
								
								website/public/css/normalize.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								website/public/css/normalize.css
									
									
									
									
										vendored
									
									
								
							| @@ -174,7 +174,8 @@ textarea { | ||||
|  */ | ||||
|  | ||||
| button, | ||||
| input { /* 1 */ | ||||
| input { | ||||
|   /* 1 */ | ||||
|   overflow: visible; | ||||
| } | ||||
|  | ||||
| @@ -184,7 +185,8 @@ input { /* 1 */ | ||||
|  */ | ||||
|  | ||||
| button, | ||||
| select { /* 1 */ | ||||
| select { | ||||
|   /* 1 */ | ||||
|   text-transform: none; | ||||
| } | ||||
|  | ||||
| @@ -193,9 +195,9 @@ select { /* 1 */ | ||||
|  */ | ||||
|  | ||||
| button, | ||||
| [type="button"], | ||||
| [type="reset"], | ||||
| [type="submit"] { | ||||
| [type='button'], | ||||
| [type='reset'], | ||||
| [type='submit'] { | ||||
|   -webkit-appearance: button; | ||||
| } | ||||
|  | ||||
| @@ -204,9 +206,9 @@ button, | ||||
|  */ | ||||
|  | ||||
| button::-moz-focus-inner, | ||||
| [type="button"]::-moz-focus-inner, | ||||
| [type="reset"]::-moz-focus-inner, | ||||
| [type="submit"]::-moz-focus-inner { | ||||
| [type='button']::-moz-focus-inner, | ||||
| [type='reset']::-moz-focus-inner, | ||||
| [type='submit']::-moz-focus-inner { | ||||
|   border-style: none; | ||||
|   padding: 0; | ||||
| } | ||||
| @@ -216,9 +218,9 @@ button::-moz-focus-inner, | ||||
|  */ | ||||
|  | ||||
| button:-moz-focusring, | ||||
| [type="button"]:-moz-focusring, | ||||
| [type="reset"]:-moz-focusring, | ||||
| [type="submit"]:-moz-focusring { | ||||
| [type='button']:-moz-focusring, | ||||
| [type='reset']:-moz-focusring, | ||||
| [type='submit']:-moz-focusring { | ||||
|   outline: 1px dotted ButtonText; | ||||
| } | ||||
|  | ||||
| @@ -267,8 +269,8 @@ textarea { | ||||
|  * 2. Remove the padding in IE 10. | ||||
|  */ | ||||
|  | ||||
| [type="checkbox"], | ||||
| [type="radio"] { | ||||
| [type='checkbox'], | ||||
| [type='radio'] { | ||||
|   box-sizing: border-box; /* 1 */ | ||||
|   padding: 0; /* 2 */ | ||||
| } | ||||
| @@ -277,8 +279,8 @@ textarea { | ||||
|  * Correct the cursor style of increment and decrement buttons in Chrome. | ||||
|  */ | ||||
|  | ||||
| [type="number"]::-webkit-inner-spin-button, | ||||
| [type="number"]::-webkit-outer-spin-button { | ||||
| [type='number']::-webkit-inner-spin-button, | ||||
| [type='number']::-webkit-outer-spin-button { | ||||
|   height: auto; | ||||
| } | ||||
|  | ||||
| @@ -287,7 +289,7 @@ textarea { | ||||
|  * 2. Correct the outline style in Safari. | ||||
|  */ | ||||
|  | ||||
| [type="search"] { | ||||
| [type='search'] { | ||||
|   -webkit-appearance: textfield; /* 1 */ | ||||
|   outline-offset: -2px; /* 2 */ | ||||
| } | ||||
| @@ -296,7 +298,7 @@ textarea { | ||||
|  * Remove the inner padding in Chrome and Safari on macOS. | ||||
|  */ | ||||
|  | ||||
| [type="search"]::-webkit-search-decoration { | ||||
| [type='search']::-webkit-search-decoration { | ||||
|   -webkit-appearance: none; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,81 +1,81 @@ | ||||
| /* Make clicks pass-through */ | ||||
| #nprogress { | ||||
|     pointer-events: none; | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| #nprogress .bar { | ||||
|     background: var(--important); | ||||
|   background: var(--important); | ||||
|  | ||||
|     position: fixed; | ||||
|     z-index: 1031; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|   position: fixed; | ||||
|   z-index: 1031; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|  | ||||
|     width: 100%; | ||||
|     height: 2px; | ||||
|   width: 100%; | ||||
|   height: 2px; | ||||
| } | ||||
|  | ||||
| /* Fancy blur effect */ | ||||
| #nprogress .peg { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     right: 0px; | ||||
|     width: 100px; | ||||
|     height: 100%; | ||||
|     box-shadow: 0 0 10px var(--important), 0 0 5px var(--important); | ||||
|     opacity: 1; | ||||
|   display: block; | ||||
|   position: absolute; | ||||
|   right: 0px; | ||||
|   width: 100px; | ||||
|   height: 100%; | ||||
|   box-shadow: 0 0 10px var(--important), 0 0 5px var(--important); | ||||
|   opacity: 1; | ||||
|  | ||||
|     -webkit-transform: rotate(3deg) translate(0px, -4px); | ||||
|     -ms-transform: rotate(3deg) translate(0px, -4px); | ||||
|     transform: rotate(3deg) translate(0px, -4px); | ||||
|   -webkit-transform: rotate(3deg) translate(0px, -4px); | ||||
|   -ms-transform: rotate(3deg) translate(0px, -4px); | ||||
|   transform: rotate(3deg) translate(0px, -4px); | ||||
| } | ||||
|  | ||||
| /* Remove these to get rid of the spinner */ | ||||
| #nprogress .spinner { | ||||
|     display: block; | ||||
|     position: fixed; | ||||
|     z-index: 1031; | ||||
|     top: 15px; | ||||
|     right: 15px; | ||||
|   display: block; | ||||
|   position: fixed; | ||||
|   z-index: 1031; | ||||
|   top: 15px; | ||||
|   right: 15px; | ||||
| } | ||||
|  | ||||
| #nprogress .spinner-icon { | ||||
|     width: 18px; | ||||
|     height: 18px; | ||||
|     box-sizing: border-box; | ||||
|   width: 18px; | ||||
|   height: 18px; | ||||
|   box-sizing: border-box; | ||||
|  | ||||
|     border: solid 2px transparent; | ||||
|     border-top-color: var(--important); | ||||
|     border-left-color: var(--important); | ||||
|     border-radius: 50%; | ||||
|   border: solid 2px transparent; | ||||
|   border-top-color: var(--important); | ||||
|   border-left-color: var(--important); | ||||
|   border-radius: 50%; | ||||
|  | ||||
|     -webkit-animation: nprogress-spinner 400ms linear infinite; | ||||
|     animation: nprogress-spinner 400ms linear infinite; | ||||
|   -webkit-animation: nprogress-spinner 400ms linear infinite; | ||||
|   animation: nprogress-spinner 400ms linear infinite; | ||||
| } | ||||
|  | ||||
| .nprogress-custom-parent { | ||||
|     overflow: hidden; | ||||
|     position: relative; | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .nprogress-custom-parent #nprogress .spinner, | ||||
| .nprogress-custom-parent #nprogress .bar { | ||||
|     position: absolute; | ||||
|   position: absolute; | ||||
| } | ||||
|  | ||||
| @-webkit-keyframes nprogress-spinner { | ||||
|     0% { | ||||
|         -webkit-transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         -webkit-transform: rotate(360deg); | ||||
|     } | ||||
|   0% { | ||||
|     -webkit-transform: rotate(0deg); | ||||
|   } | ||||
|   100% { | ||||
|     -webkit-transform: rotate(360deg); | ||||
|   } | ||||
| } | ||||
| @keyframes nprogress-spinner { | ||||
|     0% { | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
|   0% { | ||||
|     transform: rotate(0deg); | ||||
|   } | ||||
|   100% { | ||||
|     transform: rotate(360deg); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| .Error404__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     min-width: 100%; | ||||
| } | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   min-width: 100%; | ||||
| } | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| .FunctionComponent__top { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     position: relative; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin-top: 40px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin-top: 40px; | ||||
| } | ||||
| .FunctionComponent__title { | ||||
|     margin: 0; | ||||
|   margin: 0; | ||||
| } | ||||
| .FunctionComponent__description { | ||||
|     word-break: break-all; | ||||
|     margin-bottom: 0; | ||||
|   word-break: break-all; | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| .FunctionComponent__slide { | ||||
|     margin-top: 30px; | ||||
|   margin-top: 30px; | ||||
| } | ||||
| .FunctionComponent__image { | ||||
|     width: 150px; | ||||
|   width: 150px; | ||||
| } | ||||
| .FunctionComponent__star-favorite { | ||||
|     color: var(--important); | ||||
|     width: 2em !important; | ||||
|     height: 2em !important; | ||||
|     position: absolute; | ||||
|     right: 0; | ||||
|     top: 15px; | ||||
|     margin-right: 15px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|   color: var(--important); | ||||
|   width: 2em !important; | ||||
|   height: 2em !important; | ||||
|   position: absolute; | ||||
|   right: 0; | ||||
|   top: 15px; | ||||
|   margin-right: 15px; | ||||
|   cursor: pointer; | ||||
| } | ||||
|   | ||||
| @@ -1,35 +1,35 @@ | ||||
| .Admin__Modal__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     margin: 30px 0 0 0; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   margin: 30px 0 0 0; | ||||
| } | ||||
| .Admin__Modal__row { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin-bottom: 50px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin-bottom: 50px; | ||||
| } | ||||
| .Admin__Modal-top-container { | ||||
|     margin: 20px 0; | ||||
|   margin: 20px 0; | ||||
| } | ||||
| .Admin__Modal-select-option { | ||||
|     color: rgb(221, 220, 220); | ||||
|   color: rgb(221, 220, 220); | ||||
| } | ||||
| .Admin__Function-slide { | ||||
|     margin-top: 40px; | ||||
|   margin-top: 40px; | ||||
| } | ||||
| .Admin__Input-group { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin-top: 40px; | ||||
|     padding: 40px; | ||||
| } | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin-top: 40px; | ||||
|   padding: 40px; | ||||
| } | ||||
|   | ||||
| @@ -1,38 +1,38 @@ | ||||
| .Chronometer__container { | ||||
|     display: flex; | ||||
|     flex-flow: row wrap; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     padding: 20px; | ||||
|     margin-bottom: 40px; | ||||
|   display: flex; | ||||
|   flex-flow: row wrap; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   padding: 20px; | ||||
|   margin-bottom: 40px; | ||||
| } | ||||
| .Chronometer__item { | ||||
|     width: 100%; | ||||
|   width: 100%; | ||||
| } | ||||
| .Chronomter__row { | ||||
|     margin: 8px 0 16px 0; | ||||
|     text-align: center; | ||||
|   margin: 8px 0 16px 0; | ||||
|   text-align: center; | ||||
| } | ||||
| .Chronometer__time-left { | ||||
|     font-size: 3rem; | ||||
|     font-weight: 700; | ||||
|   font-size: 3rem; | ||||
|   font-weight: 700; | ||||
| } | ||||
| .Chronometer__buttons { | ||||
|     border-top: solid 3px var(--important); | ||||
|     padding-top: 12px; | ||||
|   border-top: solid 3px var(--important); | ||||
|   padding-top: 12px; | ||||
| } | ||||
| .Chronometer__row-button { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
| } | ||||
| .Chronometer-btn { | ||||
|     color: var(--text-color); | ||||
|     cursor: pointer; | ||||
|     background-color: transparent; | ||||
|     border: none; | ||||
|     outline: none; | ||||
|     margin-left: 14px; | ||||
|     margin-right: 14px; | ||||
|     width: 2em; | ||||
| } | ||||
|   color: var(--text-color); | ||||
|   cursor: pointer; | ||||
|   background-color: transparent; | ||||
|   border: none; | ||||
|   outline: none; | ||||
|   margin-left: 14px; | ||||
|   margin-right: 14px; | ||||
|   width: 2em; | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| .Product__image { | ||||
|     max-width: 150px; | ||||
|   max-width: 150px; | ||||
| } | ||||
| .Price__result { | ||||
|     height: 50px; | ||||
|     margin: 5px 0 10px 0; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding-left: 10px; | ||||
|     color: #fff; | ||||
|   height: 50px; | ||||
|   margin: 5px 0 10px 0; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding-left: 10px; | ||||
|   color: #fff; | ||||
| } | ||||
| .Price__result-success { | ||||
|     background-color: #28a745; | ||||
|   background-color: #28a745; | ||||
| } | ||||
| .Price__result-plus { | ||||
|     background-color: #dc3545; | ||||
|   background-color: #dc3545; | ||||
| } | ||||
| .Price__result-moins { | ||||
|     background-color: #343a40;    | ||||
| } | ||||
|   background-color: #343a40; | ||||
| } | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| .ManageToDo__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin: 40px 40px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin: 40px 40px; | ||||
| } | ||||
| .ManageToDo__list { | ||||
|     overflow: hidden; | ||||
|   overflow: hidden; | ||||
| } | ||||
| .ManageToDo__list-item { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     margin: 25px 0; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   margin: 25px 0; | ||||
| } | ||||
| .ManageToDo__list-item.isCompleted { | ||||
|     text-decoration: line-through; | ||||
|   text-decoration: line-through; | ||||
| } | ||||
| .ManageToDo__task-btn { | ||||
|     color: var(--text-color); | ||||
|     cursor: pointer; | ||||
|     background-color: transparent; | ||||
|     border: none; | ||||
|     outline: none; | ||||
|     margin-left: 7px; | ||||
|     margin-right: 7px; | ||||
|     width: 1.75em; | ||||
|   color: var(--text-color); | ||||
|   cursor: pointer; | ||||
|   background-color: transparent; | ||||
|   border: none; | ||||
|   outline: none; | ||||
|   margin-left: 7px; | ||||
|   margin-right: 7px; | ||||
|   width: 1.75em; | ||||
| } | ||||
| .ManageToDo__list-item-span { | ||||
|     width: calc(100% - 120px); | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
|   width: calc(100% - 120px); | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| .Home__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| .Home__logo-spin { | ||||
|     cursor: pointer; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .Home__image-width { | ||||
|     width: 13em; | ||||
|   width: 13em; | ||||
| } | ||||
| div[aria-hidden='false'] { | ||||
|   overflow: hidden !important; | ||||
| } | ||||
| div[aria-hidden="false"] { | ||||
|     overflow: hidden !important; | ||||
| } | ||||
| @@ -1,28 +1,28 @@ | ||||
| .Profile__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     margin: 30px 0 0 0; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   margin: 30px 0 0 0; | ||||
| } | ||||
| .Profile__row { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin-bottom: 50px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin-bottom: 50px; | ||||
| } | ||||
| .Profile__logo { | ||||
|     border-radius: 50%; | ||||
|     object-fit: cover; | ||||
|     width: 150px; | ||||
|     height: 150px; | ||||
|   border-radius: 50%; | ||||
|   object-fit: cover; | ||||
|   width: 150px; | ||||
|   height: 150px; | ||||
| } | ||||
| .Profile__comment { | ||||
|     margin: 0 0 50px 0; | ||||
|   margin: 0 0 50px 0; | ||||
| } | ||||
| .Profile__Modal-top-container { | ||||
|     margin: 20px 0; | ||||
| } | ||||
|   margin: 20px 0; | ||||
| } | ||||
|   | ||||
| @@ -1,22 +1,22 @@ | ||||
| .Register-Login__container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
| } | ||||
| .Register-Login__row { | ||||
|     padding: 30px; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|   padding: 30px; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
| } | ||||
| .Register-Login__title { | ||||
|     text-align: center; | ||||
|   text-align: center; | ||||
| } | ||||
| .Register-Login__Forgot-password { | ||||
|     color: var(--text-color); | ||||
|     font-size: 16px; | ||||
|   color: var(--text-color); | ||||
|   font-size: 16px; | ||||
| } | ||||
| .Register-Login__Forgot-password:hover { | ||||
|     color: var(--important); | ||||
|     text-decoration: none; | ||||
| } | ||||
|   color: var(--important); | ||||
|   text-decoration: none; | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ app.prepare().then(() => { | ||||
|   }) | ||||
|  | ||||
|   /* Server */ | ||||
|   server.listen(PORT, (error) => { | ||||
|   server.listen(PORT, error => { | ||||
|     if (error) throw error | ||||
|     console.log(`> Ready on http://localhost:${PORT}`) | ||||
|   }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user