frontend: Voir et Poster des commentaires
This commit is contained in:
parent
903590fb08
commit
39a332a867
@ -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);
|
||||
|
33
website/components/CommentCard/CommentCard.css
Normal file
33
website/components/CommentCard/CommentCard.css
Normal 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;
|
||||
}
|
32
website/components/CommentCard/CommentCard.js
Normal file
32
website/components/CommentCard/CommentCard.js
Normal 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>
|
||||
- {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;
|
@ -38,6 +38,6 @@ const FunctionCard = forwardRef((props, ref) => {
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
export default FunctionCard;
|
7
website/components/FunctionComments/FunctionComments.css
Normal file
7
website/components/FunctionComments/FunctionComments.css
Normal file
@ -0,0 +1,7 @@
|
||||
.FunctionComments__row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.FunctionComments__textarea {
|
||||
height: auto;
|
||||
resize: vertical;
|
||||
}
|
113
website/components/FunctionComments/FunctionComments.js
Normal file
113
website/components/FunctionComments/FunctionComments.js
Normal 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;
|
@ -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 (
|
||||
<div className="FunctionComponent__slide">
|
||||
<Fragment>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{props.inputArray.map((input, index) => {
|
||||
let inputResult;
|
||||
@ -69,7 +69,7 @@ const FunctionForm = (props) => {
|
||||
htmlParser(message)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
30
website/components/FunctionTabManager.js
Normal file
30
website/components/FunctionTabManager.js
Normal 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;
|
@ -4,7 +4,7 @@ export const FunctionTabsContext = createContext();
|
||||
|
||||
function FunctionTabsContextProvider(props) {
|
||||
|
||||
const [slideIndex, setSlideIndex] = useState(0);
|
||||
const [slideIndex, setSlideIndex] = useState(2);
|
||||
|
||||
return (
|
||||
<FunctionTabsContext.Provider value={{ slideIndex, setSlideIndex }}>
|
||||
|
@ -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 (
|
||||
<FunctionTabsContextProvider>
|
||||
<HeadTag title={props.title} description={props.description} image={API_URL + props.image} />
|
||||
|
||||
<div className="container-fluid">
|
||||
|
||||
<FunctionTabsTop type={props.type} />
|
||||
<FunctionComponentTop { ...props } API_URL={API_URL} publicationDate={publicationDate} />
|
||||
|
||||
<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>
|
||||
|
||||
<FunctionTabManager functionInfo={props} />
|
||||
</div>
|
||||
</FunctionTabsContextProvider>
|
||||
);
|
||||
|
@ -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 = () => {
|
||||
|
||||
|
@ -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();
|
||||
|
@ -41,6 +41,9 @@ a:hover {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
div[aria-hidden="true"] > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* LOADING */
|
||||
.isLoading {
|
||||
|
Reference in New Issue
Block a user