diff --git a/src/components/TutorialPage/components/Commnets/Comment.jsx b/src/components/TutorialPage/components/Commnets/Comment.jsx index 7a5dd7db..5b2f8bdb 100644 --- a/src/components/TutorialPage/components/Commnets/Comment.jsx +++ b/src/components/TutorialPage/components/Commnets/Comment.jsx @@ -26,6 +26,8 @@ import { addComment } from "../../../../store/actions/tutorialPageActions"; import CommentLikesDislikes from "../../../ui-helpers/CommentLikesDislikes"; +import * as actions from "../../../../store/actions/actionTypes"; + const useStyles = makeStyles(() => ({ container: { margin: "10px 0", @@ -68,6 +70,14 @@ const Comment = ({ id }) => { }) => data ); + const userHandle = useSelector( + ({ + firebase: { + profile: { handle } + } + }) => handle + ); + const [data] = commentsArray.filter(comment => comment.comment_id == id); const repliesArray = useSelector( @@ -79,18 +89,35 @@ const Comment = ({ id }) => { ); const [replies] = repliesArray.filter(replies => replies.comment_id == id); - - const handleSubmit = comment => { + const handleSubmit = async comment => { const commentData = { content: comment, replyTo: data.comment_id, tutorial_id: data.tutorial_id, createdAt: firestore.FieldValue.serverTimestamp(), - userId: "codelabzuser" + userId: userHandle }; - addComment(commentData)(firebase, firestore, dispatch); + const commentId = await addComment(commentData)( + firebase, + firestore, + dispatch + ); + if (commentId) { + const newRepliesArray = repliesArray.map(reply => { + if (reply.comment_id === id) { + return { + ...reply, + replies: [...reply.replies, commentId] + }; + } + return reply; + }); + dispatch({ + type: actions.ADD_REPLIES_SUCCESS, + payload: newRepliesArray + }); + } }; - return ( data && ( <> @@ -109,7 +136,9 @@ const Comment = ({ id }) => { }} sx={{ textTransform: "none", fontSize: "12px" }} > - {replies?.replies?.length > 0 && replies?.replies?.length}{" "} + {replies?.replies?.length > 0 + ? replies?.replies?.length + : data?.no_of_replies}{" "} Reply )} diff --git a/src/components/TutorialPage/components/Commnets/CommentBox.jsx b/src/components/TutorialPage/components/Commnets/CommentBox.jsx index 87b35387..1bf301ba 100644 --- a/src/components/TutorialPage/components/Commnets/CommentBox.jsx +++ b/src/components/TutorialPage/components/Commnets/CommentBox.jsx @@ -3,6 +3,7 @@ import { makeStyles } from "@mui/styles"; import React, { useEffect, useState } from "react"; import Textbox from "./Textbox"; import Comment from "./Comment"; +import { useSelector } from "react-redux"; const useStyles = makeStyles(() => ({ container: { @@ -30,6 +31,14 @@ const CommentBox = ({ commentsArray, onAddComment }) => { const classes = useStyles(); const [comments, setComments] = useState(commentsArray); const [currCommentCount, setCurrCommentCount] = useState(3); + const data = useSelector( + ({ + tutorialPage: { + post: { data } + } + }) => data + ); + const noOfComments = data?.no_of_comments || 0; useEffect(() => { setComments(commentsArray?.slice(0, currCommentCount)); @@ -48,7 +57,7 @@ const CommentBox = ({ commentsArray, onAddComment }) => { data-testId="tutorialpageComments" > - Comments({commentsArray?.length || 0}) + Comments({noOfComments}) diff --git a/src/components/TutorialPage/components/Commnets/Textbox.jsx b/src/components/TutorialPage/components/Commnets/Textbox.jsx index 998011da..356456ab 100644 --- a/src/components/TutorialPage/components/Commnets/Textbox.jsx +++ b/src/components/TutorialPage/components/Commnets/Textbox.jsx @@ -9,6 +9,8 @@ import { import EmojiPicker from "emoji-picker-react"; import { InsertEmoticon, Send } from "@mui/icons-material"; import AccountCircle from "@mui/icons-material/AccountCircle"; +import Avatar from "@mui/material/Avatar"; +import { useSelector } from "react-redux"; const Textbox = ({ type, handleSubmit }) => { const [commentText, setCommentText] = useState(""); @@ -17,6 +19,13 @@ const Textbox = ({ type, handleSubmit }) => { setCommentText(prev => prev + emoji.emoji); setShowEmojiPicker(false); }; + const user = useSelector( + ({ + profile: { + user: { data } + } + }) => data + ); return ( { margin: "10px 0 24px" }} > - + > + {user?.photoURL && user?.photoURL.length > 0 ? ( + + ) : ( + user?.displayName[0] + )} + { + const [open, setOpen] = React.useState(false); + return ( + <> + +
{ + setOpen(true); + setTimeout(() => { + setOpen(false); + }, 1000); + }} + > + {children} +
+ + ); +}; + +export default InfoSnackBar; diff --git a/src/components/TutorialPage/components/PostDetails.jsx b/src/components/TutorialPage/components/PostDetails.jsx index 1eb94dbe..1a9b8ced 100644 --- a/src/components/TutorialPage/components/PostDetails.jsx +++ b/src/components/TutorialPage/components/PostDetails.jsx @@ -15,6 +15,7 @@ import { getUserProfileData } from "../../../store/actions"; import { HashLink } from "react-router-hash-link"; import { useParams } from "react-router-dom"; import TutorialLikesDislikes from "../../ui-helpers/TutorialLikesDislikes"; +import InfoSnackBar from "./InfoSnackBar"; const useStyles = makeStyles(() => ({ container: { padding: "20px", @@ -48,18 +49,6 @@ const PostDetails = ({ details }) => { getUserProfileData(details.user)(firebase, firestore, dispatch); }, [details]); - const user = useSelector( - ({ - profile: { - user: { data } - } - }) => data - ); - - const getTime = timestamp => { - return timestamp.toDate().toDateString(); - }; - const classes = useStyles(); return ( <> @@ -94,12 +83,17 @@ const PostDetails = ({ details }) => { - - - + + { + navigator.clipboard.writeText(window.location.href); + }} + > + + + diff --git a/src/components/TutorialPage/components/UserDetails.jsx b/src/components/TutorialPage/components/UserDetails.jsx index 2310956f..071a964e 100644 --- a/src/components/TutorialPage/components/UserDetails.jsx +++ b/src/components/TutorialPage/components/UserDetails.jsx @@ -34,7 +34,9 @@ const User = ({ id, timestamp, size }) => { const profileData = useSelector(({ firebase: { profile } }) => profile); - const user = useSelector( + const [user, setUser] = useState(null); + + const userData = useSelector( ({ profile: { user: { data } @@ -62,11 +64,17 @@ const User = ({ id, timestamp, size }) => { }; const getTime = timestamp => { - return timestamp.toDate().toDateString(); + return timestamp.toDate().toLocaleString(); }; const showFollowButton = profileData?.uid !== user?.uid; + useEffect(() => { + if (userData && userData.handle === id) { + setUser(userData); + } + }, [userData]); + return ( <> steps ); - if ((!loading && !tutorial) || (!loading && !tutorial?.isPublished)) { - console.log(loading, tutorial); - history.push("/not-found"); - } + const userHandle = useSelector( + ({ + firebase: { + profile: { handle } + } + }) => handle + ); const handleAddComment = async comment => { const commentData = { @@ -87,7 +90,7 @@ function TutorialPage({ background = "white", textColor = "black" }) { replyTo: id, tutorial_id: id, createdAt: firestore.FieldValue.serverTimestamp(), - userId: "codelabzuser" + userId: userHandle }; const commentId = await addComment(commentData)( firebase, @@ -102,6 +105,19 @@ function TutorialPage({ background = "white", textColor = "black" }) { } }; + useEffect(() => { + let timeoutId = null; + if ((!loading && !tutorial) || (!loading && !tutorial?.isPublished)) { + console.log(loading, tutorial); + timeoutId = setTimeout(() => { + history.push("/not-found"); + }, 2000); + } + return () => { + if (timeoutId) clearTimeout(timeoutId); + }; + }, [loading, tutorial]); + return ( { })); }, [tags]); - const organizations = useSelector( - ({ - profile: { - data: { organizations } - } - }) => organizations - ); - // console.log("organizations", organizations); + const profileState = useSelector(state => state.profile.data); + + const { organizations, isEmpty } = profileState || { + organizations: null, + isEmpty: false + }; useEffect(() => { - if (!organizations) { + const isFetchProfile = organizations === null && !isEmpty; + + if (isFetchProfile) { getProfileData()(firebase, firestore, dispatch); } - }, [firestore, firebase, dispatch, organizations]); + }, [firestore, firebase, dispatch, organizations, isEmpty]); const userHandle = useSelector( ({ @@ -119,14 +119,6 @@ const NewTutorial = ({ viewModal, onSidebarClick, viewCallback, active }) => { }) => handle ); - const displayName = useSelector( - ({ - firebase: { - profile: { displayName } - } - }) => displayName - ); - //This name should be replaced by displayName when implementing backend const sampleName = "User Name Here"; const allowOrgs = organizations && organizations.length > 0; @@ -158,6 +150,7 @@ const NewTutorial = ({ viewModal, onSidebarClick, viewCallback, active }) => { const onSubmit = formData => { formData.preventDefault(); + setError(false); const tutorialData = { ...formValue, created_by: userHandle, diff --git a/src/components/ui-helpers/CommentLikesDislikes.jsx b/src/components/ui-helpers/CommentLikesDislikes.jsx index 916a7659..36a2159e 100644 --- a/src/components/ui-helpers/CommentLikesDislikes.jsx +++ b/src/components/ui-helpers/CommentLikesDislikes.jsx @@ -106,11 +106,25 @@ const CommentLikesDislikes = ({ comment_id }) => { onChange={handleUserChoice} aria-label="like dislike" > - + {upVotes} - + {downVotes} diff --git a/src/components/ui-helpers/TutorialLikesDislikes.jsx b/src/components/ui-helpers/TutorialLikesDislikes.jsx index 5c016342..9e24a005 100644 --- a/src/components/ui-helpers/TutorialLikesDislikes.jsx +++ b/src/components/ui-helpers/TutorialLikesDislikes.jsx @@ -106,11 +106,25 @@ const TutorialLikesDislikes = ({ tutorial_id }) => { onChange={handleUserChoice} aria-label="like dislike" > - + {upVotes} - + {downVotes} diff --git a/src/store/actions/actionTypes.js b/src/store/actions/actionTypes.js index 1de49e90..6ce8a244 100644 --- a/src/store/actions/actionTypes.js +++ b/src/store/actions/actionTypes.js @@ -148,6 +148,10 @@ export const ADD_COMMENT_START = "ADD_COMMENT_START"; export const ADD_COMMENT_SUCCESS = "ADD_COMMENT_SUCCESS"; export const ADD_COMMENT_FAILED = "ADD_COMMENT_FAILED"; +export const INC_COMMENT_COUNT = "INC_COMMENT_COUNT"; + +export const ADD_REPLIES_SUCCESS = "ADD_REPLIES_SUCCESS"; + export const GET_STEPS_DATA_START = "GET_STEPS_DETAILS_START"; export const GET_STEPS_DATA_SUCCESS = "GET_STEPS_DETAILS_SUCCESS"; export const GET_STEPS_DATA_FAIL = "GET_STEPS_DETAILS_FAILED"; diff --git a/src/store/actions/tutorialPageActions.js b/src/store/actions/tutorialPageActions.js index 1aa92c3a..421d5e23 100644 --- a/src/store/actions/tutorialPageActions.js +++ b/src/store/actions/tutorialPageActions.js @@ -103,7 +103,7 @@ export const getTutorialFeedData = featured_image: tutorial?.featured_image, tut_tags: tutorial?.tut_tags, upVotes: tutorial?.upVotes || 0, - downVotes: tutorial?.downVotes || 0, + downVotes: tutorial?.downVotes || 0 }; return tutorialData; }); @@ -126,6 +126,15 @@ export const getTutorialData = if (tutorial.comments && Array.isArray(tutorial.comments)) { tutorial.comments.reverse(); } + const no_of_comments = await firestore + .collection("cl_comments") + .where("tutorial_id", "==", tutorialID) + .get() + .then(querySnapshot => { + return querySnapshot.size; + }); + tutorial.no_of_comments = no_of_comments; + console.log("fetched", tutorial); dispatch({ type: actions.GET_POST_DATA_SUCCESS, payload: tutorial }); } catch (e) { dispatch({ type: actions.GET_POST_DATA_FAIL }); @@ -165,6 +174,14 @@ export const getCommentData = .doc(commentId) .get(); const comment = data.data(); + const no_of_replies = await firestore + .collection("cl_comments") + .where("replyTo", "==", commentId) + .get() + .then(querySnapshot => { + return querySnapshot.size; + }); + comment.no_of_replies = no_of_replies; dispatch({ type: actions.GET_COMMENT_DATA_SUCCESS, payload: comment }); } catch (e) { dispatch({ type: actions.GET_COMMENT_DATA_FAIL }); @@ -218,41 +235,46 @@ export const addComment = comment => async (firebase, firestore, dispatch) => { } dispatch({ type: actions.ADD_COMMENT_SUCCESS }); + dispatch({ type: actions.INC_COMMENT_COUNT }); + return docref.id; } catch (e) { dispatch({ type: actions.ADD_COMMENT_FAILED, payload: e.message }); } }; -export const getRecommendedTutorials = currentTutorialTags => async (firebase, firestore) => { - try { - const tutorialsRef = firestore.collection("tutorials"); +export const getRecommendedTutorials = + currentTutorialTags => async (firebase, firestore) => { + try { + const tutorialsRef = firestore.collection("tutorials"); - // Fetch tutorials with matching tags - const querySnapshot = await tutorialsRef - .where("tut_tags", "array-contains-any", currentTutorialTags) - .get(); + // Fetch tutorials with matching tags + const querySnapshot = await tutorialsRef + .where("tut_tags", "array-contains-any", currentTutorialTags) + .get(); - // Calculate relevance score based on matching tags - const recommendedTutorials = querySnapshot.docs - .map(doc => { - const tutorial = doc.data(); + // Calculate relevance score based on matching tags + const recommendedTutorials = querySnapshot.docs + .map(doc => { + const tutorial = doc.data(); - // Skip unpublished tutorials - if (!tutorial.isPublished) return null; + // Skip unpublished tutorials + if (!tutorial.isPublished) return null; - const matchingTags = tutorial.tut_tags.filter(tag => currentTutorialTags.includes(tag)); - return { - ...tutorial, - relevanceScore: matchingTags.length - }; - }) - .filter(tutorial => tutorial !== null); // Remove null values from the array + const matchingTags = tutorial.tut_tags.filter(tag => + currentTutorialTags.includes(tag) + ); + return { + ...tutorial, + relevanceScore: matchingTags.length + }; + }) + .filter(tutorial => tutorial !== null); // Remove null values from the array - recommendedTutorials.sort((a, b) => b.relevanceScore - a.relevanceScore); + recommendedTutorials.sort((a, b) => b.relevanceScore - a.relevanceScore); - return recommendedTutorials; - } catch (error) { - console.error("Error fetching recommended tutorials:", error); - return []; - } -}; \ No newline at end of file + return recommendedTutorials; + } catch (error) { + console.error("Error fetching recommended tutorials:", error); + return []; + } + }; diff --git a/src/store/reducers/tutorialPageReducers/commentReducer.js b/src/store/reducers/tutorialPageReducers/commentReducer.js index 7d1780ac..5ef687cc 100644 --- a/src/store/reducers/tutorialPageReducers/commentReducer.js +++ b/src/store/reducers/tutorialPageReducers/commentReducer.js @@ -62,6 +62,13 @@ const CommentReducer = (state = initialState, { type, payload }) => { error: payload }; + case actions.ADD_REPLIES_SUCCESS: + return { + ...state, + loading: false, + replies: payload + }; + default: return state; } diff --git a/src/store/reducers/tutorialPageReducers/postReducer.js b/src/store/reducers/tutorialPageReducers/postReducer.js index a681c1b6..3dd25d8c 100644 --- a/src/store/reducers/tutorialPageReducers/postReducer.js +++ b/src/store/reducers/tutorialPageReducers/postReducer.js @@ -54,6 +54,16 @@ const PostReducer = (state = initialState, { type, payload }) => { error: payload }; + case actions.INC_COMMENT_COUNT: + return { + ...state, + data: { + ...(state.data || {}), + no_of_comments: + (state.data || { no_of_comments: 0 }).no_of_comments + 1 + } + }; + default: return state; }