diff --git a/.env.example b/.env.example index bd9529ea79..bc2cb37af7 100644 --- a/.env.example +++ b/.env.example @@ -20,4 +20,4 @@ REACT_APP_USE_RECAPTCHA= REACT_APP_RECAPTCHA_SITE_KEY= # has to be inserted in the env file to use plugins and other websocket based features. -REACT_APP_BACKEND_WEBSOCKET_URL=ws://localhost:4000/graphql \ No newline at end of file +REACT_APP_BACKEND_WEBSOCKET_URL=localhost:4000/graphql \ No newline at end of file diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 7bf99d48ee..76db5206c2 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -617,6 +617,7 @@ export const ORGANIZATION_POST_CONNECTION_LIST = gql` firstName lastName } + pinned } } } @@ -843,3 +844,13 @@ export const IS_SAMPLE_ORGANIZATION_QUERY = gql` isSampleOrganization(id: $isSampleOrganizationId) } `; + +export const ORGANIZATION_CUSTOM_FIELDS = gql` + query ($customFieldsByOrganizationId: ID!) { + customFieldsByOrganization(id: $customFieldsByOrganizationId) { + _id + type + name + } + } +`; diff --git a/src/assets/svgs/deletePost.svg b/src/assets/svgs/deletePost.svg new file mode 100644 index 0000000000..b2fbbd197d --- /dev/null +++ b/src/assets/svgs/deletePost.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svgs/editPost.svg b/src/assets/svgs/editPost.svg new file mode 100644 index 0000000000..22a0a6dc08 --- /dev/null +++ b/src/assets/svgs/editPost.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/pinPost.svg b/src/assets/svgs/pinPost.svg new file mode 100644 index 0000000000..dc8d5eb2c2 --- /dev/null +++ b/src/assets/svgs/pinPost.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/svgs/pinUserCard.svg b/src/assets/svgs/pinUserCard.svg new file mode 100644 index 0000000000..a6c1bc9b45 --- /dev/null +++ b/src/assets/svgs/pinUserCard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/pinUserCardFilled.svg b/src/assets/svgs/pinUserCardFilled.svg new file mode 100644 index 0000000000..8ba5f2ec7f --- /dev/null +++ b/src/assets/svgs/pinUserCardFilled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/postUser.svg b/src/assets/svgs/postUser.svg new file mode 100644 index 0000000000..65081ac6b7 --- /dev/null +++ b/src/assets/svgs/postUser.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/report.svg b/src/assets/svgs/report.svg new file mode 100644 index 0000000000..fcbd65d31c --- /dev/null +++ b/src/assets/svgs/report.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svgs/rightScroll.svg b/src/assets/svgs/rightScroll.svg new file mode 100644 index 0000000000..08feb80d5e --- /dev/null +++ b/src/assets/svgs/rightScroll.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svgs/sharePost.svg b/src/assets/svgs/sharePost.svg new file mode 100644 index 0000000000..b7e5cd7c1b --- /dev/null +++ b/src/assets/svgs/sharePost.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svgs/threeDotUserCard.svg b/src/assets/svgs/threeDotUserCard.svg new file mode 100644 index 0000000000..b5ba16ea7d --- /dev/null +++ b/src/assets/svgs/threeDotUserCard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/UserPortal/PostCard/PostCard.module.css b/src/components/UserPortal/PostCard/PostCard.module.css index 56158473f9..1a65f4b54d 100644 --- a/src/components/UserPortal/PostCard/PostCard.module.css +++ b/src/components/UserPortal/PostCard/PostCard.module.css @@ -1,55 +1,147 @@ -.cardActions { +.cardStyle { + min-width: 370px; + max-width: 370px; + height: 470px; + margin-bottom: 30px; +} + +.cardAuthorSection { + height: 50px; + width: 100%; display: flex; - flex-direction: row; + justify-content: space-between; align-items: center; - gap: 1px; + padding: 20px; } -.cardActionBtn { - background-color: rgba(0, 0, 0, 0); - border: none; - color: black; +.profile { + width: 28px; + height: 28px; + margin-right: 12px; + font-weight: 600; + border-radius: 50%; } -.cardActionBtn:hover { - background-color: ghostwhite; - border: none; - color: black !important; +.profileNotPresent { + width: 28px; + height: 28px; + margin-right: 12px; + background-color: rgba(49, 187, 107, 0.12); + font-weight: 600; + border-radius: 50%; +} + +.user { + display: flex; + font-weight: 600; +} + +.img { + width: 100%; + min-height: 200px; } -.imageContainer { - max-width: 100%; +.imgNotPresent { + background-color: rgba(49, 187, 107, 0.12); + width: 100%; + min-height: 200px; } -.cardHeader { +.icons { display: flex; - flex-direction: row; - gap: 10px; - align-items: center; + align-items: baseline; +} + +.pinIcon { + margin-right: 12px; +} + +.postHeading { + font-weight: 600; + font-size: 18px; + margin-top: 10px; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.postMessage { + margin-top: 5px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; } -.creatorNameModal { +.postedOnText { + font-size: 18px; + color: rgb(128, 128, 128); +} + +.viewPostButton { + position: absolute; + bottom: 20px; + width: 112px; + height: 42px; +} + +.buttonContainer { + padding: 10px; display: flex; - flex-direction: row; - gap: 5px; - align-items: center; - margin-bottom: 10px; + justify-content: flex-end; +} + +.postCardContent { + padding: 5px 20px; +} + +.menu { + position: absolute; + background-color: white; + width: 150px; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 8px; + top: calc(10%); + left: 55%; + z-index: 1; } -.modalActions { +.menu button { + display: block; + width: 100%; + padding: 8px; + border: none; + background-color: transparent; + text-align: left; display: flex; - flex-direction: row; align-items: center; - gap: 1px; - margin: 5px 0px; + gap: 10px; } -.textModal { - margin-top: 10px; +.menu button:hover { + background-color: #f5f5f5; } -.colorPrimary { - background: #31bb6b; - color: white; +.threeDotButton { + border: none; + background: none; + padding: 0; + font: inherit; cursor: pointer; + outline: inherit; + height: 30px; + width: 30px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; +} + +.threeDotButton:hover { + background-color: #31bb6b; } diff --git a/src/components/UserPortal/PostCard/PostCard.test.tsx b/src/components/UserPortal/PostCard/PostCard.test.tsx index acd6176b02..e12326d102 100644 --- a/src/components/UserPortal/PostCard/PostCard.test.tsx +++ b/src/components/UserPortal/PostCard/PostCard.test.tsx @@ -1,359 +1,32 @@ import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; -import { act, render, screen } from '@testing-library/react'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { store } from 'state/store'; import i18nForTest from 'utils/i18nForTest'; -import { StaticMockLink } from 'utils/StaticMockLink'; - import PostCard from './PostCard'; -import userEvent from '@testing-library/user-event'; -import { - CREATE_COMMENT_POST, - LIKE_POST, - UNLIKE_POST, -} from 'GraphQl/Mutations/mutations'; - -const MOCKS = [ - { - request: { - query: LIKE_POST, - variables: { - postId: '', - }, - result: { - data: { - likePost: { - _id: '', - }, - }, - }, - }, - }, - { - request: { - query: UNLIKE_POST, - variables: { - post: '', - }, - result: { - data: { - unlikePost: { - _id: '', - }, - }, - }, - }, - }, - { - request: { - query: CREATE_COMMENT_POST, - variables: { - postId: '1', - comment: 'testComment', - }, - result: { - data: { - createComment: { - _id: '64ef885bca85de60ebe0f304', - creator: { - _id: '63d6064458fce20ee25c3bf7', - firstName: 'Noble', - lastName: 'Mittal', - email: 'test@gmail.com', - __typename: 'User', - }, - likeCount: 0, - likedBy: [], - text: 'testComment', - __typename: 'Comment', - }, - }, - }, - }, - }, -]; - -async function wait(ms = 100): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - }); -} -const link = new StaticMockLink(MOCKS, true); - -describe('Testing PostCard Component [User Portal]', () => { - test('Component should be rendered properly', async () => { - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, +describe('PostCard compoenent', () => { + const cardProps = { + id: '', + creator: { + firstName: 'test', + lastName: 'user', + email: 'test@user.com', image: '', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 1, - comments: [ - { - _id: '64eb13beca85de60ebe0ed0e', - creator: { - _id: '63d6064458fce20ee25c3bf7', - firstName: 'Noble', - lastName: 'Mittal', - email: 'test@gmail.com', - __typename: 'User', - }, - likeCount: 0, - likedBy: [], - text: 'First comment from Talawa user portal.', - __typename: 'Comment', - }, - ], - likedBy: [ - { - firstName: '', - lastName: '', - id: '2', - }, - ], - }; - - render( - - - - - - - - - - ); - - await wait(); - }); - - test('Component should be rendered properly if user has liked the post', async () => { - const beforeUserId = localStorage.getItem('userId'); - localStorage.setItem('userId', '2'); - - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: '', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '2', - }, - ], - }; - - render( - - - - - - - - - - ); - - await wait(); - - if (beforeUserId) { - localStorage.setItem('userId', beforeUserId); - } - }); - - test('Component should be rendered properly if user unlikes a post', async () => { - const beforeUserId = localStorage.getItem('userId'); - localStorage.setItem('userId', '2'); - - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: '', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '2', - }, - ], - }; - - render( - - - - - - - - - - ); - - await wait(); - - userEvent.click(screen.getByTestId('likePostBtn')); - - if (beforeUserId) { - localStorage.setItem('userId', beforeUserId); - } - }); - - test('Component should be rendered properly if user likes a post', async () => { - const beforeUserId = localStorage.getItem('userId'); - localStorage.setItem('userId', '2'); - - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: '', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '1', - }, - ], - }; - - render( - - - - - - - - - - ); - - await wait(); - - userEvent.click(screen.getByTestId('likePostBtn')); - - if (beforeUserId) { - localStorage.setItem('userId', beforeUserId); - } - }); - - test('Component should be rendered properly if post image is defined', async () => { - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: 'testImage', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '1', - }, - ], - }; - - render( - - - - - - - - - - ); - - await wait(); - }); - - test('Comment is created successfully after create comment button is clicked.', async () => { - const cardProps = { id: '1', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: 'testImage', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '1', - }, - ], - }; - + }, + image: '', + video: '', + text: 'This is post test text', + title: 'This is post test title', + createdAt: 1647398400000, + }; + test('Render post card with the correct content.', () => { render( - + @@ -363,44 +36,17 @@ describe('Testing PostCard Component [User Portal]', () => { ); - - const randomComment = 'testComment'; - - userEvent.click(screen.getByTestId('showCommentsBtn')); - - userEvent.type(screen.getByTestId('commentInput'), randomComment); - userEvent.click(screen.getByTestId('createCommentBtn')); - - await wait(); + const userFirstName = screen.getByTestId('creator-name'); + expect(userFirstName).toBeInTheDocument(); + const postText = screen.getByText('This is post test text'); + expect(postText).toBeInTheDocument(); + const postTitle = screen.getByText('This is post test title'); + expect(postTitle).toBeInTheDocument(); + expect(screen.getByText('16 March 2022')).toBeInTheDocument(); }); - - test('Comment modal pops when show comments button is clicked.', async () => { - const cardProps = { - id: '', - creator: { - firstName: 'test', - lastName: 'user', - email: 'test@user.com', - id: '1', - }, - image: 'testImage', - video: '', - text: 'This is post test text', - title: 'This is post test title', - likeCount: 1, - commentCount: 0, - comments: [], - likedBy: [ - { - firstName: 'test', - lastName: 'user', - id: '1', - }, - ], - }; - + test('opens menu when three dots button is clicked', () => { render( - + @@ -411,9 +57,12 @@ describe('Testing PostCard Component [User Portal]', () => { ); - await wait(); + fireEvent.click(screen.getByTestId('three-dots-button')); - userEvent.click(screen.getByTestId('showCommentsBtn')); - expect(screen.findAllByText('Comments')).not.toBeNull(); + expect(screen.getByText('Edit')).toBeInTheDocument(); + expect(screen.getByText('Delete')).toBeInTheDocument(); + expect(screen.getByText('Report')).toBeInTheDocument(); + expect(screen.getByText('Pin Post')).toBeInTheDocument(); + expect(screen.getByText('Share')).toBeInTheDocument(); }); }); diff --git a/src/components/UserPortal/PostCard/PostCard.tsx b/src/components/UserPortal/PostCard/PostCard.tsx index 0374858f23..a7f3186326 100644 --- a/src/components/UserPortal/PostCard/PostCard.tsx +++ b/src/components/UserPortal/PostCard/PostCard.tsx @@ -1,291 +1,118 @@ import React from 'react'; -import { Button, Card, Form, InputGroup, Modal } from 'react-bootstrap'; -import ThumbUpIcon from '@mui/icons-material/ThumbUp'; -import CommentIcon from '@mui/icons-material/Comment'; -import AccountCircleIcon from '@mui/icons-material/AccountCircle'; -import styles from './PostCard.module.css'; -import { useMutation } from '@apollo/client'; -import { - CREATE_COMMENT_POST, - LIKE_POST, - UNLIKE_POST, -} from 'GraphQl/Mutations/mutations'; -import { toast } from 'react-toastify'; -import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; -import ThumbUpOffAltIcon from '@mui/icons-material/ThumbUpOffAlt'; -import { useTranslation } from 'react-i18next'; -import SendIcon from '@mui/icons-material/Send'; -import { errorHandler } from 'utils/errorHandler'; -import CommentCard from '../CommentCard/CommentCard'; +import { Button, Card } from 'react-bootstrap'; +import { ReactComponent as PinIcon } from 'assets/svgs/pinUserCard.svg'; +import { ReactComponent as TheeDotIcon } from 'assets/svgs/threeDotUserCard.svg'; +import { ReactComponent as EditPostIcon } from 'assets/svgs/editPost.svg'; +import { ReactComponent as DeletePostIcon } from 'assets/svgs/deletePost.svg'; +import { ReactComponent as PinUserCardIcon } from 'assets/svgs/pinUserCard.svg'; +import { ReactComponent as ReportIcon } from 'assets/svgs/report.svg'; +import { ReactComponent as SharePostIcon } from 'assets/svgs/sharePost.svg'; +import styles from './PostCard.module.css'; interface InterfacePostCardProps { id: string; creator: { firstName: string; lastName: string; - email: string; + image: string; id: string; }; image: string; video: string; text: string; title: string; - likeCount: number; - commentCount: number; - comments: { - creator: { - _id: string; - firstName: string; - lastName: string; - email: string; - }; - likeCount: number; - likedBy: { - id: string; - }[]; - text: string; - }[]; - likedBy: { - firstName: string; - lastName: string; - id: string; - }[]; -} - -interface InterfaceCommentCardProps { - id: string; - creator: { - id: string; - firstName: string; - lastName: string; - email: string; - }; - likeCount: number; - likedBy: { - id: string; - }[]; - text: string; + createdAt: number; } export default function postCard(props: InterfacePostCardProps): JSX.Element { - const { t } = useTranslation('translation', { - keyPrefix: 'postCard', - }); + const [isMenuOpen, setMenuOpen] = React.useState(false); - const userId = localStorage.getItem('userId'); - const likedByUser = props.likedBy.some((likedBy) => likedBy.id === userId); - const [comments, setComments] = React.useState(props.comments); - const [numComments, setNumComments] = React.useState(props.commentCount); - - const [likes, setLikes] = React.useState(props.likeCount); - const [isLikedByUser, setIsLikedByUser] = React.useState(likedByUser); - const [showComments, setShowComments] = React.useState(false); - const [commentInput, setCommentInput] = React.useState(''); - - const postCreator = `${props.creator.firstName} ${props.creator.lastName}`; - - const [likePost, { loading: likeLoading }] = useMutation(LIKE_POST); - const [unLikePost, { loading: unlikeLoading }] = useMutation(UNLIKE_POST); - const [create, { loading: commentLoading }] = - useMutation(CREATE_COMMENT_POST); - - const toggleCommentsModal = (): void => setShowComments(!showComments); - - const handleToggleLike = async (): Promise => { - if (isLikedByUser) { - try { - const { data } = await unLikePost({ - variables: { - postId: props.id, - }, - }); - /* istanbul ignore next */ - if (data) { - setLikes((likes) => likes - 1); - setIsLikedByUser(false); - } - } catch (error: any) { - /* istanbul ignore next */ - toast.error(error); - } - } else { - try { - const { data } = await likePost({ - variables: { - postId: props.id, - }, - }); - /* istanbul ignore next */ - if (data) { - setLikes((likes) => likes + 1); - setIsLikedByUser(true); - } - } catch (error: any) { - /* istanbul ignore next */ - toast.error(error); - } - } - }; - - const handleCommentInput = ( - event: React.ChangeEvent - ): void => { - const comment = event.target.value; - setCommentInput(comment); + const toggleMenu = (): void => { + setMenuOpen(!isMenuOpen); }; - const createComment = async (): Promise => { - try { - const { data: createEventData } = await create({ - variables: { - postId: props.id, - comment: commentInput, - }, - }); - - /* istanbul ignore next */ - if (createEventData) { - setCommentInput(''); - setNumComments((numComments) => numComments + 1); + const formatDate = (dateString: number): string => { + const date = new Date(dateString); + const day = date.getDate(); + const month = date.toLocaleString('default', { month: 'long' }); + const year = date.getFullYear(); - const newComment: any = { - id: createEventData.createComment._id, - creator: { - id: createEventData.createComment.creator.id, - firstName: createEventData.createComment.creator.firstName, - lastName: createEventData.createComment.creator.lastName, - email: createEventData.createComment.creator.email, - }, - likeCount: createEventData.createComment.likeCount, - likedBy: createEventData.createComment.likedBy, - text: createEventData.createComment.text, - }; - - setComments([...comments, newComment]); - } - } catch (error: any) { - /* istanbul ignore next */ - errorHandler(t, error); - } + return `${day} ${month} ${year}`; }; return ( <> - - -
- - {postCreator} + +
+
+ {props.creator.image && ( + + )} + {!props.creator.image && ( +
+ )} + + + {props.creator.firstName} {props.creator.lastName} +
- - - {props.title} - {props.text} - {props.image && ( - - )} - - -
- - {likes} - {` ${t('likes')}`} - + - {numComments} - {` ${t('comments')}`} + + + {isMenuOpen && ( +
+ + + + + +
+ )}
-
- - - -
- - {postCreator} +
+ {/* image */} + {props.image && } + {!props.image &&
} +
+ {props.title} +
+ Posted On: + {formatDate(props.createdAt)}
- {props.image && ( - - )} -
{props.text}
-
- - {likes} - {` ${t('likes')}`} + + {props.text} + +
+
-

