🎨 website/utils API_URL + standardJS
This commit is contained in:
		| @@ -2,7 +2,7 @@ import Link from 'next/link' | ||||
| import { useState, forwardRef, memo } from 'react' | ||||
| import date from 'date-and-time' | ||||
| import Loader from '../Loader' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import { API_URL } from '../../utils/api' | ||||
| import './FunctionCard.css' | ||||
|  | ||||
| const FunctionCard = memo( | ||||
|   | ||||
| @@ -3,10 +3,9 @@ import { useEffect, useState, forwardRef, useContext } from 'react' | ||||
| import date from 'date-and-time' | ||||
| import htmlParser from 'html-react-parser' | ||||
| import { UserContext } from '../../../contexts/UserContext' | ||||
| import { API_URL } from '../../../utils/config/config' | ||||
| import ReactMarkdown from 'react-markdown' | ||||
| import CodeBlock from '../../CodeBlock' | ||||
| import api from '../../../utils/api' | ||||
| import api, { API_URL } from '../../../utils/api' | ||||
| import './CommentCard.css' | ||||
|  | ||||
| const CommentCard = forwardRef((props, ref) => { | ||||
|   | ||||
| @@ -5,8 +5,7 @@ import { faStar } from '@fortawesome/free-solid-svg-icons' | ||||
| import { faStar as farStar } from '@fortawesome/free-regular-svg-icons' | ||||
| import date from 'date-and-time' | ||||
| import { UserContext } from '../../contexts/UserContext' | ||||
| import api from '../../utils/api' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import api, { API_URL } from '../../utils/api' | ||||
| import '../FunctionCard/FunctionCard.css' | ||||
|  | ||||
| const FunctionComponentTop = props => { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { useState } from 'react' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import { API_URL } from '../../utils/api' | ||||
| import HeadTag from '../HeadTag' | ||||
| import FunctionTabsTop from './FunctionTabsTop' | ||||
| import FunctionComponentTop from './FunctionComponentTop' | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import Link from 'next/link' | ||||
| import { forwardRef, memo } from 'react' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import { API_URL } from '../../utils/api' | ||||
| import './UserCard.css' | ||||
|  | ||||
| const UserCard = memo( | ||||
|   | ||||
| @@ -10,9 +10,14 @@ const Error404 = () => ( | ||||
|       image='/images/error404.png' | ||||
|     /> | ||||
|     <div className='Error404__container'> | ||||
|       <h1>Erreur <span className='important'>404</span></h1> | ||||
|       <h1> | ||||
|         Erreur <span className='important'>404</span> | ||||
|       </h1> | ||||
|       <p className='text-center'> | ||||
|                 Cette page n'existe pas! <Link href='/'><a>Revenir à la page d'accueil ?</a></Link> | ||||
|         Cette page n'existe pas!{' '} | ||||
|         <Link href='/'> | ||||
|           <a>Revenir à la page d'accueil ?</a> | ||||
|         </Link> | ||||
|       </p> | ||||
|     </div> | ||||
|   </> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import axios from 'axios' | ||||
| import ReactMarkdown from 'react-markdown/with-html' | ||||
| import HeadTag from '../components/HeadTag' | ||||
|  | ||||
| const About = (props) => { | ||||
| const About = props => { | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag | ||||
| @@ -14,7 +14,23 @@ const About = (props) => { | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24 text-center'> | ||||
|             <h1 style={{ marginBottom: 0, paddingTop: '20px' }}>À-propos</h1> | ||||
|             <p style={{ marginTop: '5px', borderBottom: '1px solid var(--important)', paddingBottom: '30px' }}>(README.md du <a target='_blank' rel='noopener noreferrer' href='https://github.com/Divlo/FunctionProject'>GitHub</a>)</p> | ||||
|             <p | ||||
|               style={{ | ||||
|                 marginTop: '5px', | ||||
|                 borderBottom: '1px solid var(--important)', | ||||
|                 paddingBottom: '30px' | ||||
|               }} | ||||
|             > | ||||
|               (README.md du{' '} | ||||
|               <a | ||||
|                 target='_blank' | ||||
|                 rel='noopener noreferrer' | ||||
|                 href='https://github.com/Divlo/FunctionProject' | ||||
|               > | ||||
|                 GitHub | ||||
|               </a> | ||||
|               ) | ||||
|             </p> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
| @@ -24,9 +40,11 @@ const About = (props) => { | ||||
|               source={props.data} | ||||
|               escapeHtml={false} | ||||
|               linkTarget='_blank' | ||||
|               transformLinkUri={(uri) => { | ||||
|               transformLinkUri={uri => { | ||||
|                 if (uri.startsWith('./')) { | ||||
|                   return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice(2)}` | ||||
|                   return `https://github.com/Divlo/FunctionProject/blob/master/${uri.slice( | ||||
|                     2 | ||||
|                   )}` | ||||
|                 } | ||||
|                 return uri | ||||
|               }} | ||||
| @@ -39,7 +57,9 @@ const About = (props) => { | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps (_context) { | ||||
|   const { data } = await axios.get('https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md') | ||||
|   const { data } = await axios.get( | ||||
|     'https://raw.githubusercontent.com/Divlo/FunctionProject/master/README.md' | ||||
|   ) | ||||
|   return { | ||||
|     props: { data } | ||||
|   } | ||||
|   | ||||
| @@ -6,42 +6,68 @@ import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction' | ||||
| import EditArticleFunction from '../../components/FunctionAdmin/EditArticleFunction' | ||||
| import EditFormFunction from '../../components/FunctionAdmin/EditFormFunction' | ||||
| import redirect from '../../utils/redirect' | ||||
| import api from '../../utils/api' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import api, { API_URL } from '../../utils/api' | ||||
| import '../../components/FunctionPage/FunctionTabs.css' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const AdminFunctionComponent = (props) => { | ||||
| const AdminFunctionComponent = props => { | ||||
|   const [slideIndex, setSlideIndex] = useState(0) | ||||
|  | ||||
|   const handleDeleteFunction = async () => { | ||||
|     await api.delete(`/admin/functions/${props.functionInfo.id}`, { headers: { Authorization: props.user.token } }) | ||||
|     await api.delete(`/admin/functions/${props.functionInfo.id}`, { | ||||
|       headers: { Authorization: props.user.token } | ||||
|     }) | ||||
|     redirect({}, '/admin') | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title={props.functionInfo.title} description={props.functionInfo.description} image={API_URL + props.functionInfo.image} /> | ||||
|       <HeadTag | ||||
|         title={props.functionInfo.title} | ||||
|         description={props.functionInfo.description} | ||||
|         image={API_URL + props.functionInfo.image} | ||||
|       /> | ||||
|  | ||||
|       <div className='container-fluid'> | ||||
|         <div className='container'> | ||||
|           <div className='row justify-content-center'> | ||||
|             <ul className='FunctionTabs__nav'> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(0)} className={`FunctionTabs__nav-link ${(slideIndex === 0) && 'FunctionTabs__nav-link-active'}`}>✒️ Modifier</a> | ||||
|                 <a | ||||
|                   onClick={() => setSlideIndex(0)} | ||||
|                   className={`FunctionTabs__nav-link ${slideIndex === 0 && | ||||
|                     'FunctionTabs__nav-link-active'}`} | ||||
|                 > | ||||
|                   ✒️ Modifier | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(1)} className={`FunctionTabs__nav-link ${(slideIndex === 1) && 'FunctionTabs__nav-link-active'}`}>📝 Article</a> | ||||
|                 <a | ||||
|                   onClick={() => setSlideIndex(1)} | ||||
|                   className={`FunctionTabs__nav-link ${slideIndex === 1 && | ||||
|                     'FunctionTabs__nav-link-active'}`} | ||||
|                 > | ||||
|                   📝 Article | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li className='FunctionTabs__nav-item'> | ||||
|                 <a onClick={() => setSlideIndex(2)} className={`FunctionTabs__nav-link ${(slideIndex === 2) && 'FunctionTabs__nav-link-active'}`}>⚙️ Utilisation</a> | ||||
|                 <a | ||||
|                   onClick={() => setSlideIndex(2)} | ||||
|                   className={`FunctionTabs__nav-link ${slideIndex === 2 && | ||||
|                     'FunctionTabs__nav-link-active'}`} | ||||
|                 > | ||||
|                   ⚙️ Utilisation | ||||
|                 </a> | ||||
|               </li> | ||||
|             </ul> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div className='container-fluid'> | ||||
|           <SwipeableViews onChangeIndex={(index) => setSlideIndex(index)} index={slideIndex}> | ||||
|           <SwipeableViews | ||||
|             onChangeIndex={index => setSlideIndex(index)} | ||||
|             index={slideIndex} | ||||
|           > | ||||
|             <div className='Admin__Function-slide'> | ||||
|               <AddEditFunction | ||||
|                 defaultInputState={{ ...props.functionInfo }} | ||||
| @@ -49,7 +75,9 @@ const AdminFunctionComponent = (props) => { | ||||
|                 isEditing | ||||
|               /> | ||||
|               <div style={{ marginBottom: '30px' }} className='text-center'> | ||||
|                 <button onClick={handleDeleteFunction} className='btn btn-dark'>Supprimer la fonction</button> | ||||
|                 <button onClick={handleDeleteFunction} className='btn btn-dark'> | ||||
|                   Supprimer la fonction | ||||
|                 </button> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div className='Admin__Function-slide'> | ||||
| @@ -72,8 +100,9 @@ export async function getServerSideProps (context) { | ||||
|   if (!user.isAdmin) { | ||||
|     return redirect(context, '/404') | ||||
|   } | ||||
|   return api.get(`/admin/functions/${slug}`, { headers: { Authorization: user.token } }) | ||||
|     .then((response) => { | ||||
|   return api | ||||
|     .get(`/admin/functions/${slug}`, { headers: { Authorization: user.token } }) | ||||
|     .then(response => { | ||||
|       return { | ||||
|         props: { | ||||
|           user, | ||||
|   | ||||
| @@ -10,54 +10,77 @@ import AddEditFunction from '../../components/FunctionAdmin/AddEditFunction' | ||||
| import redirect from '../../utils/redirect' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const Admin = (props) => { | ||||
| const Admin = props => { | ||||
|   const [isOpen, setIsOpen] = useState(false) | ||||
|  | ||||
|   const toggleModal = () => setIsOpen(!isOpen) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject." /> | ||||
|       <HeadTag | ||||
|         title='Admin - FunctionProject' | ||||
|         description="Page d'administration de FunctionProject." | ||||
|       /> | ||||
|  | ||||
|       {/* Création d'une fonction */} | ||||
|       {(isOpen) | ||||
|         ? ( | ||||
|           <Modal toggleModal={toggleModal}> | ||||
|             <div className='Admin__Modal__container container-fluid'> | ||||
|               <div className='Admin__Modal__row row'> | ||||
|                 <div className='col-24'> | ||||
|                   <div className='Admin__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'>Crée une nouvelle fonction</h2> | ||||
|                     </div> | ||||
|       {isOpen ? ( | ||||
|         <Modal toggleModal={toggleModal}> | ||||
|           <div className='Admin__Modal__container container-fluid'> | ||||
|             <div className='Admin__Modal__row row'> | ||||
|               <div className='col-24'> | ||||
|                 <div className='Admin__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'>Crée une nouvelle fonction</h2> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|  | ||||
|                 <div className='col-24'> | ||||
|                   <AddEditFunction defaultInputState={{ type: 'form' }} {...props} /> | ||||
|                 </div> | ||||
|               <div className='col-24'> | ||||
|                 <AddEditFunction | ||||
|                   defaultInputState={{ type: 'form' }} | ||||
|                   {...props} | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </Modal> | ||||
|         ) | ||||
|  | ||||
|         : ( | ||||
|           <FunctionsList isAdmin token={props.user.token}> | ||||
|             <div className='col-24'> | ||||
|               <h1 className='Functions__title'>Administration</h1> | ||||
|               <button onClick={toggleModal} style={{ margin: '0 0 40px 0' }} className='btn btn-dark'>Crée une nouvelle fonction</button> | ||||
|               <Link href='/admin/manageCategories'> | ||||
|                 <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les catégories</button> | ||||
|               </Link> | ||||
|               <Link href='/admin/manageQuotes'> | ||||
|                 <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>Gérer les citations</button> | ||||
|               </Link> | ||||
|             </div> | ||||
|           </FunctionsList> | ||||
|         )} | ||||
|           </div> | ||||
|         </Modal> | ||||
|       ) : ( | ||||
|         <FunctionsList isAdmin token={props.user.token}> | ||||
|           <div className='col-24'> | ||||
|             <h1 className='Functions__title'>Administration</h1> | ||||
|             <button | ||||
|               onClick={toggleModal} | ||||
|               style={{ margin: '0 0 40px 0' }} | ||||
|               className='btn btn-dark' | ||||
|             > | ||||
|               Crée une nouvelle fonction | ||||
|             </button> | ||||
|             <Link href='/admin/manageCategories'> | ||||
|               <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'> | ||||
|                 Gérer les catégories | ||||
|               </button> | ||||
|             </Link> | ||||
|             <Link href='/admin/manageQuotes'> | ||||
|               <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'> | ||||
|                 Gérer les citations | ||||
|               </button> | ||||
|             </Link> | ||||
|           </div> | ||||
|         </FunctionsList> | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -15,23 +15,36 @@ import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const defaultCategoryState = { name: '', color: '#ffffff' } | ||||
|  | ||||
| const AddEditCategory = (props) => { | ||||
| const AddEditCategory = props => { | ||||
|   const [inputState, setInputState] = useState(props.defaultInputState) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|  | ||||
|   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 apiCallCategory = () => { | ||||
|     if (props.isEditing) return api.put(`/admin/categories/${inputState.id}`, { name: inputState.name, color: inputState.color }, { headers: { Authorization: props.user.token } }) | ||||
|     return api.post('/admin/categories', inputState, { headers: { Authorization: props.user.token } }) | ||||
|     if (props.isEditing) { | ||||
|       return api.put( | ||||
|         `/admin/categories/${inputState.id}`, | ||||
|         { name: inputState.name, color: inputState.color }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|     } | ||||
|     return api.post('/admin/categories', inputState, { | ||||
|       headers: { Authorization: props.user.token } | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = (event) => { | ||||
|   const handleSubmit = event => { | ||||
|     event.preventDefault() | ||||
|     setIsLoading(true) | ||||
|     apiCallCategory() | ||||
| @@ -39,8 +52,10 @@ const AddEditCategory = (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) | ||||
|       }) | ||||
|   } | ||||
| @@ -51,10 +66,20 @@ const AddEditCategory = (props) => { | ||||
|         <div className='col-24'> | ||||
|           <div className='Admin__Modal-top-container row'> | ||||
|             <div className='col-24'> | ||||
|               <span onClick={props.handleToggleModal} style={{ cursor: 'pointer', position: 'absolute', left: 0 }}> | ||||
|                 <FontAwesomeIcon icon={faTimes} style={{ width: '1.5rem', color: 'red' }} /> | ||||
|               <span | ||||
|                 onClick={props.handleToggleModal} | ||||
|                 style={{ cursor: 'pointer', position: 'absolute', left: 0 }} | ||||
|               > | ||||
|                 <FontAwesomeIcon | ||||
|                   icon={faTimes} | ||||
|                   style={{ width: '1.5rem', color: 'red' }} | ||||
|                 /> | ||||
|               </span> | ||||
|               <h2 className='text-center'>{(props.isEditing) ? 'Modifier la catégorie' : 'Crée une nouvelle catégorie'}</h2> | ||||
|               <h2 className='text-center'> | ||||
|                 {props.isEditing | ||||
|                   ? 'Modifier la catégorie' | ||||
|                   : 'Crée une nouvelle catégorie'} | ||||
|               </h2> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| @@ -62,25 +87,39 @@ const AddEditCategory = (props) => { | ||||
|         <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='(e.g : ✨ Utilitaires)' /> | ||||
|               <label className='form-label' htmlFor='name'> | ||||
|                 Nom : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={inputState.name} | ||||
|                 onChange={handleChange} | ||||
|                 type='text' | ||||
|                 name='name' | ||||
|                 id='name' | ||||
|                 className='form-control' | ||||
|                 placeholder='(e.g : ✨ Utilitaires)' | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <div className='form-group'> | ||||
|               <label className='form-label' htmlFor='title'>Couleur :</label> | ||||
|               <PhotoshopPicker color={inputState.color} onChange={(color) => handleChange({ target: { name: 'color', value: color.hex } })} /> | ||||
|               <label className='form-label' htmlFor='title'> | ||||
|                 Couleur : | ||||
|               </label> | ||||
|               <PhotoshopPicker | ||||
|                 color={inputState.color} | ||||
|                 onChange={color => | ||||
|                   handleChange({ target: { name: 'color', value: color.hex } })} | ||||
|               /> | ||||
|             </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> | ||||
|         </div> | ||||
|       </div> | ||||
| @@ -88,22 +127,26 @@ const AddEditCategory = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const manageCategories = (props) => { | ||||
| const manageCategories = props => { | ||||
|   const [, categories] = useAPI('/categories') | ||||
|   const [isOpen, setIsOpen] = useState(false) | ||||
|   const [isEditing, setIsEditing] = useState(false) | ||||
|   const [defaultInputState, setDefaultInputState] = useState(defaultCategoryState) | ||||
|   const [defaultInputState, setDefaultInputState] = useState( | ||||
|     defaultCategoryState | ||||
|   ) | ||||
|  | ||||
|   const toggleModal = () => setIsOpen(!isOpen) | ||||
|  | ||||
|   const handleRemoveCategory = async (categoryId) => { | ||||
|   const handleRemoveCategory = async categoryId => { | ||||
|     try { | ||||
|       await api.delete(`/admin/categories/${categoryId}`, { headers: { Authorization: props.user.token } }) | ||||
|       await api.delete(`/admin/categories/${categoryId}`, { | ||||
|         headers: { Authorization: props.user.token } | ||||
|       }) | ||||
|       window.location.reload(true) | ||||
|     } catch {} | ||||
|   } | ||||
|  | ||||
|   const handleEditCategory = (categoryInfo) => { | ||||
|   const handleEditCategory = categoryInfo => { | ||||
|     setDefaultInputState(categoryInfo) | ||||
|     setIsEditing(true) | ||||
|     toggleModal() | ||||
| @@ -111,64 +154,124 @@ const manageCategories = (props) => { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les catégories." /> | ||||
|       <HeadTag | ||||
|         title='Admin - FunctionProject' | ||||
|         description="Page d'administration de FunctionProject. Gérer les catégories." | ||||
|       /> | ||||
|  | ||||
|       { | ||||
|         (isOpen) | ||||
|           ? ( | ||||
|             <Modal> | ||||
|               <AddEditCategory handleToggleModal={toggleModal} defaultInputState={defaultInputState} {...props} isEditing={isEditing} /> | ||||
|             </Modal> | ||||
|           ) | ||||
|           : ( | ||||
|             <div className='container-fluid text-center'> | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='col-24'> | ||||
|                   <h1>Gérer les catégories</h1> | ||||
|                   <button onClick={() => { setDefaultInputState(defaultCategoryState); toggleModal(); setIsEditing(false) }} style={{ margin: '0 0 40px 0' }} className='btn btn-dark'>Ajouter une catégorie</button> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='container-fluid'> | ||||
|                   <div className='col-24 table-column'> | ||||
|                     <table className='table'> | ||||
|                       <thead> | ||||
|                         <tr> | ||||
|                           <th className='table-row' scope='col'>id</th> | ||||
|                           <th className='table-row' scope='col'>name</th> | ||||
|                           <th className='table-row' scope='col'>color</th> | ||||
|                           <th className='table-row' scope='col'>createdAt</th> | ||||
|                           <th className='table-row' scope='col'>updatedAt</th> | ||||
|                           <th className='table-row' scope='col'>Modifier</th> | ||||
|                           <th className='table-row' scope='col'>Supprimer</th> | ||||
|       {isOpen ? ( | ||||
|         <Modal> | ||||
|           <AddEditCategory | ||||
|             handleToggleModal={toggleModal} | ||||
|             defaultInputState={defaultInputState} | ||||
|             {...props} | ||||
|             isEditing={isEditing} | ||||
|           /> | ||||
|         </Modal> | ||||
|       ) : ( | ||||
|         <div className='container-fluid text-center'> | ||||
|           <div className='row justify-content-center'> | ||||
|             <div className='col-24'> | ||||
|               <h1>Gérer les catégories</h1> | ||||
|               <button | ||||
|                 onClick={() => { | ||||
|                   setDefaultInputState(defaultCategoryState) | ||||
|                   toggleModal() | ||||
|                   setIsEditing(false) | ||||
|                 }} | ||||
|                 style={{ margin: '0 0 40px 0' }} | ||||
|                 className='btn btn-dark' | ||||
|               > | ||||
|                 Ajouter une catégorie | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className='row justify-content-center'> | ||||
|             <div className='container-fluid'> | ||||
|               <div className='col-24 table-column'> | ||||
|                 <table className='table'> | ||||
|                   <thead> | ||||
|                     <tr> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         id | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         name | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         color | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         createdAt | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         updatedAt | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         Modifier | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         Supprimer | ||||
|                       </th> | ||||
|                     </tr> | ||||
|                   </thead> | ||||
|                   <tbody> | ||||
|                     {categories.map(category => { | ||||
|                       return ( | ||||
|                         <tr | ||||
|                           key={category.id} | ||||
|                           style={{ backgroundColor: category.color }} | ||||
|                         > | ||||
|                           <td className='table-row'>{category.id}</td> | ||||
|                           <td className='table-row'>{category.name}</td> | ||||
|                           <td className='table-row'>{category.color}</td> | ||||
|                           <td className='table-row'> | ||||
|                             {date.format( | ||||
|                               new Date(category.createdAt), | ||||
|                               'DD/MM/YYYY à HH:mm', | ||||
|                               true | ||||
|                             )} | ||||
|                           </td> | ||||
|                           <td className='table-row'> | ||||
|                             {date.format( | ||||
|                               new Date(category.updatedAt), | ||||
|                               'DD/MM/YYYY à HH:mm', | ||||
|                               true | ||||
|                             )} | ||||
|                           </td> | ||||
|                           <td | ||||
|                             style={{ cursor: 'pointer' }} | ||||
|                             onClick={() => | ||||
|                               handleEditCategory({ | ||||
|                                 name: category.name, | ||||
|                                 color: category.color, | ||||
|                                 id: category.id | ||||
|                               })} | ||||
|                           > | ||||
|                             <FontAwesomeIcon | ||||
|                               icon={faPen} | ||||
|                               style={{ width: '1.5rem' }} | ||||
|                             /> | ||||
|                           </td> | ||||
|                           <td | ||||
|                             style={{ cursor: 'pointer' }} | ||||
|                             onClick={() => handleRemoveCategory(category.id)} | ||||
|                           > | ||||
|                             <FontAwesomeIcon | ||||
|                               icon={faTrash} | ||||
|                               style={{ width: '1.5rem' }} | ||||
|                             /> | ||||
|                           </td> | ||||
|                         </tr> | ||||
|                       </thead> | ||||
|                       <tbody> | ||||
|                         {categories.map((category) => { | ||||
|                           return ( | ||||
|                             <tr key={category.id} style={{ backgroundColor: category.color }}> | ||||
|                               <td className='table-row'>{category.id}</td> | ||||
|                               <td className='table-row'>{category.name}</td> | ||||
|                               <td className='table-row'>{category.color}</td> | ||||
|                               <td className='table-row'>{date.format(new Date(category.createdAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                               <td className='table-row'>{date.format(new Date(category.updatedAt), 'DD/MM/YYYY à HH:mm', true)}</td> | ||||
|                               <td style={{ cursor: 'pointer' }} onClick={() => handleEditCategory({ name: category.name, color: category.color, id: category.id })}> | ||||
|                                 <FontAwesomeIcon icon={faPen} style={{ width: '1.5rem' }} /> | ||||
|                               </td> | ||||
|                               <td style={{ cursor: 'pointer' }} onClick={() => handleRemoveCategory(category.id)}> | ||||
|                                 <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                               </td> | ||||
|                             </tr> | ||||
|                           ) | ||||
|                         })} | ||||
|                       </tbody> | ||||
|                     </table> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                       ) | ||||
|                     })} | ||||
|                   </tbody> | ||||
|                 </table> | ||||
|               </div> | ||||
|             </div> | ||||
|           ) | ||||
|       } | ||||
|           </div> | ||||
|         </div> | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,12 @@ import HeadTag from '../../components/HeadTag' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/admin.css' | ||||
|  | ||||
| const manageQuotes = (props) => { | ||||
|   const [quotesData, setQuotesData] = useState({ hasMore: true, rows: [], totalItems: 0 }) | ||||
| const manageQuotes = props => { | ||||
|   const [quotesData, setQuotesData] = useState({ | ||||
|     hasMore: true, | ||||
|     rows: [], | ||||
|     totalItems: 0 | ||||
|   }) | ||||
|   const [isLoadingQuotes, setLoadingQuotes] = useState(true) | ||||
|   const [pageQuotes, setPageQuotes] = useState(1) | ||||
|  | ||||
| @@ -20,7 +24,9 @@ const manageQuotes = (props) => { | ||||
|  | ||||
|   const getQuotesData = async () => { | ||||
|     setLoadingQuotes(true) | ||||
|     const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { headers: { Authorization: props.user.token } }) | ||||
|     const { data } = await api.get(`/admin/quotes?limit=20page=${pageQuotes}`, { | ||||
|       headers: { Authorization: props.user.token } | ||||
|     }) | ||||
|     setQuotesData({ | ||||
|       hasMore: data.hasMore, | ||||
|       rows: [...quotesData.rows, ...data.rows], | ||||
| @@ -31,33 +37,48 @@ const manageQuotes = (props) => { | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastQuoteRef = useCallback((node) => { | ||||
|     if (isLoadingQuotes) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|         setPageQuotes(pageQuotes + 1) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingQuotes, quotesData.hasMore]) | ||||
|   const lastQuoteRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingQuotes) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|             setPageQuotes(pageQuotes + 1) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingQuotes, quotesData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   const handleValidationQuote = async (id, isValid) => { | ||||
|     try { | ||||
|       await api.put(`/admin/quotes/${id}`, { isValid }, { headers: { Authorization: props.user.token } }) | ||||
|       await api.put( | ||||
|         `/admin/quotes/${id}`, | ||||
|         { isValid }, | ||||
|         { headers: { Authorization: props.user.token } } | ||||
|       ) | ||||
|       window.location.reload(true) | ||||
|     } catch {} | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title='Admin - FunctionProject' description="Page d'administration de FunctionProject. Gérer les citations." /> | ||||
|       <HeadTag | ||||
|         title='Admin - FunctionProject' | ||||
|         description="Page d'administration de FunctionProject. Gérer les citations." | ||||
|       /> | ||||
|  | ||||
|       <div className='container-fluid'> | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24 text-center'> | ||||
|             <h2>Liste des citations (non validées) : </h2> | ||||
|             <p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p> | ||||
|             <p style={{ marginTop: '5px' }}> | ||||
|               Total de {quotesData.totalItems} citations. | ||||
|             </p> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
| @@ -66,35 +87,72 @@ const manageQuotes = (props) => { | ||||
|             <table className='table'> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th className='table-row' scope='col'>Citation/Proverbe</th> | ||||
|                   <th className='table-row' scope='col'>Auteur</th> | ||||
|                   <th className='table-row' scope='col'>Proposée par</th> | ||||
|                   <th className='table-row' scope='col'>Valider</th> | ||||
|                   <th className='table-row' scope='col'>Supprimer</th> | ||||
|                   <th className='table-row' scope='col'> | ||||
|                     Citation/Proverbe | ||||
|                   </th> | ||||
|                   <th className='table-row' scope='col'> | ||||
|                     Auteur | ||||
|                   </th> | ||||
|                   <th className='table-row' scope='col'> | ||||
|                     Proposée par | ||||
|                   </th> | ||||
|                   <th className='table-row' scope='col'> | ||||
|                     Valider | ||||
|                   </th> | ||||
|                   <th className='table-row' scope='col'> | ||||
|                     Supprimer | ||||
|                   </th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 {quotesData.rows.map((currentQuote, index) => { | ||||
|                   const quoteJSX = ( | ||||
|                     <> | ||||
|                       <td className='table-row text-center'>{currentQuote.quote}</td> | ||||
|                       <td className='table-row text-center'>{currentQuote.author}</td> | ||||
|                       <td className='table-row text-center'> | ||||
|                         <Link href='/users/[name]' as={`/users/${currentQuote.user.name}`}> | ||||
|                         {currentQuote.quote} | ||||
|                       </td> | ||||
|                       <td className='table-row text-center'> | ||||
|                         {currentQuote.author} | ||||
|                       </td> | ||||
|                       <td className='table-row text-center'> | ||||
|                         <Link | ||||
|                           href='/users/[name]' | ||||
|                           as={`/users/${currentQuote.user.name}`} | ||||
|                         > | ||||
|                           <a>{currentQuote.user.name}</a> | ||||
|                         </Link> | ||||
|                       </td> | ||||
|                       <td onClick={() => handleValidationQuote(currentQuote.id, true)} className='table-row text-center' style={{ cursor: 'pointer' }}> | ||||
|                         <FontAwesomeIcon icon={faCheck} style={{ width: '1.5rem' }} /> | ||||
|                       <td | ||||
|                         onClick={() => | ||||
|                           handleValidationQuote(currentQuote.id, true)} | ||||
|                         className='table-row text-center' | ||||
|                         style={{ cursor: 'pointer' }} | ||||
|                       > | ||||
|                         <FontAwesomeIcon | ||||
|                           icon={faCheck} | ||||
|                           style={{ width: '1.5rem' }} | ||||
|                         /> | ||||
|                       </td> | ||||
|                       <td onClick={() => handleValidationQuote(currentQuote.id, false)} className='table-row text-center' style={{ cursor: 'pointer' }}> | ||||
|                         <FontAwesomeIcon icon={faTrash} style={{ width: '1.5rem' }} /> | ||||
|                       <td | ||||
|                         onClick={() => | ||||
|                           handleValidationQuote(currentQuote.id, false)} | ||||
|                         className='table-row text-center' | ||||
|                         style={{ cursor: 'pointer' }} | ||||
|                       > | ||||
|                         <FontAwesomeIcon | ||||
|                           icon={faTrash} | ||||
|                           style={{ width: '1.5rem' }} | ||||
|                         /> | ||||
|                       </td> | ||||
|                     </> | ||||
|                   ) | ||||
|                   // Si c'est le dernier élément | ||||
|                   if (quotesData.rows.length === index + 1) { | ||||
|                     return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr> | ||||
|                     return ( | ||||
|                       <tr key={index} ref={lastQuoteRef}> | ||||
|                         {quoteJSX} | ||||
|                       </tr> | ||||
|                     ) | ||||
|                   } | ||||
|                   return <tr key={index}>{quoteJSX}</tr> | ||||
|                 })} | ||||
|   | ||||
| @@ -7,12 +7,18 @@ import redirect from '../../utils/redirect' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/FunctionComponent.css' | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
| const FunctionTabManager = props => { | ||||
|   if (props.type === 'form') { | ||||
|     return ( | ||||
|       <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|       <FunctionTabs | ||||
|         setSlideIndex={props.setSlideIndex} | ||||
|         slideIndex={props.slideIndex} | ||||
|       > | ||||
|         <div className='FunctionComponent__slide'> | ||||
|           <FunctionForm inputsArray={[...props.utilizationForm || []]} slug={props.slug} /> | ||||
|           <FunctionForm | ||||
|             inputsArray={[...(props.utilizationForm || [])]} | ||||
|             slug={props.slug} | ||||
|           /> | ||||
|         </div> | ||||
|         <div className='FunctionComponent__slide'> | ||||
|           <FunctionArticle article={props.article} /> | ||||
| @@ -25,7 +31,10 @@ const FunctionTabManager = (props) => { | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|     <FunctionTabs | ||||
|       setSlideIndex={props.setSlideIndex} | ||||
|       slideIndex={props.slideIndex} | ||||
|     > | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <FunctionArticle article={props.article} /> | ||||
|       </div> | ||||
| @@ -36,18 +45,23 @@ const FunctionTabManager = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionComponent = (props) => ( | ||||
| const FunctionComponent = props => ( | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
|     tabNames={(props.type === 'form') ? ['⚙️ Utilisation', '📝 Article', '📬 Commentaires'] : ['📝 Article', '📬 Commentaires']} | ||||
|     tabNames={ | ||||
|       props.type === 'form' | ||||
|         ? ['⚙️ Utilisation', '📝 Article', '📬 Commentaires'] | ||||
|         : ['📝 Article', '📬 Commentaires'] | ||||
|     } | ||||
|   /> | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   const { slug } = context.params | ||||
|   return api.get(`/functions/${slug}`) | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get(`/functions/${slug}`) | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ const Chronometer = () => { | ||||
|     } else { | ||||
|       if (interval) clearInterval(interval) | ||||
|       interval = setInterval(() => { | ||||
|         setTimeLength((time) => time + 1) | ||||
|         setTimeLength(time => time + 1) | ||||
|       }, 1000) | ||||
|     } | ||||
|     setIsPlaying(!isPlaying) | ||||
| @@ -44,7 +44,10 @@ const Chronometer = () => { | ||||
|  | ||||
|   const getFormattedValue = () => { | ||||
|     const minutesAndSeconds = convertSeconds(timeLength) | ||||
|     const minutes = (minutesAndSeconds.minutes < 100) ? (('0' + minutesAndSeconds.minutes).slice(-2)) : minutesAndSeconds.minutes | ||||
|     const minutes = | ||||
|       minutesAndSeconds.minutes < 100 | ||||
|         ? ('0' + minutesAndSeconds.minutes).slice(-2) | ||||
|         : minutesAndSeconds.minutes | ||||
|     const seconds = ('0' + minutesAndSeconds.seconds).slice(-2) | ||||
|     return `${minutes}:${seconds}` | ||||
|   } | ||||
| @@ -65,9 +68,15 @@ const Chronometer = () => { | ||||
|             <div className='Chronometer__item Chronometer__buttons'> | ||||
|               <div className='Chronomter__row Chronometer__row-button'> | ||||
|                 <button onClick={handlePlayPause} className='Chronometer-btn'> | ||||
|                   <FontAwesomeIcon {...(isPlaying) ? { icon: faPause } : { icon: faPlay }} /> | ||||
|                   <FontAwesomeIcon | ||||
|                     {...(isPlaying ? { icon: faPause } : { icon: faPlay })} | ||||
|                   /> | ||||
|                 </button> | ||||
|                 <button onClick={handleReset} className='Chronometer-btn' title='Remettre à zéro ?'> | ||||
|                 <button | ||||
|                   onClick={handleReset} | ||||
|                   className='Chronometer-btn' | ||||
|                   title='Remettre à zéro ?' | ||||
|                 > | ||||
|                   <FontAwesomeIcon icon={faSync} /> | ||||
|                 </button> | ||||
|               </div> | ||||
| @@ -82,14 +91,24 @@ const Chronometer = () => { | ||||
| const Pomodoro = () => { | ||||
|   return ( | ||||
|     <div style={{ marginBottom: '50px' }} className='container-fluid'> | ||||
|       <Codepen hash='vYEbPoB' user='Divlo' height={800} defaultTab='result' preview={false} loader={() => <Loader />} /> | ||||
|       <Codepen | ||||
|         hash='vYEbPoB' | ||||
|         user='Divlo' | ||||
|         height={800} | ||||
|         defaultTab='result' | ||||
|         preview={false} | ||||
|         loader={() => <Loader />} | ||||
|       /> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
| const FunctionTabManager = props => { | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|     <FunctionTabs | ||||
|       setSlideIndex={props.setSlideIndex} | ||||
|       slideIndex={props.slideIndex} | ||||
|     > | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <Chronometer /> | ||||
|       </div> | ||||
| @@ -106,17 +125,23 @@ const FunctionTabManager = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const chronometerTimer = (props) => ( | ||||
| const chronometerTimer = props => ( | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
|     tabNames={['⏰ Chronomètre', '⌛ Pomodoro', '📝 Article', '📬 Commentaires']} | ||||
|     tabNames={[ | ||||
|       '⏰ Chronomètre', | ||||
|       '⌛ Pomodoro', | ||||
|       '📝 Article', | ||||
|       '📬 Commentaires' | ||||
|     ]} | ||||
|   /> | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   return api.get('/functions/chronometerTimer') | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get('/functions/chronometerTimer') | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -43,8 +43,16 @@ const GenerateQuote = () => { | ||||
|     <div className='container-fluid'> | ||||
|       <div className='row justify-content-center'> | ||||
|         <div className='col-24 text-center'> | ||||
|           <button onClick={getRandomQuote} className='btn btn-dark'>Générer une nouvelle citation</button> | ||||
|           <button style={{ marginLeft: '15px' }} onClick={handleCopyQuote} className='btn btn-dark'>Copier la citation</button> | ||||
|           <button onClick={getRandomQuote} className='btn btn-dark'> | ||||
|             Générer une nouvelle citation | ||||
|           </button> | ||||
|           <button | ||||
|             style={{ marginLeft: '15px' }} | ||||
|             onClick={handleCopyQuote} | ||||
|             className='btn btn-dark' | ||||
|           > | ||||
|             Copier la citation | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div style={{ marginTop: '20px' }} className='row justify-content-center'> | ||||
| @@ -53,7 +61,10 @@ const GenerateQuote = () => { | ||||
|           <p>- {quote?.author}</p> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div style={{ marginBottom: '20px' }} className='row justify-content-center'> | ||||
|       <div | ||||
|         style={{ marginBottom: '20px' }} | ||||
|         className='row justify-content-center' | ||||
|       > | ||||
|         <a | ||||
|           target='_blank' | ||||
|           rel='noopener noreferrer' | ||||
| @@ -69,12 +80,16 @@ const GenerateQuote = () => { | ||||
|  | ||||
| let pageQuotes = 1 | ||||
| const QuoteList = () => { | ||||
|   const [quotesData, setQuotesData] = useState({ hasMore: true, rows: [], totalItems: 0 }) | ||||
|   const [quotesData, setQuotesData] = useState({ | ||||
|     hasMore: true, | ||||
|     rows: [], | ||||
|     totalItems: 0 | ||||
|   }) | ||||
|   const [isLoadingQuotes, setLoadingQuotes] = useState(true) | ||||
|  | ||||
|   // Récupère les citations initiales | ||||
|   useEffect(() => { | ||||
|     getQuotesData().then((data) => setQuotesData(data)) | ||||
|     getQuotesData().then(data => setQuotesData(data)) | ||||
|   }, []) | ||||
|  | ||||
|   const getQuotesData = async () => { | ||||
| @@ -86,32 +101,40 @@ const QuoteList = () => { | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastQuoteRef = useCallback((node) => { | ||||
|     if (isLoadingQuotes) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|         pageQuotes += 1 | ||||
|         getQuotesData().then((data) => { | ||||
|           setQuotesData((oldData) => { | ||||
|             return { | ||||
|               hasMore: data.hasMore, | ||||
|               rows: [...oldData.rows, ...data.rows], | ||||
|               totalItems: data.totalItems | ||||
|             } | ||||
|           }) | ||||
|         }) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingQuotes, quotesData.hasMore]) | ||||
|   const lastQuoteRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingQuotes) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && quotesData.hasMore) { | ||||
|             pageQuotes += 1 | ||||
|             getQuotesData().then(data => { | ||||
|               setQuotesData(oldData => { | ||||
|                 return { | ||||
|                   hasMore: data.hasMore, | ||||
|                   rows: [...oldData.rows, ...data.rows], | ||||
|                   totalItems: data.totalItems | ||||
|                 } | ||||
|               }) | ||||
|             }) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingQuotes, quotesData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       <div className='row justify-content-center'> | ||||
|         <div className='col-24 text-center'> | ||||
|           <h2 style={{ margin: 0 }}>Liste des citations : </h2> | ||||
|           <p style={{ marginTop: '5px' }}>Total de {quotesData.totalItems} citations.</p> | ||||
|           <p style={{ marginTop: '5px' }}> | ||||
|             Total de {quotesData.totalItems} citations. | ||||
|           </p> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
| @@ -120,19 +143,32 @@ const QuoteList = () => { | ||||
|           <table> | ||||
|             <thead> | ||||
|               <tr> | ||||
|                 <th className='table-row' scope='col'>Citation/Proverbe</th> | ||||
|                 <th className='table-row' scope='col'>Auteur</th> | ||||
|                 <th className='table-row' scope='col'>Proposée par</th> | ||||
|                 <th className='table-row' scope='col'> | ||||
|                   Citation/Proverbe | ||||
|                 </th> | ||||
|                 <th className='table-row' scope='col'> | ||||
|                   Auteur | ||||
|                 </th> | ||||
|                 <th className='table-row' scope='col'> | ||||
|                   Proposée par | ||||
|                 </th> | ||||
|               </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|               {quotesData.rows.map((currentQuote, index) => { | ||||
|                 const quoteJSX = ( | ||||
|                   <> | ||||
|                     <td className='table-row text-center'>{currentQuote.quote}</td> | ||||
|                     <td className='table-row text-center'>{currentQuote.author}</td> | ||||
|                     <td className='table-row text-center'> | ||||
|                       <Link href='/users/[name]' as={`/users/${currentQuote.user.name}`}> | ||||
|                       {currentQuote.quote} | ||||
|                     </td> | ||||
|                     <td className='table-row text-center'> | ||||
|                       {currentQuote.author} | ||||
|                     </td> | ||||
|                     <td className='table-row text-center'> | ||||
|                       <Link | ||||
|                         href='/users/[name]' | ||||
|                         as={`/users/${currentQuote.user.name}`} | ||||
|                       > | ||||
|                         <a>{currentQuote.user.name}</a> | ||||
|                       </Link> | ||||
|                     </td> | ||||
| @@ -140,7 +176,11 @@ const QuoteList = () => { | ||||
|                 ) | ||||
|                 // Si c'est le dernier élément | ||||
|                 if (quotesData.rows.length === index + 1) { | ||||
|                   return <tr key={index} ref={lastQuoteRef}>{quoteJSX}</tr> | ||||
|                   return ( | ||||
|                     <tr key={index} ref={lastQuoteRef}> | ||||
|                       {quoteJSX} | ||||
|                     </tr> | ||||
|                   ) | ||||
|                 } | ||||
|                 return <tr key={index}>{quoteJSX}</tr> | ||||
|               })} | ||||
| @@ -158,25 +198,30 @@ const SuggestQuote = () => { | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     const inputStateNew = { ...inputState } | ||||
|     inputStateNew[event.target.name] = event.target.value | ||||
|     setInputState(inputStateNew) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = (event) => { | ||||
|   const handleSubmit = event => { | ||||
|     setIsLoading(true) | ||||
|     event.preventDefault() | ||||
|     const token = user.token | ||||
|     if (isAuth && token != null) { | ||||
|       api.post('/quotes', inputState, { headers: { Authorization: token } }) | ||||
|       api | ||||
|         .post('/quotes', inputState, { headers: { Authorization: token } }) | ||||
|         .then(({ data }) => { | ||||
|           setInputState({ quote: '', author: '' }) | ||||
|           setMessage(`<p class="form-success"><b>Succès:</b> ${data.message}</p>`) | ||||
|           setMessage( | ||||
|             `<p class="form-success"><b>Succès:</b> ${data.message}</p>` | ||||
|           ) | ||||
|           setIsLoading(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>` | ||||
|           ) | ||||
|           setIsLoading(false) | ||||
|         }) | ||||
|     } | ||||
| @@ -185,7 +230,11 @@ const SuggestQuote = () => { | ||||
|   if (!isAuth) { | ||||
|     return ( | ||||
|       <p className='text-center'> | ||||
|                 Vous devez être <Link href='/users/login'><a>connecté</a></Link> pour proposer une citation. | ||||
|         Vous devez être{' '} | ||||
|         <Link href='/users/login'> | ||||
|           <a>connecté</a> | ||||
|         </Link>{' '} | ||||
|         pour proposer une citation. | ||||
|       </p> | ||||
|     ) | ||||
|   } | ||||
| @@ -195,42 +244,68 @@ const SuggestQuote = () => { | ||||
|       <div className='row justify-content-center'> | ||||
|         <div className='col-24 text-center'> | ||||
|           <h2 style={{ margin: 0 }}>Proposer une citation : </h2> | ||||
|           <p style={{ marginTop: '5px' }}>Vous pouvez proposer des citations, et une fois validé elles seront rajoutés à la liste des citations.</p> | ||||
|           <p style={{ marginTop: '5px' }}> | ||||
|             Vous pouvez proposer des citations, et une fois validé elles seront | ||||
|             rajoutés à la liste des citations. | ||||
|           </p> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div style={{ marginBottom: '40px' }} className='row'> | ||||
|         <div className='col-24'> | ||||
|           <form onSubmit={handleSubmit}> | ||||
|             <div className='form-group'> | ||||
|               <label htmlFor='quote' className='form-label'>Citation :</label> | ||||
|               <textarea value={inputState.quote} onChange={handleChange} style={{ height: 'auto' }} id='quote' name='quote' type='text' className='form-control' rows='4' placeholder='La citation...' /> | ||||
|               <label htmlFor='quote' className='form-label'> | ||||
|                 Citation : | ||||
|               </label> | ||||
|               <textarea | ||||
|                 value={inputState.quote} | ||||
|                 onChange={handleChange} | ||||
|                 style={{ height: 'auto' }} | ||||
|                 id='quote' | ||||
|                 name='quote' | ||||
|                 type='text' | ||||
|                 className='form-control' | ||||
|                 rows='4' | ||||
|                 placeholder='La citation...' | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <div className='form-group'> | ||||
|               <label htmlFor='author' className='form-label'>Auteur :</label> | ||||
|               <input value={inputState.author} onChange={handleChange} name='author' id='author' type='text' className='form-control' placeholder="L'auteur de la citation..." /> | ||||
|               <label htmlFor='author' className='form-label'> | ||||
|                 Auteur : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={inputState.author} | ||||
|                 onChange={handleChange} | ||||
|                 name='author' | ||||
|                 id='author' | ||||
|                 type='text' | ||||
|                 className='form-control' | ||||
|                 placeholder="L'auteur de la citation..." | ||||
|               /> | ||||
|             </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> | ||||
|       </div> | ||||
|       <div className='form-result text-center'> | ||||
|         { | ||||
|           (isLoading) | ||||
|             ? <Loader /> | ||||
|             : htmlParser(message) | ||||
|         } | ||||
|         {isLoading ? <Loader /> : htmlParser(message)} | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
| const FunctionTabManager = props => { | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|     <FunctionTabs | ||||
|       setSlideIndex={props.setSlideIndex} | ||||
|       slideIndex={props.slideIndex} | ||||
|     > | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <GenerateQuote /> | ||||
|       </div> | ||||
| @@ -250,17 +325,24 @@ const FunctionTabManager = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const randomQuote = (props) => ( | ||||
| const randomQuote = props => ( | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
|     tabNames={['⚙️ Utilisation', '📜 Liste', '✒️ Proposer', '📝 Article', '📬 Commentaires']} | ||||
|     tabNames={[ | ||||
|       '⚙️ Utilisation', | ||||
|       '📜 Liste', | ||||
|       '✒️ Proposer', | ||||
|       '📝 Article', | ||||
|       '📬 Commentaires' | ||||
|     ]} | ||||
|   /> | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   return api.get('/functions/randomQuote') | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get('/functions/randomQuote') | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -29,14 +29,14 @@ const PlayRightPrice = () => { | ||||
|     setIsLoadingProduct(false) | ||||
|   } | ||||
|  | ||||
|   const handleChange = (event) => { | ||||
|   const handleChange = event => { | ||||
|     setEnteredPrice(event.target.value) | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async (event) => { | ||||
|   const handleSubmit = async event => { | ||||
|     event.preventDefault() | ||||
|     const objectTry = {} | ||||
|     const guessedPrice = Number((enteredPrice).replace(',', '.').replace(' ', '')) | ||||
|     const guessedPrice = Number(enteredPrice.replace(',', '.').replace(' ', '')) | ||||
|     if (!isNaN(guessedPrice)) { | ||||
|       objectTry.guessedPrice = guessedPrice | ||||
|       objectTry.numberTry = attemptsArray.length + 1 | ||||
| @@ -54,80 +54,114 @@ const PlayRightPrice = () => { | ||||
|  | ||||
|   return ( | ||||
|     <div className='container-fluid'> | ||||
|       { | ||||
|         (!isPlaying) | ||||
|           ? ( | ||||
|             <div className='row justify-content-center'> | ||||
|               <div className='form-group text-center'> | ||||
|                 <button onClick={handlePlaying} type='submit' className='btn btn-dark'>Jouer</button> | ||||
|               </div> | ||||
|       {!isPlaying ? ( | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='form-group text-center'> | ||||
|             <button | ||||
|               onClick={handlePlaying} | ||||
|               type='submit' | ||||
|               className='btn btn-dark' | ||||
|             > | ||||
|               Jouer | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|       ) : isLoadingProduct ? ( | ||||
|         <div className='row justify-content-center'> | ||||
|           <Loader /> | ||||
|         </div> | ||||
|       ) : ( | ||||
|         <> | ||||
|           <div className='row justify-content-center'> | ||||
|             <div | ||||
|               style={{ marginBottom: '20px' }} | ||||
|               className='col-24 text-center' | ||||
|             > | ||||
|               <h4>{productToGuess.name}</h4> | ||||
|               <img | ||||
|                 src={productToGuess.image} | ||||
|                 alt={productToGuess.name} | ||||
|                 className='Product__image' | ||||
|               /> | ||||
|             </div> | ||||
|           ) | ||||
|           : (isLoadingProduct) | ||||
|             ? ( | ||||
|               <div className='row justify-content-center'> | ||||
|                 <Loader /> | ||||
|               </div> | ||||
|             ) | ||||
|             : ( | ||||
|               <> | ||||
|                 <div className='row justify-content-center'> | ||||
|                   <div style={{ marginBottom: '20px' }} className='col-24 text-center'> | ||||
|                     <h4>{productToGuess.name}</h4> | ||||
|                     <img src={productToGuess.image} alt={productToGuess.name} className='Product__image' /> | ||||
|           </div> | ||||
|  | ||||
|           <div className='row justify-content-center'> | ||||
|             <div style={{ marginBottom: '25px' }} className='col-24'> | ||||
|               {attemptsArray.length > 0 && | ||||
|               attemptsArray[0].message === | ||||
|                 'Bravo, vous avez trouvé le juste prix !' ? ( | ||||
|                   <div className='form-group text-center'> | ||||
|                     <button | ||||
|                       onClick={handlePlaying} | ||||
|                       type='submit' | ||||
|                       className='btn btn-dark' | ||||
|                     > | ||||
|                     Rejouer ? | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </div> | ||||
|                 ) : ( | ||||
|                   <form onSubmit={handleSubmit}> | ||||
|                     <div className='text-center'> | ||||
|                       <input | ||||
|                         value={enteredPrice} | ||||
|                         onChange={handleChange} | ||||
|                         name='enteredPrice' | ||||
|                         id='enteredPrice' | ||||
|                         type='number' | ||||
|                         step='0.01' | ||||
|                         className='form-control' | ||||
|                         autoComplete='off' | ||||
|                         placeholder='Devinez le prix (prix à virgule possible!)' | ||||
|                       /> | ||||
|                     </div> | ||||
|  | ||||
|                 <div className='row justify-content-center'> | ||||
|                   <div style={{ marginBottom: '25px' }} className='col-24'> | ||||
|                     {((attemptsArray.length > 0) && attemptsArray[0].message === 'Bravo, vous avez trouvé le juste prix !') | ||||
|                       ? ( | ||||
|                         <div className='form-group text-center'> | ||||
|                           <button onClick={handlePlaying} type='submit' className='btn btn-dark'>Rejouer ?</button> | ||||
|                         </div> | ||||
|                       ) | ||||
|                       : ( | ||||
|                         <form onSubmit={handleSubmit}> | ||||
|                           <div className='text-center'> | ||||
|                             <input value={enteredPrice} onChange={handleChange} name='enteredPrice' id='enteredPrice' type='number' step='0.01' className='form-control' autoComplete='off' placeholder='Devinez le prix (prix à virgule possible!)' /> | ||||
|                           </div> | ||||
|                     <div className='form-group text-center'> | ||||
|                       <button type='submit' className='btn btn-dark'> | ||||
|                       Deviner | ||||
|                       </button> | ||||
|                     </div> | ||||
|                   </form> | ||||
|                 )} | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|                           <div className='form-group text-center'> | ||||
|                             <button type='submit' className='btn btn-dark'>Deviner</button> | ||||
|                           </div> | ||||
|                         </form> | ||||
|                       )} | ||||
|                   </div> | ||||
|           <div | ||||
|             style={{ marginBottom: '30px' }} | ||||
|             className='row justify-content-center' | ||||
|           > | ||||
|             {attemptsArray.map((attempt, index) => { | ||||
|               const { message } = attempt | ||||
|               let priceResultClass | ||||
|               if (message === "C'est moins !") { | ||||
|                 priceResultClass = 'Price__result-moins' | ||||
|               } else if (message === "C'est plus !") { | ||||
|                 priceResultClass = 'Price__result-plus' | ||||
|               } else { | ||||
|                 priceResultClass = 'Price__result-success' | ||||
|               } | ||||
|               return ( | ||||
|                 <div | ||||
|                   key={index} | ||||
|                   className={`col-24 Price__result ${priceResultClass}`} | ||||
|                 > | ||||
|                   # {attempt.numberTry} ({attempt.guessedPrice}) {message} | ||||
|                 </div> | ||||
|  | ||||
|                 <div style={{ marginBottom: '30px' }} className='row justify-content-center'> | ||||
|                   {attemptsArray.map((attempt, index) => { | ||||
|                     const { message } = attempt | ||||
|                     let priceResultClass | ||||
|                     if (message === "C'est moins !") { | ||||
|                       priceResultClass = 'Price__result-moins' | ||||
|                     } else if (message === "C'est plus !") { | ||||
|                       priceResultClass = 'Price__result-plus' | ||||
|                     } else { | ||||
|                       priceResultClass = 'Price__result-success' | ||||
|                     } | ||||
|                     return ( | ||||
|                       <div key={index} className={`col-24 Price__result ${priceResultClass}`}> | ||||
|                                       # {attempt.numberTry} ({attempt.guessedPrice}) {message} | ||||
|                       </div> | ||||
|                     ) | ||||
|                   })} | ||||
|                 </div> | ||||
|               </> | ||||
|             ) | ||||
|       } | ||||
|               ) | ||||
|             })} | ||||
|           </div> | ||||
|         </> | ||||
|       )} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
| const FunctionTabManager = props => { | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|     <FunctionTabs | ||||
|       setSlideIndex={props.setSlideIndex} | ||||
|       slideIndex={props.slideIndex} | ||||
|     > | ||||
|       <div style={{ marginTop: '10px' }}> | ||||
|         <PlayRightPrice /> | ||||
|       </div> | ||||
| @@ -141,7 +175,7 @@ const FunctionTabManager = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const rightPrice = (props) => ( | ||||
| const rightPrice = props => ( | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
| @@ -150,8 +184,9 @@ const rightPrice = (props) => ( | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   return api.get('/functions/rightPrice') | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get('/functions/rightPrice') | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,9 @@ const ManageToDo = () => { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const getTasks = async () => { | ||||
|       const { data } = await api.get('/tasks', { headers: { Authorization: user.token } }) | ||||
|       const { data } = await api.get('/tasks', { | ||||
|         headers: { Authorization: user.token } | ||||
|       }) | ||||
|       setTasks(data) | ||||
|     } | ||||
|     if (isAuth && user.token != null) { | ||||
| @@ -27,16 +29,18 @@ const ManageToDo = () => { | ||||
|     } | ||||
|   }, [isAuth]) | ||||
|  | ||||
|   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 => { | ||||
|     event.preventDefault() | ||||
|     try { | ||||
|       const { data } = await api.post('/tasks', inputState, { headers: { Authorization: user.token } }) | ||||
|       const { data } = await api.post('/tasks', inputState, { | ||||
|         headers: { Authorization: user.token } | ||||
|       }) | ||||
|       const newTasks = [...tasks] | ||||
|       newTasks.push(data) | ||||
|       setTasks(newTasks) | ||||
| @@ -47,7 +51,9 @@ const ManageToDo = () => { | ||||
|   const handleRemoveTask = async (id, index) => { | ||||
|     const newTasks = [...tasks] | ||||
|     try { | ||||
|       await api.delete(`/tasks/${id}`, { headers: { Authorization: user.token } }) | ||||
|       await api.delete(`/tasks/${id}`, { | ||||
|         headers: { Authorization: user.token } | ||||
|       }) | ||||
|       newTasks.splice(index, 1) | ||||
|       setTasks(newTasks) | ||||
|     } catch {} | ||||
| @@ -55,7 +61,11 @@ const ManageToDo = () => { | ||||
|  | ||||
|   const handleEditTask = async (id, index, isCompleted) => { | ||||
|     try { | ||||
|       await api.put(`/tasks/${id}`, { isCompleted: !isCompleted }, { headers: { Authorization: user.token } }) | ||||
|       await api.put( | ||||
|         `/tasks/${id}`, | ||||
|         { isCompleted: !isCompleted }, | ||||
|         { headers: { Authorization: user.token } } | ||||
|       ) | ||||
|       const newTasks = [...tasks] | ||||
|       const taskObject = newTasks[index] | ||||
|       taskObject.isCompleted = !isCompleted | ||||
| @@ -66,7 +76,11 @@ const ManageToDo = () => { | ||||
|   if (!isAuth) { | ||||
|     return ( | ||||
|       <p className='text-center'> | ||||
|                 Vous devez être <Link href='/users/login'><a>connecté</a></Link> pour gérer des "tâches à faire". | ||||
|         Vous devez être{' '} | ||||
|         <Link href='/users/login'> | ||||
|           <a>connecté</a> | ||||
|         </Link>{' '} | ||||
|         pour gérer des "tâches à faire". | ||||
|       </p> | ||||
|     ) | ||||
|   } | ||||
| @@ -77,31 +91,62 @@ const ManageToDo = () => { | ||||
|         <div className='col-24'> | ||||
|           <form onSubmit={handleSubmit}> | ||||
|             <div className='text-center'> | ||||
|               <label htmlFor='task' className='form-label'>Ajouter une tâche à faire :</label> | ||||
|               <input value={inputState.task} onChange={handleChange} name='task' id='task' type='text' className='form-control' placeholder='(e.g : Apprendre à coder)' /> | ||||
|               <label htmlFor='task' className='form-label'> | ||||
|                 Ajouter une tâche à faire : | ||||
|               </label> | ||||
|               <input | ||||
|                 value={inputState.task} | ||||
|                 onChange={handleChange} | ||||
|                 name='task' | ||||
|                 id='task' | ||||
|                 type='text' | ||||
|                 className='form-control' | ||||
|                 placeholder='(e.g : Apprendre à coder)' | ||||
|               /> | ||||
|             </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> | ||||
|       </div> | ||||
|  | ||||
|       {(tasks.length > 0) && | ||||
|       {tasks.length > 0 && ( | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24 ManageToDo__container'> | ||||
|             <ul className='ManageToDo__list'> | ||||
|               {tasks.map((task, index) => { | ||||
|                 return ( | ||||
|                   <li key={task.id} className={`ManageToDo__list-item ${(task.isCompleted) ? 'isCompleted' : ''}`}> | ||||
|                     <span className='ManageToDo__list-item-span'>{task.task}</span> | ||||
|                   <li | ||||
|                     key={task.id} | ||||
|                     className={`ManageToDo__list-item ${ | ||||
|                       task.isCompleted ? 'isCompleted' : '' | ||||
|                     }`} | ||||
|                   > | ||||
|                     <span className='ManageToDo__list-item-span'> | ||||
|                       {task.task} | ||||
|                     </span> | ||||
|                     <div> | ||||
|                       <button className='ManageToDo__task-btn' title='Supprimer de la liste' onClick={() => handleRemoveTask(task.id, index)}> | ||||
|                       <button | ||||
|                         className='ManageToDo__task-btn' | ||||
|                         title='Supprimer de la liste' | ||||
|                         onClick={() => handleRemoveTask(task.id, index)} | ||||
|                       > | ||||
|                         <FontAwesomeIcon icon={faTrash} /> | ||||
|                       </button> | ||||
|                       <button className='ManageToDo__task-btn' onClick={() => handleEditTask(task.id, index, task.isCompleted)}> | ||||
|                         <FontAwesomeIcon {...(task.isCompleted) ? { icon: faTimes } : { icon: faCheck }} /> | ||||
|                       <button | ||||
|                         className='ManageToDo__task-btn' | ||||
|                         onClick={() => | ||||
|                           handleEditTask(task.id, index, task.isCompleted)} | ||||
|                       > | ||||
|                         <FontAwesomeIcon | ||||
|                           {...(task.isCompleted | ||||
|                             ? { icon: faTimes } | ||||
|                             : { icon: faCheck })} | ||||
|                         /> | ||||
|                       </button> | ||||
|                     </div> | ||||
|                   </li> | ||||
| @@ -109,14 +154,18 @@ const ManageToDo = () => { | ||||
|               })} | ||||
|             </ul> | ||||
|           </div> | ||||
|         </div>} | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const FunctionTabManager = (props) => { | ||||
| const FunctionTabManager = props => { | ||||
|   return ( | ||||
|     <FunctionTabs setSlideIndex={props.setSlideIndex} slideIndex={props.slideIndex}> | ||||
|     <FunctionTabs | ||||
|       setSlideIndex={props.setSlideIndex} | ||||
|       slideIndex={props.slideIndex} | ||||
|     > | ||||
|       <div className='FunctionComponent__slide'> | ||||
|         <ManageToDo /> | ||||
|       </div> | ||||
| @@ -130,7 +179,7 @@ const FunctionTabManager = (props) => { | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const toDoList = (props) => ( | ||||
| const toDoList = props => ( | ||||
|   <FunctionPage | ||||
|     FunctionTabManager={FunctionTabManager} | ||||
|     {...props} | ||||
| @@ -139,8 +188,9 @@ const toDoList = (props) => ( | ||||
| ) | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   return api.get('/functions/toDoList') | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get('/functions/toDoList') | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,26 +20,28 @@ const Home = () => { | ||||
|     <> | ||||
|       <HeadTag /> | ||||
|       <div className='Home__container container-fluid text-center'> | ||||
|  | ||||
|         <AutoPlaySwipeableViews enableMouseEvents interval={15000}> | ||||
|  | ||||
|           {/* Slide 1 */} | ||||
|           <div className='row align-items-center justify-content-center'> | ||||
|             <div className='col-24'> | ||||
|               <h1 className='title-important'>FunctionProject</h1> | ||||
|               <p className='Home__description'> | ||||
|                                 Apprenez la programmation grâce à l'apprentissage par projet alias fonction (en <span className='important'>JavaScript</span>). | ||||
|                 Apprenez la programmation grâce à l'apprentissage par projet | ||||
|                 alias fonction (en <span className='important'>JavaScript</span> | ||||
|                 ). | ||||
|                 <br /> | ||||
|                 <Link href='/about'> | ||||
|                   <a>En savoir plus ? (à-propos)</a> | ||||
|                 </Link> | ||||
|                 <br /> | ||||
|                                 Découvrez la liste des fonctions disponibles : | ||||
|                 Découvrez la liste des fonctions disponibles : | ||||
|               </p> | ||||
|             </div> | ||||
|             <div className='col-24 Home__logo-spin'> | ||||
|               <Link href='/functions'> | ||||
|                 <a><Loader width='100%' height='13em' speed='5s' /></a> | ||||
|                 <a> | ||||
|                   <Loader width='100%' height='13em' speed='5s' /> | ||||
|                 </a> | ||||
|               </Link> | ||||
|             </div> | ||||
|           </div> | ||||
| @@ -49,18 +51,27 @@ const Home = () => { | ||||
|             <div className='col-24'> | ||||
|               <h1 className='title-important'>Code Source</h1> | ||||
|               <p className='Home__description'> | ||||
|                                 Le partage est essentiel afin de progresser. <br /> | ||||
|                                 Par conséquent chaque fonction a un article expliquant comment elle fonctionne et <br /> | ||||
|                                 le code source du projet est disponible sur mon profil GitHub : | ||||
|                 Le partage est essentiel afin de progresser. <br /> | ||||
|                 Par conséquent chaque fonction a un article expliquant comment | ||||
|                 elle fonctionne et <br /> | ||||
|                 le code source du projet est disponible sur mon profil GitHub : | ||||
|               </p> | ||||
|             </div> | ||||
|             <div className='col-24'> | ||||
|               <a target='_blank' rel='noopener noreferrer' href='https://github.com/Divlo/FunctionProject'><img className='Home__image-width' src='/images/GitHub.png' alt='GitHub' /></a> | ||||
|               <a | ||||
|                 target='_blank' | ||||
|                 rel='noopener noreferrer' | ||||
|                 href='https://github.com/Divlo/FunctionProject' | ||||
|               > | ||||
|                 <img | ||||
|                   className='Home__image-width' | ||||
|                   src='/images/GitHub.png' | ||||
|                   alt='GitHub' | ||||
|                 /> | ||||
|               </a> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|         </AutoPlaySwipeableViews> | ||||
|  | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
|   | ||||
| @@ -12,11 +12,10 @@ import htmlParser from 'html-react-parser' | ||||
| import Loader from '../../components/Loader' | ||||
| import ReactMarkdown from 'react-markdown' | ||||
| import CodeBlock from '../../components/CodeBlock' | ||||
| import api from '../../utils/api' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import api, { API_URL } from '../../utils/api' | ||||
| import '../../public/css/pages/profile.css' | ||||
|  | ||||
| const Profile = (props) => { | ||||
| const Profile = props => { | ||||
|   const { isAuth, user, logoutUser } = useContext(UserContext) | ||||
|   const [isOpen, setIsOpen] = useState(false) | ||||
|   const [inputState, setInputState] = useState({}) | ||||
| @@ -25,7 +24,12 @@ const Profile = (props) => { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isAuth) { | ||||
|       setInputState({ name: user.name, email: user.email, biography: user.biography, isPublicEmail: user.isPublicEmail }) | ||||
|       setInputState({ | ||||
|         name: user.name, | ||||
|         email: user.email, | ||||
|         biography: user.biography, | ||||
|         isPublicEmail: user.isPublicEmail | ||||
|       }) | ||||
|     } | ||||
|   }, [isAuth]) | ||||
|  | ||||
| @@ -33,11 +37,16 @@ const Profile = (props) => { | ||||
|  | ||||
|   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() | ||||
|     const token = user.token | ||||
|     if (isAuth && token != null) { | ||||
| @@ -49,14 +58,17 @@ const Profile = (props) => { | ||||
|       formData.append('isPublicEmail', inputState.isPublicEmail) | ||||
|       formData.append('logo', inputState.logo) | ||||
|  | ||||
|       api.put('/users/', formData, { headers: { Authorization: token } }) | ||||
|       api | ||||
|         .put('/users/', formData, { headers: { Authorization: token } }) | ||||
|         .then(() => { | ||||
|           setIsLoading(false) | ||||
|           logoutUser() | ||||
|           redirect({}, '/users/login?isSuccessEdit=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) | ||||
|         }) | ||||
|     } | ||||
| @@ -64,174 +76,316 @@ const Profile = (props) => { | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag title={`${props.name} - FunctionProject`} description={`Profil utilisateur de ${props.name}. ${(props.biography != null) ? props.biography : ''}`} /> | ||||
|       <HeadTag | ||||
|         title={`${props.name} - FunctionProject`} | ||||
|         description={`Profil utilisateur de ${props.name}. ${ | ||||
|           props.biography != null ? props.biography : '' | ||||
|         }`} | ||||
|       /> | ||||
|  | ||||
|       {/* Édition du profil */} | ||||
|       {(isOpen) | ||||
|         ? ( | ||||
|           <Modal toggleModal={toggleModal}> | ||||
|             <div className='Profile__container container-fluid'> | ||||
|               <div className='Profile__row 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 comme à l'inscription (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' /> | ||||
|                     </div> | ||||
|  | ||||
|                     <div className='form-group'> | ||||
|                       <label className='form-label' htmlFor='logo'>Logo <em>(400x400 recommandé)</em> :</label> | ||||
|                       <p style={{ margin: 0 }}><em>Si aucun fichier est choisi, le logo ne sera pas modifié.</em></p> | ||||
|                       <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) | ||||
|                     } | ||||
|       {isOpen ? ( | ||||
|         <Modal toggleModal={toggleModal}> | ||||
|           <div className='Profile__container container-fluid'> | ||||
|             <div className='Profile__row 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 comme à l'inscription (vérifier vos | ||||
|                         emails). | ||||
|                       </em> | ||||
|                     </p> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </Modal> | ||||
|         ) | ||||
|         : ( | ||||
|           <div className='container-fluid Profile__container'> | ||||
|             <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 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='col-24 text-center'> | ||||
|                     {(props.biography != null) && <p>{props.biography}</p>} | ||||
|                     {(props.email != null) && <p><span className='important'>Email :</span> {props.email}</p>} | ||||
|                     <p><span className='important'>Date de création du compte :</span> {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)}</p> | ||||
|                   <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> | ||||
|  | ||||
|                   {(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 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' | ||||
|                     /> | ||||
|                   </div> | ||||
|  | ||||
|                   <div className='form-group'> | ||||
|                     <label className='form-label' htmlFor='logo'> | ||||
|                       Logo <em>(400x400 recommandé)</em> : | ||||
|                     </label> | ||||
|                     <p style={{ margin: 0 }}> | ||||
|                       <em> | ||||
|                         Si aucun fichier est choisi, le logo ne sera pas | ||||
|                         modifié. | ||||
|                       </em> | ||||
|                     </p> | ||||
|                     <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> | ||||
|  | ||||
|             {(props.favoritesArray.length > 0) && | ||||
|           </div> | ||||
|         </Modal> | ||||
|       ) : ( | ||||
|         <div className='container-fluid Profile__container'> | ||||
|           <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'> | ||||
|                   <h2>Dernières fonctions ajoutées aux <span className='important'>favoris</span> :</h2> | ||||
|                   <img | ||||
|                     className='Profile__logo' | ||||
|                     src={API_URL + props.logo} | ||||
|                     alt={props.name} | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div className='col-24'> | ||||
|                   <div className='row justify-content-center'> | ||||
|                     {props.favoritesArray.map((favorite) => { | ||||
|  | ||||
|                 <div className='col-24 text-center'> | ||||
|                   {props.biography != null && <p>{props.biography}</p>} | ||||
|                   {props.email != null && ( | ||||
|                     <p> | ||||
|                       <span className='important'>Email :</span> {props.email} | ||||
|                     </p> | ||||
|                   )} | ||||
|                   <p> | ||||
|                     <span className='important'> | ||||
|                       Date de création du compte : | ||||
|                     </span>{' '} | ||||
|                     {date.format( | ||||
|                       new Date(props.createdAt), | ||||
|                       'DD/MM/YYYY à HH:mm', | ||||
|                       true | ||||
|                     )} | ||||
|                   </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> | ||||
|  | ||||
|           {props.favoritesArray.length > 0 && ( | ||||
|             <div className='row justify-content-center'> | ||||
|               <div className='col-24 text-center'> | ||||
|                 <h2> | ||||
|                   Dernières fonctions ajoutées aux{' '} | ||||
|                   <span className='important'>favoris</span> : | ||||
|                 </h2> | ||||
|               </div> | ||||
|               <div className='col-24'> | ||||
|                 <div className='row justify-content-center'> | ||||
|                   {props.favoritesArray.map(favorite => { | ||||
|                     return <FunctionCard key={favorite.id} {...favorite} /> | ||||
|                   })} | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
|           {props.commentsArray.length > 0 && ( | ||||
|             <div className='row justify-content-center'> | ||||
|               <div className='col-24 text-center'> | ||||
|                 <h2> | ||||
|                   Derniers <span className='important'>commentaires</span> : | ||||
|                 </h2> | ||||
|               </div> | ||||
|               <div className='col-24'> | ||||
|                 {props.commentsArray.map(comment => ( | ||||
|                   <div | ||||
|                     key={comment.id} | ||||
|                     className='row Profile__row Profile__comment' | ||||
|                   > | ||||
|                     <div className='col-20'> | ||||
|                       <p style={{ textAlign: 'center', marginTop: '30px' }}> | ||||
|                         Posté sur la fonction  | ||||
|                         <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 | ||||
|                         )} | ||||
|                       </p> | ||||
|                       <ReactMarkdown | ||||
|                         source={comment.message} | ||||
|                         renderers={{ code: CodeBlock }} | ||||
|                       /> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                 ))} | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
|           {props.quotesArray.length > 0 && ( | ||||
|             <div className='row justify-content-center'> | ||||
|               <div className='col-24 text-center'> | ||||
|                 <h2> | ||||
|                   Dernières <span className='important'>citations</span>{' '} | ||||
|                   proposées (et validées) : | ||||
|                 </h2> | ||||
|                 <p> | ||||
|                   Citations pour la fonction{' '} | ||||
|                   <Link href='/functions/randomQuote'> | ||||
|                     <a>Générateur de citations</a> | ||||
|                   </Link> | ||||
|                   . | ||||
|                 </p> | ||||
|               </div> | ||||
|               <div className='col-24 table-column'> | ||||
|                 <table style={{ marginBottom: '50px' }}> | ||||
|                   <thead> | ||||
|                     <tr> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         Citation/Proverbe | ||||
|                       </th> | ||||
|                       <th className='table-row' scope='col'> | ||||
|                         Auteur | ||||
|                       </th> | ||||
|                     </tr> | ||||
|                   </thead> | ||||
|                   <tbody> | ||||
|                     {props.quotesArray.map((currentQuote, index) => { | ||||
|                       return ( | ||||
|                         <FunctionCard key={favorite.id} {...favorite} /> | ||||
|                         <tr key={index}> | ||||
|                           <td className='table-row text-center'> | ||||
|                             {currentQuote.quote} | ||||
|                           </td> | ||||
|                           <td className='table-row text-center'> | ||||
|                             {currentQuote.author} | ||||
|                           </td> | ||||
|                         </tr> | ||||
|                       ) | ||||
|                     })} | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div>} | ||||
|  | ||||
|             {(props.commentsArray.length > 0) && | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='col-24 text-center'> | ||||
|                   <h2>Derniers <span className='important'>commentaires</span> :</h2> | ||||
|                 </div> | ||||
|                 <div className='col-24'> | ||||
|                   {props.commentsArray.map((comment) => ( | ||||
|                     <div key={comment.id} className='row Profile__row Profile__comment'> | ||||
|                       <div className='col-20'> | ||||
|                         <p style={{ textAlign: 'center', marginTop: '30px' }}> | ||||
|                                                       Posté sur la fonction  | ||||
|                           <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)} | ||||
|                         </p> | ||||
|                         <ReactMarkdown source={comment.message} renderers={{ code: CodeBlock }} /> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   ))} | ||||
|                 </div> | ||||
|               </div>} | ||||
|  | ||||
|             {(props.quotesArray.length > 0) && | ||||
|               <div className='row justify-content-center'> | ||||
|                 <div className='col-24 text-center'> | ||||
|                   <h2>Dernières <span className='important'>citations</span> proposées (et validées) :</h2> | ||||
|                   <p>Citations pour la fonction <Link href='/functions/randomQuote'><a>Générateur de citations</a></Link>.</p> | ||||
|                 </div> | ||||
|                 <div className='col-24 table-column'> | ||||
|                   <table style={{ marginBottom: '50px' }}> | ||||
|                     <thead> | ||||
|                       <tr> | ||||
|                         <th className='table-row' scope='col'>Citation/Proverbe</th> | ||||
|                         <th className='table-row' scope='col'>Auteur</th> | ||||
|                       </tr> | ||||
|                     </thead> | ||||
|                     <tbody> | ||||
|                       {props.quotesArray.map((currentQuote, index) => { | ||||
|                         return ( | ||||
|                           <tr key={index}> | ||||
|                             <td className='table-row text-center'>{currentQuote.quote}</td> | ||||
|                             <td className='table-row text-center'>{currentQuote.author}</td> | ||||
|                           </tr> | ||||
|                         ) | ||||
|                       })} | ||||
|                     </tbody> | ||||
|                   </table> | ||||
|                 </div> | ||||
|               </div>} | ||||
|           </div> | ||||
|         )} | ||||
|                   </tbody> | ||||
|                 </table> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|         </div> | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export async function getServerSideProps (context) { | ||||
|   const { name } = context.params | ||||
|   return api.get(`/users/${name}`) | ||||
|     .then((response) => ({ props: response.data })) | ||||
|   return api | ||||
|     .get(`/users/${name}`) | ||||
|     .then(response => ({ props: response.data })) | ||||
|     .catch(() => redirect(context, '/404')) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import axios from 'axios' | ||||
| import { API_URL } from './config/config' | ||||
|  | ||||
| export const API_URL = process.env.NEXT_PUBLIC_API_URL | ||||
|  | ||||
| const api = axios.create({ | ||||
|   baseURL: API_URL, | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| export const API_URL = process.env.NEXT_PUBLIC_API_URL | ||||
		Reference in New Issue
	
	Block a user