frontend: Édition profil + corrections backend
This commit is contained in:
		| @@ -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)); | ||||
|             } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
							
								
								
									
										9
									
								
								website/components/Modal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								website/components/Modal.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| const Modal = (props) => ( | ||||
|     <div className="Modal container-fluid"> | ||||
|         <div className="Modal__content"> | ||||
|             {props.children} | ||||
|         </div> | ||||
|     </div> | ||||
| ); | ||||
|  | ||||
| export default Modal; | ||||
| @@ -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('<p class="form-success"><b>Succès:</b> Connexion réussi!</p>'); | ||||
|                 setLoginLoading(false); | ||||
| @@ -46,8 +45,14 @@ function UserContextProvider(props) { | ||||
|         setIsAuth(false); | ||||
|     }  | ||||
|  | ||||
|     const changeUserValue = (user) => { | ||||
|         cookies.remove('user'); | ||||
|         cookies.set('user', user); | ||||
|         setUser(user); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth }}> | ||||
|         <UserContext.Provider value={{ user, loginUser, logoutUser, loginLoading, messageLogin, isAuth, changeUserValue }}> | ||||
|             {props.children} | ||||
|         </UserContext.Provider> | ||||
|     ); | ||||
|   | ||||
							
								
								
									
										29
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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", | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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 = () => { | ||||
|                             </div> | ||||
|                         </form> | ||||
|                         <div className="form-result text-center"> | ||||
|                             {(router.query.isConfirmed !== undefined) && <p className="form-success"><b>Succès:</b> Votre compte a bien été confirmé, vous pouvez maintenant vous connectez!</p>} | ||||
|                             {(router.query.isConfirmed !== undefined && messageLogin === "") && <p className="form-success"><b>Succès:</b> Votre compte a bien été confirmé, vous pouvez maintenant vous connectez!</p>} | ||||
|                             {(router.query.isSuccessEdit !== undefined && messageLogin === "") && <p className="form-success"><b>Succès:</b> Votre profil a bien été modifié, vous pouvez maintenant vous connectez!</p>} | ||||
|                             { | ||||
|                                 (loginLoading) ?  | ||||
|                                     <Loader /> | ||||
|   | ||||
| @@ -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(`<p class="form-error"><b>Erreur:</b> ${error.response.data.message}</p>`); | ||||
|                     setIsLoading(false); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <HeadTag title={`${props.name} - FunctionProject`} description={`Profil utilisateur de ${props.name}. ${(props.biography != undefined) ? props.biography : ""}`} /> | ||||
|  | ||||
|             <div className="container-fluid Profile__container"> | ||||
|             {/* Édition du profil */} | ||||
|             {(isOpen) && | ||||
|                 <Modal toggleModal={toggleModal}> | ||||
|                     <div className="container-fluid Profile__container"> | ||||
|                         <div className="row Profile__row"> | ||||
|                             <div className="col-24"> | ||||
|                                 <div className="Profile__Modal-top-container row"> | ||||
|                                     <div className="col-24"> | ||||
|                                         <span onClick={toggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                                             <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|                                         </span> | ||||
|                                         <h2 className="text-center">Éditer le profil</h2> | ||||
|                                         <p className="text-center"><em>(Vous devrez vous reconnecter après la sauvegarde) <br/> Si vous changez votre adresse email, vous devrez la confirmer (vérifier vos emails).</em></p> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|  | ||||
|                             <div className="col-24"> | ||||
|                                 <form onSubmit={handleSubmit}> | ||||
|                                     <div className="form-group"> | ||||
|                                         <label className="form-label" htmlFor="name">Nom :</label> | ||||
|                                         <input value={inputState.name} onChange={handleChange} type="text" name="name" id="name" className="form-control" placeholder="Divlo" /> | ||||
|                                     </div> | ||||
|  | ||||
|                                     <div className="form-group"> | ||||
|                                         <label className="form-label" htmlFor="email">Email :</label> | ||||
|                                         <input value={inputState.email} onChange={handleChange} type="email" name="email" id="email" className="form-control" placeholder="email@gmail.com" /> | ||||
|                                     </div> | ||||
|  | ||||
|                                     <div className="form-group custom-control custom-switch"> | ||||
|                                         <input onChange={(event) => handleChange(event, true)} type="checkbox" name="isPublicEmail" checked={inputState.isPublicEmail} className="custom-control-input" id="isPublicEmail" /> | ||||
|                                         <label className="custom-control-label" htmlFor="isPublicEmail">Email Public</label> | ||||
|                                     </div> | ||||
|  | ||||
|                                     <div className="form-group"> | ||||
|                                         <label className="form-label" htmlFor="biography">Biographie :</label> | ||||
|                                         <textarea style={{ height: 'auto' }} value={inputState.biography} onChange={handleChange} name="biography" id="biography" className="form-control" rows="5"></textarea> | ||||
|                                     </div> | ||||
|  | ||||
|                                     <div className="form-group"> | ||||
|                                         <label className="form-label" htmlFor="logo">Logo <em>(400x400 recommandé)</em> :</label> | ||||
|                                         <br/> | ||||
|                                         <input onChange={handleChange} accept="image/jpeg,image/jpg,image/png,image/gif" type="file" name="logo" id="logo" /> | ||||
|                                     </div> | ||||
|  | ||||
|                                     <div className="form-group text-center"> | ||||
|                                         <button type="submit" className="btn btn-dark">Sauvegarder</button> | ||||
|                                     </div> | ||||
|                                 </form> | ||||
|                                 <div className="form-result text-center"> | ||||
|                                     { | ||||
|                                         (isLoading) ?  | ||||
|                                             <Loader /> | ||||
|                                         : | ||||
|                                             htmlParser(message) | ||||
|                                     } | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </Modal> | ||||
|             } | ||||
|  | ||||
|             <div className={`container-fluid Profile__container ${(isOpen) ? "d-none" : ""}`}> | ||||
|                 <div className="row Profile__row"> | ||||
|                     <div className="col-20"> | ||||
|                         <div className="text-center"> | ||||
|                             <h1>Profil de <span className="important">{props.name}</span></h1> | ||||
|                         </div> | ||||
|                         <div className="row justify-content-center"> | ||||
|  | ||||
|                             <div className="col-24 text-center"> | ||||
|                                 <img className="Profile__logo" src={API_URL + props.logo} alt={props.name} /> | ||||
|                             </div> | ||||
|  | ||||
|                             <div className="col-24 text-center"> | ||||
|                                 {(props.biography != undefined) && <p>{props.biography}</p>} | ||||
|                                 {(props.email != undefined) && <p><span className="important">Email :</span> {props.email}</p>} | ||||
|                                 <p><span className="important">Date de création :</span> {publicationDate}</p> | ||||
|                             </div> | ||||
|  | ||||
|                             {(isAuth && user.name === props.name) &&                             | ||||
|                                 <button onClick={toggleModal} style={{ marginBottom: '25px' }} className="btn btn-dark"> | ||||
|                                     <FontAwesomeIcon icon={faPen} style={{cursor: 'pointer', width: '1rem'}} />  | ||||
|                                       Éditez le profil | ||||
|                                 </button> | ||||
|                             } | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
| @@ -59,13 +182,13 @@ const Profile = (props) => { | ||||
|                         <div className="col-24 text-center"> | ||||
|                             <h2>Derniers <span className="important">commentaires :</span></h2> | ||||
|                         </div> | ||||
|                         <div className="col-18 text-center"> | ||||
|                         <div className="col-24 text-center"> | ||||
|                             {props.commentsArray.map((comment) => ( | ||||
|                                 <div key={comment.id} className="row Profile__row Profile__comment"> | ||||
|                                     <div className="col-20"> | ||||
|                                         <p> | ||||
|                                             Posté sur la fonction   | ||||
|                                             <Link href={(comment.function.type === 'form' || comment.function.type === 'article') ? "/functions/[slug]" : `/functions/${props.slug}`} as={`/functions/${comment.function.slug}`}> | ||||
|                                             <Link href={(comment.function.type === 'form' || comment.function.type === 'article') ? "/functions/[slug]" : `/functions/${comment.function.slug}`} as={`/functions/${comment.function.slug}`}> | ||||
|                                                 <a>{comment.function.title}</a> | ||||
|                                             </Link>  | ||||
|                                              le {date.format(new Date(comment.createdAt), 'DD/MM/YYYY à HH:mm', true)} | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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%; | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user