Comments

- {numComments ? ( - comments.map((comment: any, index: any) => { - const cardProps: InterfaceCommentCardProps = { - id: comment.id, - creator: { - id: comment.creator.id, - firstName: comment.creator.firstName, - lastName: comment.creator.lastName, - email: comment.creator.email, - }, - likeCount: comment.likeCount, - likedBy: comment.likedBy, - text: comment.text, - }; - - return ; - }) - ) : ( - <>No comments to show. - )} -
- - - - {commentLoading ? ( - - ) : ( - - )} - - - - +
+ ); } diff --git a/src/screens/UserPortal/Home/Home.module.css b/src/screens/UserPortal/Home/Home.module.css index 48643b3445..dd69868e29 100644 --- a/src/screens/UserPortal/Home/Home.module.css +++ b/src/screens/UserPortal/Home/Home.module.css @@ -1,71 +1,157 @@ -.borderNone { - border: none; +.mainContainer { + display: flex; } -.colorWhite { - color: white; +.postPageContainer { + position: relative; + display: flex; + flex-direction: column; + width: 80%; + height: 100%; + background-color: rgb(242, 247, 255); + font-family: 'Inter', sans-serif; + font-size: 18px; + margin-left: 15px; } -.maxWidth { - max-width: 300px; +.mainHeading { + color: rgb(0, 0, 0); + font-size: 40px; + font-weight: 600; + line-height: 48px; + letter-spacing: 0%; + margin-top: 40px; + margin-bottom: 45px; } -.colorLight { - background-color: #f5f5f5; +.startPostText { + color: rgb(0, 0, 0); + font-size: 24px; + font-weight: 400; + line-height: 22px; + letter-spacing: 0%; + text-align: left; + margin-bottom: 1%; } -.mainContainer { - width: 50%; - flex-grow: 3; - padding: 30px; - max-height: 100%; - overflow: auto; +.yourPostInput { + width: 90%; + height: 51px; + left: 379px; + right: -367px; + top: 161px; + bottom: 508px; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0px 8px 8px 0px; + background: rgb(241, 243, 246); } -.containerHeight { - height: calc(100vh - 66px); +.yourPostInput::placeholder { + text-indent: 20px; } -.link { - text-decoration: none !important; - color: black; +.startPostAndBrowse { + margin: 2%; + color: red; } -.postInputContainer { - background-color: white; - padding: 10px; - border-radius: 10px; - margin-bottom: 20px; +.browseButton { + height: 51px; + width: 116px; + background-color: rgb(49, 187, 107); + color: white; + font-size: 16px; + font-weight: 600; + border-radius: 8px 0px 0px 8px; + display: flex; + justify-content: center; + align-items: center; + padding: 2px; + cursor: pointer; } -.postActionContainer { +.browseAndPostInput { + display: flex; + justify-content: baseline; + align-items: center; +} + +.cardStyle { + width: 98%; + height: 220px; +} + +.postButton { + width: 130px; + height: 45px; + border-radius: 8px; + padding: 5px; display: flex; flex-direction: row; - justify-content: space-between; - gap: 10px; + justify-content: center; + align-items: center; } -.postActionBtn { - background-color: white; - border: none; - color: black; +.postButtonDiv { + width: 98%; + display: flex; + justify-content: end; + align-items: center; + margin-top: 25px; } -.postActionBtn:hover { - background-color: ghostwhite; - border: none; - color: black; +.postButtonIcon { + margin-left: 10px; } -.postInput { - height: 200px !important; - resize: none; - border: none; - box-shadow: none; - background-color: white; +.feedText { + color: rgb(0, 0, 0); + font-size: 40px; + font-weight: 600; + line-height: 48px; + letter-spacing: 0%; + margin-top: 40px; + margin-bottom: 20px; +} + +.secondaryHeading { + font-size: 25px; + font-weight: 600px; margin-bottom: 10px; } -.imageInput { - background-color: white; +.pinnedPosts { + display: flex; + flex-direction: row; + align-items: center; + gap: 3%; + margin-bottom: 30px; + width: 100%; + overflow-x: auto; + transition: transform 0.5s ease; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.pinnedPosts::-webkit-scrollbar { + display: none; +} + +.rightScrollButton { + height: 40px; + width: 40px; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + right: 1%; + top: 28%; + transform: translateY(-50%); +} + +.feedPosts { + display: flex; + flex-wrap: wrap; + gap: 3%; + margin-bottom: 40px; } diff --git a/src/screens/UserPortal/Home/Home.test.tsx b/src/screens/UserPortal/Home/Home.test.tsx index 105714cfc8..a6b88ef8da 100644 --- a/src/screens/UserPortal/Home/Home.test.tsx +++ b/src/screens/UserPortal/Home/Home.test.tsx @@ -2,19 +2,17 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { MockedProvider } from '@apollo/react-testing'; import { I18nextProvider } from 'react-i18next'; - import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { store } from 'state/store'; -import i18nForTest from 'utils/i18nForTest'; import { StaticMockLink } from 'utils/StaticMockLink'; -import Home from './Home'; -import userEvent from '@testing-library/user-event'; -import * as getOrganizationId from 'utils/getOrganizationId'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; -import { toast } from 'react-toastify'; +import * as getOrganizationId from 'utils/getOrganizationId'; +import i18nForTest from 'utils/i18nForTest'; +import userEvent from '@testing-library/user-event'; import dayjs from 'dayjs'; +import Home from './Home'; jest.mock('react-toastify', () => ({ toast: { @@ -47,6 +45,7 @@ const MOCKS = [ firstName: 'Aditya', lastName: 'Shelke', email: 'adidacreator1@gmail.com', + image: '', }, createdAt: dayjs(new Date()).add(1, 'day'), likeCount: 0, @@ -190,14 +189,7 @@ describe('Testing Home Screen [User Portal]', () => { expect(getOrganizationIdSpy).toHaveBeenCalled(); }); - - test('Screen should be rendered properly when user types on the Post Input', async () => { - const getOrganizationIdSpy = jest - .spyOn(getOrganizationId, 'default') - .mockImplementation(() => { - return ''; - }); - + test('Should render the main heading', async () => { render( @@ -212,21 +204,11 @@ describe('Testing Home Screen [User Portal]', () => { await wait(); - expect(getOrganizationIdSpy).toHaveBeenCalled(); - - const randomPostInput = 'This is a test'; - userEvent.type(screen.getByTestId('postInput'), randomPostInput); - - expect(screen.queryByText(randomPostInput)).toBeInTheDocument(); + const mainHeading = screen.getByText('POSTS'); + expect(mainHeading).toBeInTheDocument(); }); - test('Error toast should be visible when user tries to create a post with an empty body', async () => { - const getOrganizationIdSpy = jest - .spyOn(getOrganizationId, 'default') - .mockImplementation(() => { - return ''; - }); - + test('Should render the sub heading', async () => { render( @@ -241,22 +223,11 @@ describe('Testing Home Screen [User Portal]', () => { await wait(); - expect(getOrganizationIdSpy).toHaveBeenCalled(); - - userEvent.click(screen.getByTestId('postAction')); - - expect(toast.error).toBeCalledWith( - "Can't create a post with an empty body." - ); + const mainHeading = screen.getByText('Feed'); + expect(mainHeading).toBeInTheDocument(); }); - test('Info toast should be visible when user tries to create a post with a valid body', async () => { - const getOrganizationIdSpy = jest - .spyOn(getOrganizationId, 'default') - .mockImplementation(() => { - return ''; - }); - + test('Should display "Start a Post" text', async () => { render( @@ -271,15 +242,27 @@ describe('Testing Home Screen [User Portal]', () => { await wait(); - expect(getOrganizationIdSpy).toHaveBeenCalled(); + const startPostText = screen.getByText('Start a Post'); + expect(startPostText).toBeInTheDocument(); + }); - const randomPostInput = 'This is a test'; - userEvent.type(screen.getByTestId('postInput'), randomPostInput); - expect(screen.queryByText(randomPostInput)).toBeInTheDocument(); + test('Should update postContent state on input change', async () => { + render( + + + + + + + + + + ); - userEvent.click(screen.getByTestId('postAction')); + await wait(); - expect(toast.error).not.toBeCalledWith(); - expect(toast.info).toBeCalledWith('Processing your post. Please wait.'); + const postInput = screen.getByTestId('postInput'); + userEvent.type(postInput, 'Testing post content'); + expect(postInput).toHaveValue('Testing post content'); }); }); diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx index ad02e1b26c..5dc9f6db71 100644 --- a/src/screens/UserPortal/Home/Home.tsx +++ b/src/screens/UserPortal/Home/Home.tsx @@ -1,43 +1,54 @@ -import React from 'react'; +import React, { useRef, useEffect } from 'react'; import type { ChangeEvent } from 'react'; -import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import { Button, Card } from 'react-bootstrap'; import styles from './Home.module.css'; import UserSidebar from 'components/UserPortal/UserSidebar/UserSidebar'; -import OrganizationSidebar from 'components/UserPortal/OrganizationSidebar/OrganizationSidebar'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import { Button, FloatingLabel, Form } from 'react-bootstrap'; -import { Link } from 'react-router-dom'; +import OrganizationNavbar from 'components/UserPortal/OrganizationNavbar/OrganizationNavbar'; +import convertToBase64 from 'utils/convertToBase64'; import getOrganizationId from 'utils/getOrganizationId'; -import SendIcon from '@mui/icons-material/Send'; -import PostCard from 'components/UserPortal/PostCard/PostCard'; import { useMutation, useQuery } from '@apollo/client'; -import { - ADVERTISEMENTS_GET, - ORGANIZATION_POST_CONNECTION_LIST, -} from 'GraphQl/Queries/Queries'; +import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; +import { toast } from 'react-toastify'; import { errorHandler } from 'utils/errorHandler'; import { useTranslation } from 'react-i18next'; -import convertToBase64 from 'utils/convertToBase64'; -import { toast } from 'react-toastify'; +import PostCard from 'components/UserPortal/PostCard/PostCard'; +import { ReactComponent as PostIcon } from 'assets/svgs/postUser.svg'; +import { ReactComponent as RightScrollIcon } from 'assets/svgs/rightScroll.svg'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; -import PromotedPost from 'components/UserPortal/PromotedPost/PromotedPost'; interface InterfacePostCardProps { id: string; creator: { firstName: string; lastName: string; - email: string; + image: string; id: string; }; image: string; video: string; text: string; title: string; - likeCount: number; + createdAt: number; +} + +interface InterfacePost { + _id: string; + title: string; + text: string; + imageUrl: string; + videoUrl: string; + creator: { + _id: string; + firstName: string; + lastName: string; + image: string; + }; + createdAt: number; + likeCount: string; commentCount: number; comments: { + _id: string; creator: { _id: string; firstName: string; @@ -46,37 +57,51 @@ interface InterfacePostCardProps { }; likeCount: number; likedBy: { - id: string; - }[]; + _id: string; + }; text: string; - }[]; + }; likedBy: { + _id: string; firstName: string; lastName: string; - id: string; - }[]; + }; + pinned: boolean; } export default function home(): JSX.Element { const { t } = useTranslation('translation', { keyPrefix: 'home' }); - const organizationId = getOrganizationId(window.location.href); - const [posts, setPosts] = React.useState([]); + const [posts, setPosts] = React.useState([]); const [postContent, setPostContent] = React.useState(''); const [postImage, setPostImage] = React.useState(''); - const currentOrgId = window.location.href.split('/id=')[1] + ''; - const [adContent, setAdContent] = React.useState([]); + const [hasPinnedPost, setHasPinnedPost] = React.useState(false); + + useEffect(() => { + const hasPinned = posts.some((post) => post.pinned === true); + setHasPinnedPost(hasPinned); + }); + + const carouselRef = useRef(null); + + const handleScrollRight = (): void => { + if (carouselRef.current) { + (carouselRef.current as any).scrollBy({ + left: 300, // Adjust this value to change the scroll amount + behavior: 'smooth', + }); + } + }; + + const handlePostInput = (e: ChangeEvent): void => { + const content = e.target.value; + setPostContent(content); + }; const navbarProps = { currentPage: 'home', }; - const { - data: promotedPostsData, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - refetch: _promotedPostsRefetch, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - loading: promotedPostsLoading, - } = useQuery(ADVERTISEMENTS_GET); + const { data, refetch, @@ -116,114 +141,146 @@ export default function home(): JSX.Element { } }; - const handlePostInput = (e: ChangeEvent): void => { - const content = e.target.value; - - setPostContent(content); - }; - React.useEffect(() => { if (data) { setPosts(data.postsByOrganizationConnection.edges); } }, [data]); - React.useEffect(() => { - if (promotedPostsData) { - setAdContent(promotedPostsData.getAdvertisements); - } - }, [data]); - return ( <> -
+
-
-
- - - -
- => { - const target = e.target as HTMLInputElement; - const file = target.files && target.files[0]; - if (file) { - const image = await convertToBase64(file); - setPostImage(image); +
+
POSTS
+ +
+
Start a Post
+
+
-
-
-

{t('feed')}

-
- - {t('pinnedPosts')} - + Browse Files + + - +
+
+ +
-
- {adContent - .filter((ad: any) => ad.orgId == currentOrgId) - .filter((ad: any) => new Date(ad.endDate) > new Date()).length == 0 - ? '' - : adContent - .filter((ad: any) => ad.orgId == currentOrgId) - .filter((ad: any) => new Date(ad.endDate) > new Date()) - .map((post: any) => ( - - ))} + + Feed + Pinned post {loadingPosts ? (
Loading...
) : ( - <> - {posts.map((post: any) => { +
+ {posts + .filter((post) => post.pinned === true) + .map((post: any) => { + const allLikes: any = []; + post.likedBy.forEach((value: any) => { + const singleLike = { + firstName: value.firstName, + lastName: value.lastName, + id: value._id, + }; + allLikes.push(singleLike); + }); + + const postComments: any = []; + post.comments.forEach((value: any) => { + const commentLikes: any = []; + + value.likedBy.forEach((commentLike: any) => { + const singleLike = { + id: commentLike._id, + }; + commentLikes.push(singleLike); + }); + + const singleCommnet: any = { + id: value._id, + creator: { + firstName: value.creator.firstName, + lastName: value.creator.lastName, + id: value.creator._id, + image: value.creator.image, + }, + likeCount: value.likeCount, + likedBy: commentLikes, + text: value.text, + }; + + postComments.push(singleCommnet); + }); + + const cardProps: InterfacePostCardProps = { + id: post._id, + creator: { + id: post.creator._id, + firstName: post.creator.firstName, + lastName: post.creator.lastName, + image: post.creator.image, + }, + image: post.imageUrl, + video: post.videoUrl, + title: post.title, + text: post.text, + createdAt: post.createdAt, + }; + + return ; + })} + {hasPinnedPost && ( + + )} +
+ )} + + Your Feed +
+ {posts + .filter((post) => post.pinned === false) + .map((post: any) => { const allLikes: any = []; post.likedBy.forEach((value: any) => { const singleLike = { @@ -251,7 +308,7 @@ export default function home(): JSX.Element { firstName: value.creator.firstName, lastName: value.creator.lastName, id: value.creator._id, - email: value.creator.email, + image: value.creator.image, }, likeCount: value.likeCount, likedBy: commentLikes, @@ -267,24 +324,19 @@ export default function home(): JSX.Element { id: post.creator._id, firstName: post.creator.firstName, lastName: post.creator.lastName, - email: post.creator.email, + image: post.creator.image, }, image: post.imageUrl, video: post.videoUrl, title: post.title, text: post.text, - likeCount: post.likeCount, - commentCount: post.commentCount, - comments: postComments, - likedBy: allLikes, + createdAt: post.createdAt, }; return ; })} - - )} +
-
);