From 39a332a8670e38477b1817ac398f896ba84f87f4 Mon Sep 17 00:00:00 2001 From: Divlo Date: Fri, 10 Apr 2020 20:20:07 +0200 Subject: [PATCH] frontend: Voir et Poster des commentaires --- api/controllers/comments.js | 7 +- .../components/CommentCard/CommentCard.css | 33 +++++ website/components/CommentCard/CommentCard.js | 32 +++++ .../components/FunctionCard/FunctionCard.js | 2 +- .../FunctionComments/FunctionComments.css | 7 ++ .../FunctionComments/FunctionComments.js | 113 ++++++++++++++++++ website/components/FunctionForm.js | 6 +- website/components/FunctionTabManager.js | 30 +++++ website/contexts/FunctionTabsContext.js | 2 +- website/pages/functions/[slug].js | 17 +-- website/pages/functions/index.js | 2 +- website/pages/profile/[name].js | 2 +- website/public/css/general.css | 3 + 13 files changed, 232 insertions(+), 24 deletions(-) create mode 100644 website/components/CommentCard/CommentCard.css create mode 100644 website/components/CommentCard/CommentCard.js create mode 100644 website/components/FunctionComments/FunctionComments.css create mode 100644 website/components/FunctionComments/FunctionComments.js create mode 100644 website/components/FunctionTabManager.js diff --git a/api/controllers/comments.js b/api/controllers/comments.js index af3a14a..98f77d9 100644 --- a/api/controllers/comments.js +++ b/api/controllers/comments.js @@ -38,8 +38,11 @@ exports.postCommentsByFunctionId = async (req, res, next) => { if (!resultFunction) { return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); } - await Comments.create({ message, userId: req.userId, functionId }); - return res.status(201).json({ result: "Le commentaire a bien été ajouté!" }); + if (!message) { + return errorHandling(next, { message: "Vous ne pouvez pas poster de commentaire vide.", statusCode: 400 }); + } + const comment = await Comments.create({ message, userId: req.userId, functionId }); + return res.status(201).json(comment); } catch (error) { console.log(error); return errorHandling(next, serverError); diff --git a/website/components/CommentCard/CommentCard.css b/website/components/CommentCard/CommentCard.css new file mode 100644 index 0000000..8fc2115 --- /dev/null +++ b/website/components/CommentCard/CommentCard.css @@ -0,0 +1,33 @@ +.CommentCard { + display: flex; + flex-direction: column; + word-wrap: break-word; + box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); + border: 1px solid black; + border-radius: .7em; + margin: 15px 0 15px 0; +} +.CommentCard__container { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + padding: 20px; +} +.CommentCard__user-logo { + border-radius: 50%; + object-fit: cover; + width: 50px; + height: 50px; + cursor: pointer; +} +.CommentCard__message-info { + display: flex; + align-items: center; + margin-left: 10px; + font-size: 16px; +} +.CommentCard__message { + line-height: 1.8; + margin: 15px 0 0 0; +} \ No newline at end of file diff --git a/website/components/CommentCard/CommentCard.js b/website/components/CommentCard/CommentCard.js new file mode 100644 index 0000000..2947c8b --- /dev/null +++ b/website/components/CommentCard/CommentCard.js @@ -0,0 +1,32 @@ +import Link from 'next/link'; +import { forwardRef } from 'react'; +import { API_URL } from '../../utils/config'; +import date from 'date-and-time'; +import './CommentCard.css'; + +const CommentCard = forwardRef((props, ref) => { + return ( +
+
+
+ + {props.user.name} + + + + {props.user.name} + +  - {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)} + +
+
+

+ {props.message} +

+
+
+
+ ); +}); + +export default CommentCard; \ No newline at end of file diff --git a/website/components/FunctionCard/FunctionCard.js b/website/components/FunctionCard/FunctionCard.js index 71dfdac..1719872 100644 --- a/website/components/FunctionCard/FunctionCard.js +++ b/website/components/FunctionCard/FunctionCard.js @@ -38,6 +38,6 @@ const FunctionCard = forwardRef((props, ref) => { ); -}) +}); export default FunctionCard; \ No newline at end of file diff --git a/website/components/FunctionComments/FunctionComments.css b/website/components/FunctionComments/FunctionComments.css new file mode 100644 index 0000000..51d4139 --- /dev/null +++ b/website/components/FunctionComments/FunctionComments.css @@ -0,0 +1,7 @@ +.FunctionComments__row { + margin-bottom: 20px; +} +.FunctionComments__textarea { + height: auto; + resize: vertical; +} \ No newline at end of file diff --git a/website/components/FunctionComments/FunctionComments.js b/website/components/FunctionComments/FunctionComments.js new file mode 100644 index 0000000..2ff8ad6 --- /dev/null +++ b/website/components/FunctionComments/FunctionComments.js @@ -0,0 +1,113 @@ +import { Fragment, useState, useEffect, useContext, useRef, useCallback } from 'react'; +import Link from 'next/link'; +import { UserContext } from '../../contexts/UserContext'; +import CommentCard from '../CommentCard/CommentCard'; +import Loader from '../Loader'; +import api from '../../utils/api'; +import './FunctionComments.css'; + +const FunctionComments = ({ functionId }) => { + + // State pour poster un commentaire + const { isAuth, user } = useContext(UserContext); + const [inputState, setInputState] = useState({}); + + // State pour afficher les commentaires + const [commentsData, setCommentsData] = useState({ hasMore: true, rows: [] }); + const [isLoadingComments, setLoadingComments] = useState(true); + const [pageComments, setPageComments] = useState(1); + + // Permet la pagination au scroll + const observer = useRef(); + const lastCommentCardRef = useCallback((node) => { + if (isLoadingComments) return; + if (observer.current) observer.current.disconnect(); + observer.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && commentsData.hasMore) { + setPageComments(pageComments + 1); + } + }, { threshold: 1 }); + if (node) observer.current.observe(node); + }, [isLoadingComments, commentsData.hasMore]); + + const getCommentsData = () => { + setLoadingComments(true); + return new Promise(async (next) => { + const result = await api.get(`/comments/${functionId}/?page=${pageComments}&limit=10`); + setLoadingComments(false); + next(result.data); + }); + } + + // Récupère les commentaires si la page change + useEffect(() => { + getCommentsData().then((data) => setCommentsData({ + hasMore: data.hasMore, + rows: [...commentsData.rows, ...data.rows] + })); + }, [pageComments]); + + const handleChange = (event) => { + const inputStateNew = { ...inputState }; + inputStateNew[event.target.name] = event.target.value; + setInputState(inputStateNew); + } + + const handleSubmit = async (event) => { + event.preventDefault(); + const token = user.token; + if (isAuth && token != undefined && inputState.commentPost != undefined) { + try { + const response = await api.post(`/comments/${functionId}`, { message: inputState.commentPost }, { headers: { 'Authorization': token } }); + const comment = { ...response.data, user: { name: user.name, logo: user.logo } }; + setCommentsData({ hasMore: commentsData.hasMore, rows: [comment, ...commentsData.rows] }); + setInputState({}); + } catch { } + } + } + + return ( + +
+
+
+ { + (isAuth) ? +
+
+ + +
+
+ +
+
+ : +

+ Vous devez être connecté pour poster un commentaire. +

+ } +
+
+
+
+ {isLoadingComments && +
+ +
+ } +
+ {commentsData.rows.map((comment, index) => { + // Si c'est le dernier élément + if (commentsData.rows.length === index + 1) { + return ; + } + return ; + })} +
+
+
+ ); +} + +export default FunctionComments; \ No newline at end of file diff --git a/website/components/FunctionForm.js b/website/components/FunctionForm.js index f3f08c1..2d2d142 100644 --- a/website/components/FunctionForm.js +++ b/website/components/FunctionForm.js @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { Fragment, useState } from 'react'; import Loader from './Loader'; import htmlParser from 'html-react-parser'; import api from '../utils/api'; @@ -38,7 +38,7 @@ const FunctionForm = (props) => { ); } return ( -
+
{props.inputArray.map((input, index) => { let inputResult; @@ -69,7 +69,7 @@ const FunctionForm = (props) => { htmlParser(message) }
- + ); } diff --git a/website/components/FunctionTabManager.js b/website/components/FunctionTabManager.js new file mode 100644 index 0000000..c572192 --- /dev/null +++ b/website/components/FunctionTabManager.js @@ -0,0 +1,30 @@ +import FunctionTabs from './FunctionTabs/FunctionTabs'; +import FunctionForm from './FunctionForm'; +import FunctionComments from './FunctionComments/FunctionComments'; + +const FunctionTabManager = (props) => { + if (props.functionInfo.type === "form") { + return ( + +
+ +
+
Article
+
+ +
+
+ ); + } + + return ( + +
Article
+
+ +
+
+ ); +} + +export default FunctionTabManager; \ No newline at end of file diff --git a/website/contexts/FunctionTabsContext.js b/website/contexts/FunctionTabsContext.js index f080886..c12b30a 100644 --- a/website/contexts/FunctionTabsContext.js +++ b/website/contexts/FunctionTabsContext.js @@ -4,7 +4,7 @@ export const FunctionTabsContext = createContext(); function FunctionTabsContextProvider(props) { - const [slideIndex, setSlideIndex] = useState(0); + const [slideIndex, setSlideIndex] = useState(2); return ( diff --git a/website/pages/functions/[slug].js b/website/pages/functions/[slug].js index 15dd8a6..9771df8 100644 --- a/website/pages/functions/[slug].js +++ b/website/pages/functions/[slug].js @@ -2,8 +2,7 @@ import FunctionTabsContextProvider from '../../contexts/FunctionTabsContext'; import HeadTag from '../../components/HeadTag'; import FunctionComponentTop from '../../components/FunctionComponentTop'; import FunctionTabsTop from '../../components/FunctionTabs/FunctionTabsTop'; -import FunctionTabs from '../../components/FunctionTabs/FunctionTabs'; -import FunctionForm from '../../components/FunctionForm'; +import FunctionTabManager from '../../components/FunctionTabManager'; import redirect from '../../utils/redirect'; import api from '../../utils/api'; import { API_URL } from '../../utils/config'; @@ -18,22 +17,10 @@ const FunctionComponent = (props) => { return ( -
- - - - {(props.type !== "article") ? - - : -
Slide 1
- } -
Slide 2
-
Slide 3
-
- +
); diff --git a/website/pages/functions/index.js b/website/pages/functions/index.js index e2bc4a0..36ca75e 100644 --- a/website/pages/functions/index.js +++ b/website/pages/functions/index.js @@ -3,9 +3,9 @@ import { useRouter } from 'next/router'; import HeadTag from '../../components/HeadTag'; import FunctionCard from '../../components/FunctionCard/FunctionCard'; import Loader from '../../components/Loader'; -import '../../public/css/pages/functions.css'; import api from '../../utils/api'; import useAPI from '../../hooks/useAPI'; +import '../../public/css/pages/functions.css'; const Functions = () => { diff --git a/website/pages/profile/[name].js b/website/pages/profile/[name].js index d2ffaac..0b118fc 100644 --- a/website/pages/profile/[name].js +++ b/website/pages/profile/[name].js @@ -52,7 +52,7 @@ const Profile = (props) => { formData.append('isPublicEmail', inputState.isPublicEmail); formData.append('logo', inputState.logo); - api.put('/users/', formData, { headers: { 'Authorization': user.token } }) + api.put('/users/', formData, { headers: { 'Authorization': token } }) .then(() => { setIsLoading(false); logoutUser(); diff --git a/website/public/css/general.css b/website/public/css/general.css index ac5e396..6895f94 100644 --- a/website/public/css/general.css +++ b/website/public/css/general.css @@ -41,6 +41,9 @@ a:hover { justify-content: center; align-items: center; } +div[aria-hidden="true"] > * { + display: none; +} /* LOADING */ .isLoading {