frontend: Voir et Poster des commentaires

This commit is contained in:
Divlo 2020-04-10 20:20:07 +02:00
parent 903590fb08
commit 39a332a867
13 changed files with 232 additions and 24 deletions

View File

@ -38,8 +38,11 @@ exports.postCommentsByFunctionId = async (req, res, next) => {
if (!resultFunction) { if (!resultFunction) {
return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 }); return errorHandling(next, { message: "La fonction n'existe pas.", statusCode: 404 });
} }
await Comments.create({ message, userId: req.userId, functionId }); if (!message) {
return res.status(201).json({ result: "Le commentaire a bien été ajouté!" }); 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) { } catch (error) {
console.log(error); console.log(error);
return errorHandling(next, serverError); return errorHandling(next, serverError);

View File

@ -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;
}

View File

@ -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 (
<div ref={ref} className="CommentCard col-24">
<div className="CommentCard__container">
<div className="row">
<Link href={"/profile/[name]"} as={`/profile/${props.user.name}`}>
<img className="CommentCard__user-logo" src={API_URL + props.user.logo} alt={props.user.name} />
</Link>
<span className="CommentCard__message-info">
<Link href={"/profile/[name]"} as={`/profile/${props.user.name}`}>
<a>{props.user.name}</a>
</Link>
&nbsp;- {date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', true)}
</span>
</div>
<div className="row">
<p className="CommentCard__message">
{props.message}
</p>
</div>
</div>
</div>
);
});
export default CommentCard;

View File

@ -38,6 +38,6 @@ const FunctionCard = forwardRef((props, ref) => {
</div> </div>
</Link> </Link>
); );
}) });
export default FunctionCard; export default FunctionCard;

View File

@ -0,0 +1,7 @@
.FunctionComments__row {
margin-bottom: 20px;
}
.FunctionComments__textarea {
height: auto;
resize: vertical;
}

View File

@ -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 (
<Fragment>
<div className="FunctionComments__post container-fluid">
<div className="row FunctionComments__row">
<div className="col-24">
{
(isAuth) ?
<form onSubmit={handleSubmit}>
<div className="form-group FunctionComments__post-group">
<label className="form-label" htmlFor="commentPost">Ajouter un commentaire :</label>
<textarea className="FunctionComments__textarea form-control" value={inputState.commentPost} onChange={handleChange} name="commentPost" id="commentPost" placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ..."></textarea>
</div>
<div className="form-group" style={{ marginTop: '0.7em' }}>
<button type="submit" className="btn btn-dark">Envoyer</button>
</div>
</form>
:
<p className="text-center">
Vous devez être <Link href={'/login'}><a>connecté</a></Link> pour poster un commentaire.
</p>
}
</div>
</div>
</div>
<div className="container-fluid">
{isLoadingComments &&
<div className="row justify-content-center">
<Loader />
</div>
}
<div className="row justify-content-center">
{commentsData.rows.map((comment, index) => {
// Si c'est le dernier élément
if (commentsData.rows.length === index + 1) {
return <CommentCard key={comment.id} ref={lastCommentCardRef} { ...comment } />;
}
return <CommentCard key={comment.id} { ...comment } />;
})}
</div>
</div>
</Fragment>
);
}
export default FunctionComments;

View File

