diff --git a/api/assets/utils/deleteFilesNameStartWith.js b/api/assets/utils/deleteFilesNameStartWith.js index b2940d6..fef91e0 100644 --- a/api/assets/utils/deleteFilesNameStartWith.js +++ b/api/assets/utils/deleteFilesNameStartWith.js @@ -9,7 +9,7 @@ function deleteFilesNameStartWith(pattern, dirPath = __dirname) { // Iterate through the found file names for (const name of fileNames) { // If file name matches the pattern - if (name.startsWith(pattern)) { + if (name.startsWith(pattern) && name !== 'default.png') { console.log(name) fs.unlinkSync(path.join(dirPath, name)); } diff --git a/api/controllers/users.js b/api/controllers/users.js index 04bba56..de0fd7f 100644 --- a/api/controllers/users.js +++ b/api/controllers/users.js @@ -19,14 +19,27 @@ const deleteFilesNameStartWith = require('../assets/utils/deleteFilesNameS async function handleEditUser(res, { name, email, biography, isPublicEmail }, userId, logoName) { const user = await Users.findOne({ where: { id: userId } }); user.name = name; - user.email = email; - user.biography = biography; + if (user.email !== email) { + const tempToken = uuid.v4(); + user.email = email; + user.isConfirmed = false; + user.tempToken = tempToken; + await transporter.sendMail({ + from: `"FunctionProject" <${EMAIL_INFO.auth.user}>`, + to: email, + subject: "FunctionProject - Confirmer l'email", + html: emailTemplate("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 != undefined) { + user.biography = biography; + } user.isPublicEmail = isPublicEmail; if (logoName != undefined) { user.logo = `/images/users/${logoName}`; } await user.save(); - return res.status(200).json({ message: "Le profil a bien été modifié!" }); + 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.putUser = (req, res, next) => { @@ -115,7 +128,7 @@ exports.login = async (req, res, next) => { } const token = jwt.sign({ email: user.email, userId: user.id - }, JWT_SECRET, { expiresIn: '3h' }); + }, JWT_SECRET, { expiresIn: '6h' }); 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 }); } catch (error) { console.log(error); @@ -198,7 +211,7 @@ exports.getUserInfo = async (req, res, next) => { const { name } = req.params; try { const user = await Users.findOne({ - where: { name }, + where: { name, isConfirmed: true }, attributes: { exclude: ["updatedAt", "isAdmin", "isConfirmed", "password", "tempToken", "tempExpirationToken"] }, @@ -232,6 +245,7 @@ exports.getUserInfo = async (req, res, next) => { const userObject = { // Si Public Email ... (user.isPublicEmail) && { email: user.email }, + isPublicEmail: user.isPublicEmail, name: user.name, biography: user.biography, logo: user.logo, diff --git a/website/components/Modal.js b/website/components/Modal.js new file mode 100644 index 0000000..af29ab5 --- /dev/null +++ b/website/components/Modal.js @@ -0,0 +1,9 @@ +const Modal = (props) => ( +
+
+ {props.children} +
+
+); + +export default Modal; \ No newline at end of file diff --git a/website/contexts/UserContext.js b/website/contexts/UserContext.js index c28a279..36f0cb2 100644 --- a/website/contexts/UserContext.js +++ b/website/contexts/UserContext.js @@ -26,8 +26,7 @@ function UserContextProvider(props) { api.post('/users/login', { email, password }) .then((response) => { const user = response.data; - cookies.set('user', user); - setUser(user); + changeUserValue(user); setIsAuth(true); setMessageLogin('

Succès: Connexion réussi!

'); setLoginLoading(false); @@ -46,8 +45,14 @@ function UserContextProvider(props) { setIsAuth(false); } + const changeUserValue = (user) => { + cookies.remove('user'); + cookies.set('user', user); + setUser(user); + } + return ( - + {props.children} ); diff --git a/website/package-lock.json b/website/package-lock.json index dfb0cec..c172428 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1395,6 +1395,35 @@ "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz", + "integrity": "sha512-gtis2/5yLdfI6n0ia0jH7NJs5i/Z/8M/ZbQL6jXQhCthEOe5Cr5NcQPhgTvFxNOtURE03/ZqUcEskdn2M+QaBg==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.28", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.28.tgz", + "integrity": "sha512-4LeaNHWvrneoU0i8b5RTOJHKx7E+y7jYejplR7uSVB34+mp3Veg7cbKk7NBCLiI4TyoWS1wh9ZdoyLJR8wSAdg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.28" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz", + "integrity": "sha512-IHUgDJdomv6YtG4p3zl1B5wWf9ffinHIvebqQOmV3U+3SLw4fC+LUCCgwfETkbTtjy5/Qws2VoVf6z/ETQpFpg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.28" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.9.tgz", + "integrity": "sha512-49V3WNysLZU5fZ3sqSuys4nGRytsrxJktbv3vuaXkEoxv22C6T7TEG0TW6+nqVjMnkfCQd5xOnmJoZHMF78tOw==", + "requires": { + "prop-types": "^15.7.2" + } + }, "@next/polyfill-nomodule": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/@next/polyfill-nomodule/-/polyfill-nomodule-9.3.2.tgz", diff --git a/website/package.json b/website/package.json index abd9a3e..4e36188 100644 --- a/website/package.json +++ b/website/package.json @@ -8,6 +8,9 @@ "start": "cross-env NODE_ENV=production node server" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.28", + "@fortawesome/free-solid-svg-icons": "^5.13.0", + "@fortawesome/react-fontawesome": "^0.1.9", "@zeit/next-css": "^1.0.1", "axios": "^0.19.2", "date-and-time": "^0.13.1", diff --git a/website/pages/login.js b/website/pages/login.js index bb6060f..e51523d 100644 --- a/website/pages/login.js +++ b/website/pages/login.js @@ -13,7 +13,7 @@ const Login = () => { const [inputState, setInputState] = useState({}); const { loginUser, messageLogin, loginLoading } = useContext(UserContext); - const handleChange = () => { + const handleChange = (event) => { const inputStateNew = { ...inputState }; inputStateNew[event.target.name] = event.target.value; setInputState(inputStateNew); @@ -55,7 +55,8 @@ const Login = () => {
- {(router.query.isConfirmed !== undefined) &&

Succès: Votre compte a bien été confirmé, vous pouvez maintenant vous connectez!

} + {(router.query.isConfirmed !== undefined && messageLogin === "") &&

Succès: Votre compte a bien été confirmé, vous pouvez maintenant vous connectez!

} + {(router.query.isSuccessEdit !== undefined && messageLogin === "") &&

Succès: Votre profil a bien été modifié, vous pouvez maintenant vous connectez!

} { (loginLoading) ? diff --git a/website/pages/profile/[name].js b/website/pages/profile/[name].js index 07a41e7..0c74fd5 100644 --- a/website/pages/profile/[name].js +++ b/website/pages/profile/[name].js @@ -1,9 +1,15 @@ import Link from 'next/link'; -import { Fragment } from 'react'; +import { Fragment, useContext, useState } from 'react'; +import { UserContext } from '../../contexts/UserContext'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faPen, faTimes } from '@fortawesome/free-solid-svg-icons'; import date from 'date-and-time'; import HeadTag from '../../components/HeadTag'; import FunctionCard from '../../components/FunctionCard/FunctionCard'; +import Modal from '../../components/Modal'; import redirect from '../../utils/redirect'; +import htmlParser from 'html-react-parser'; +import Loader from '../../components/Loader'; import api from '../../utils/api'; import { API_URL } from '../../utils/config'; import '../../public/css/pages/profile.css'; @@ -14,25 +20,142 @@ const Profile = (props) => { const createdAt = new Date(props.createdAt); const publicationDate = `${('0'+createdAt.getDate()).slice(-2)}/${('0'+(createdAt.getMonth()+1)).slice(-2)}/${createdAt.getFullYear()}`; + const { isAuth, user, logoutUser } = useContext(UserContext); + const [isOpen, setIsOpen] = useState(false); + + let defaultInputState = {}; + if (isAuth) { + defaultInputState = { name: user.name, email: user.email, biography: user.biography, isPublicEmail: user.isPublicEmail }; + } + const [inputState, setInputState] = useState(defaultInputState); + + const [message, setMessage] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const toggleModal = () => setIsOpen(!isOpen); + + const handleChange = (event, isTypeCheck = false) => { + const inputStateNew = { ...inputState }; + inputStateNew[event.target.name] = (event.target.files != undefined) ? event.target.files[0] : (isTypeCheck) ? event.target.checked : event.target.value; + setInputState(inputStateNew); + } + + const handleSubmit = (event) => { + event.preventDefault(); + const token = user.token; + if (isAuth && token != undefined) { + setIsLoading(true); + const formData = new FormData(); + formData.append('name', inputState.name); + formData.append('email', inputState.email); + formData.append('biography', inputState.biography); + formData.append('isPublicEmail', inputState.isPublicEmail); + formData.append('logo', inputState.logo); + + api.put('/users/', formData, { headers: { 'Authorization': user.token } }) + .then(() => { + setIsLoading(false); + logoutUser(); + redirect({}, '/login?isSuccessEdit=true'); + }) + .catch((error) => { + setMessage(`

Erreur: ${error.response.data.message}

`); + setIsLoading(false); + }); + } + } + return ( -
+ {/* Édition du profil */} + {(isOpen) && + +
+
+
+
+
+ + + +

Éditer le profil

+

(Vous devrez vous reconnecter après la sauvegarde)
Si vous changez votre adresse email, vous devrez la confirmer (vérifier vos emails).

+
+
+
+ +
+
+
+ + +
+ +
+ + +
+ +
+ handleChange(event, true)} type="checkbox" name="isPublicEmail" checked={inputState.isPublicEmail} className="custom-control-input" id="isPublicEmail" /> + +
+ +
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ { + (isLoading) ? + + : + htmlParser(message) + } +
+
+
+
+
+ } + +

Profil de {props.name}

+
{props.name}
+
{(props.biography != undefined) &&

{props.biography}

} {(props.email != undefined) &&

Email : {props.email}

}

Date de création : {publicationDate}

+ + {(isAuth && user.name === props.name) && + + }
@@ -59,13 +182,13 @@ const Profile = (props) => {

Derniers commentaires :

-
+
{props.commentsArray.map((comment) => (

Posté sur la fonction  - + {comment.function.title}  le {date.format(new Date(comment.createdAt), 'DD/MM/YYYY à HH:mm', true)} diff --git a/website/pages/register.js b/website/pages/register.js index 70de711..e52127f 100644 --- a/website/pages/register.js +++ b/website/pages/register.js @@ -11,7 +11,7 @@ const Register = () => { const [message, setMessage] = useState(""); const [isLoading, setIsLoading] = useState(false); - const handleChange = () => { + const handleChange = (event) => { const inputStateNew = { ...inputState }; inputStateNew[event.target.name] = event.target.value; setInputState(inputStateNew); diff --git a/website/public/css/pages/profile.css b/website/public/css/pages/profile.css index 78ecd3a..9d205aa 100644 --- a/website/public/css/pages/profile.css +++ b/website/public/css/pages/profile.css @@ -22,4 +22,70 @@ } .Profile__comment { margin: 0 0 50px 0; +} +.Profile__Modal-top-container { + margin: 20px 0; +} +.custom-control { + display: flex; + justify-content: center; +} +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; +} +.custom-control-label { + 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-switch .custom-control-label::before { + left: -2.25rem; + width: 1.75rem; + pointer-events: all; + border-radius: .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; +} +.custom-switch .custom-control-input:checked~.custom-control-label::after { + background-color: #fff; + -webkit-transform: translateX(.75rem); + transform: translateX(.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; +} +.custom-control-label::after { + position: absolute; + top: .25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background: no-repeat 50%/50% 50%; } \ No newline at end of file diff --git a/website/utils/redirect.js b/website/utils/redirect.js index e7bea71..456a8c4 100644 --- a/website/utils/redirect.js +++ b/website/utils/redirect.js @@ -1,9 +1,9 @@ function redirect(ctx, path) { - if (ctx.res) { + if (ctx.res != undefined) { ctx.res.writeHead(302, { Location: path }); ctx.res.end(); } else { - document.location.pathname = path; + document.location.href = path; } }