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) {
|
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);
|
||||||
|
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>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
export default FunctionCard;
|
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 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
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 }}>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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 = () => {
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user