@ -1,4 +1,4 @@
import { useState } from 'react'; import { Fragment, useState } from 'react';
import Loader from './Loader'; import Loader from './Loader';
import htmlParser from 'html-react-parser'; import htmlParser from 'html-react-parser';
import api from '../utils/api'; import api from '../utils/api';
@ -38,7 +38,7 @@ const FunctionForm = (props) => {
); );
} }
return ( return (
<div className="FunctionComponent__slide"> <Fragment>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{props.inputArray.map((input, index) => { {props.inputArray.map((input, index) => {
let inputResult; let inputResult;
@ -69,7 +69,7 @@ const FunctionForm = (props) => {
htmlParser(message) htmlParser(message)
} }
</div> </div>
</div> </Fragment>
); );
} }

View File

@ -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 (
<FunctionTabs type={props.functionInfo.type}>
<div className="FunctionComponent__slide">
<FunctionForm inputArray={ [...props.functionInfo.utilizationForm || []] } slug={props.functionInfo.slug} />
</div>
<div className="FunctionComponent__slide text-center">Article</div>
<div className="FunctionComponent__slide">
<FunctionComments functionId={props.functionInfo.id} />
</div>
</FunctionTabs>
);
}
return (
<FunctionTabs type={props.type}>
<div className="FunctionComponent__slide text-center">Article</div>
<div className="FunctionComponent__slide">
<FunctionComments functionId={props.functionInfo.id} />
</div>
</FunctionTabs>
);
}
export default FunctionTabManager;

View File

@ -4,7 +4,7 @@ export const FunctionTabsContext = createContext();
function FunctionTabsContextProvider(props) { function FunctionTabsContextProvider(props) {
const [slideIndex, setSlideIndex] = useState(0); const [slideIndex, setSlideIndex] = useState(2);
return ( return (
<FunctionTabsContext.Provider value={{ slideIndex, setSlideIndex }}> <FunctionTabsContext.Provider value={{ slideIndex, setSlideIndex }}>

View File

@ -2,8 +2,7 @@ import FunctionTabsContextProvider from '../../contexts/FunctionTabsContext';
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag';
import FunctionComponentTop from '../../components/FunctionComponentTop'; import FunctionComponentTop from '../../components/FunctionComponentTop';
import FunctionTabsTop from '../../components/FunctionTabs/FunctionTabsTop'; import FunctionTabsTop from '../../components/FunctionTabs/FunctionTabsTop';
import FunctionTabs from '../../components/FunctionTabs/FunctionTabs'; import FunctionTabManager from '../../components/FunctionTabManager';
import FunctionForm from '../../components/FunctionForm';
import redirect from '../../utils/redirect'; import redirect from '../../utils/redirect';
import api from '../../utils/api'; import api from '../../utils/api';
import { API_URL } from '../../utils/config'; import { API_URL } from '../../utils/config';
@ -18,22 +17,10 @@ const FunctionComponent = (props) => {
return ( return (
<FunctionTabsContextProvider> <FunctionTabsContextProvider>
<HeadTag title={props.title} description={props.description} image={API_URL + props.image} /> <HeadTag title={props.title} description={props.description} image={API_URL + props.image} />
<div className="container-fluid"> <div className="container-fluid">
<FunctionTabsTop type={props.type} /> <FunctionTabsTop type={props.type} />
<FunctionComponentTop { ...props } API_URL={API_URL} publicationDate={publicationDate} /> <FunctionComponentTop { ...props } API_URL={API_URL} publicationDate={publicationDate} />
<FunctionTabManager functionInfo={props} />
<FunctionTabs type={props.type}>
{(props.type !== "article") ?
<FunctionForm inputArray={ [...props.utilizationForm || []] } slug={props.slug} />
:
<div className="FunctionComponent__slide text-center">Slide 1</div>
}
<div className="FunctionComponent__slide text-center">Slide 2</div>
<div className="FunctionComponent__slide text-center">Slide 3</div>
</FunctionTabs>
</div> </div>
</FunctionTabsContextProvider> </FunctionTabsContextProvider>
); );

View File

@ -3,9 +3,9 @@ import { useRouter } from 'next/router';
import HeadTag from '../../components/HeadTag'; import HeadTag from '../../components/HeadTag';
import FunctionCard from '../../components/FunctionCard/FunctionCard'; import FunctionCard from '../../components/FunctionCard/FunctionCard';
import Loader from '../../components/Loader'; import Loader from '../../components/Loader';
import '../../public/css/pages/functions.css';
import api from '../../utils/api'; import api from '../../utils/api';
import useAPI from '../../hooks/useAPI'; import useAPI from '../../hooks/useAPI';
import '../../public/css/pages/functions.css';
const Functions = () => { const Functions = () => {

View File

@ -52,7 +52,7 @@ const Profile = (props) => {
formData.append('isPublicEmail', inputState.isPublicEmail); formData.append('isPublicEmail', inputState.isPublicEmail);
formData.append('logo', inputState.logo); formData.append('logo', inputState.logo);
api.put('/users/', formData, { headers: { 'Authorization': user.token } }) api.put('/users/', formData, { headers: { 'Authorization': token } })
.then(() => { .then(() => {
setIsLoading(false); setIsLoading(false);
logoutUser(); logoutUser();

View File

@ -41,6 +41,9 @@ a:hover {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
div[aria-hidden="true"] > * {
display: none;
}
/* LOADING */ /* LOADING */
.isLoading { .isLoading {