Skip to content

Commit

Permalink
Merge pull request #3102 from ONEARMY/feat/question-discussion-add-co…
Browse files Browse the repository at this point in the history
…mments

feat: question discussion add/edit comments
  • Loading branch information
iSCJT authored Jan 22, 2024
2 parents 263e506 + fb28abd commit 3e1f44f
Show file tree
Hide file tree
Showing 12 changed files with 516 additions and 21 deletions.
5 changes: 4 additions & 1 deletion packages/components/src/CommentItem/CommentItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ export const CommentItem = (props: CommentItemProps) => {
message="Are you sure you want to delete this comment?"
confirmButtonText="Delete"
handleCancel={() => setShowDeleteModal(false)}
handleConfirm={() => handleDelete && handleDelete(_id)}
handleConfirm={() => {
handleDelete && handleDelete(_id)
setShowDeleteModal(false)
}}
/>
</Flex>
</Box>
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/ConfirmModal/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const ConfirmModal = (props: Props) => {
</Flex>
<Flex px={1}>
<Button
aria-label={`Confirm ${confirmButtonText} action`}
data-cy="Confirm.modal: Confirm"
variant={'outline'}
onClick={() => props?.handleConfirm()}
Expand Down
16 changes: 16 additions & 0 deletions packages/components/src/CreateComment/CreateComment.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,19 @@ Donec dapibus leo quis sagittis fringilla. Phasellus ut imperdiet sapien. Nullam
/>
)
}

export const WithCustomPlaceholder: StoryFn<typeof CreateComment> = () => {
const [comment, setComment] = useState('')

return (
<CreateComment
comment={comment}
placeholder="Custom placeholder"
onChange={setComment}
onSubmit={() => null}
userProfileType="member"
maxLength={12300}
isLoggedIn={true}
/>
)
}
119 changes: 119 additions & 0 deletions packages/components/src/CreateComment/CreateComment.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { fireEvent } from '@testing-library/react'
import { vi } from 'vitest'

import { render } from '../tests/utils'
import { CreateComment } from './CreateComment'
import { WithCustomPlaceholder } from './CreateComment.stories'

describe('CreateComment Component', () => {
const mockOnSubmit = vi.fn()
const mockOnChange = vi.fn()

it('renders correctly when logged in', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={true}
comment=""
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
expect(
screen.getByPlaceholderText('Leave your questions or feedback...'),
).toBeInTheDocument()
expect(screen.queryByText('Login to leave a comment')).toBeNull()
})

it('renders login prompt when not logged in', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={false}
comment=""
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
expect(screen.getByText('Login')).toBeInTheDocument()
expect(
screen.queryByPlaceholderText('Leave your questions or feedback...'),
).toBeNull()
})

it('enables submit button when comment is entered and user is logged in', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={true}
comment="Test comment"
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
expect(screen.getByText('Leave a comment')).not.toBeDisabled()
})

it('disables submit button when no comment is entered', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={true}
comment=""
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
expect(screen.getByText('Leave a comment')).toBeDisabled()
})

it('handles user input in textarea', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={true}
comment=""
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
const textarea = screen.getByPlaceholderText(
'Leave your questions or feedback...',
)
fireEvent.change(textarea, { target: { value: 'New comment' } })
expect(mockOnChange).toHaveBeenCalledWith('New comment')
})

it('calls onSubmit when the submit button is clicked', () => {
const screen = render(
<CreateComment
maxLength={100}
isLoggedIn={true}
comment="Test comment"
onSubmit={mockOnSubmit}
onChange={mockOnChange}
/>,
)
const button = screen.getByText('Leave a comment')
fireEvent.click(button)
expect(mockOnSubmit).toHaveBeenCalledWith('Test comment')
})

it('renders with custom placeholder', () => {
const screen = render(
<WithCustomPlaceholder
comment={''}
placeholder="Custom placeholder"
onChange={vi.fn()}
onSubmit={() => null}
userProfileType="member"
maxLength={12300}
isLoggedIn={true}
/>,
)

expect(
screen.getByPlaceholderText('Custom placeholder'),
).toBeInTheDocument()
})
})
5 changes: 4 additions & 1 deletion packages/components/src/CreateComment/CreateComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export interface Props {
onSubmit: (value: string) => void
onChange: (value: string) => void
comment: string
placeholder?: string
userProfileType?: string
}

export const CreateComment = (props: Props) => {
const { comment, isLoggedIn, maxLength, onSubmit } = props
const userProfileType = props.userProfileType || 'member'
const placeholder = props.placeholder || 'Leave your questions or feedback...'

const onChange = (newValue: string) => {
props.onChange && props?.onChange(newValue)
Expand Down Expand Up @@ -52,8 +54,9 @@ export const CreateComment = (props: Props) => {
onChange={(event) => {
onChange && onChange(event.target.value)
}}
aria-label="Comment"
data-cy="comments-form"
placeholder="Leave your questions or feedback..."
placeholder={placeholder}
sx={{
background: 'none',
resize: 'vertical',
Expand Down
11 changes: 8 additions & 3 deletions packages/components/src/EditComment/EditComment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Field, Form } from 'react-final-form'
import { Flex, Text } from 'theme-ui'
import { Flex, Label } from 'theme-ui'

import { Button, FieldTextarea } from '../'

Expand Down Expand Up @@ -28,9 +28,13 @@ export const EditComment = (props: EditCommentProps) => {
p={2}
onSubmit={handleSubmit}
>
<Text as="label" sx={{ marginBottom: '6px', fontSize: 3 }}>
<Label
as="label"
htmlFor="comment"
sx={{ marginBottom: '6px', fontSize: 3 }}
>
Edit comment
</Text>
</Label>
<Field name="comment" id="comment" component={FieldTextarea} />
<Flex mt={4} ml="auto">
<Button
Expand All @@ -43,6 +47,7 @@ export const EditComment = (props: EditCommentProps) => {
</Button>
<Button
type={'submit'}
aria-label="Save changes"
small
onClick={() => {
props?.handleSubmit(values.comment)
Expand Down
3 changes: 2 additions & 1 deletion src/models/comment.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* @deprecated To be replaced by IDiscussionComment
*
* Comments are currently used as documents nested
* underneath Howtos and Research items
*
* - Howto: Available at howto.comments
* - ResearchItem: Available under researchItem.updates.comments
*/
Expand Down
4 changes: 4 additions & 0 deletions src/models/discussion.models.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { IComment } from './comment.model'

/**
* Extends IComment with parentCommentId
* to support nested comments
*/
export type IDiscussionComment = IComment & {
parentCommentId: string | null
}
Expand Down
98 changes: 91 additions & 7 deletions src/pages/Question/QuestionComments.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,100 @@
import { CommentList } from 'oa-components'
import { Box } from 'theme-ui'
import { useEffect, useState } from 'react'
import { DiscussionContainer } from 'oa-components'
import { MAX_COMMENT_LENGTH } from 'src/constants'
import { logger } from 'src/logger'
import { useDiscussionStore } from 'src/stores/Discussions/discussions.store'
import { Card } from 'theme-ui'

import type { IDiscussionComment } from 'src/models'
import type { IDiscussion, IDiscussionComment, IUserPPDB } from 'src/models'

interface IProps {
questionDocId: string
comments: IDiscussionComment[]
activeUser?: IUserPPDB | null
onSubmit: (comment: string) => void
commentsUpdated: (comments: IDiscussionComment[]) => void
}

export const QuestionComments = ({ comments }: IProps) => {
export const QuestionComments = ({
questionDocId,
comments,
activeUser,
onSubmit,
commentsUpdated,
}: IProps) => {
const [comment, setComment] = useState('')
const [discussionObject, setDiscussionObject] = useState<IDiscussion | null>(
null,
)
const store = useDiscussionStore()

useEffect(() => {
const loadDiscussion = async () => {
const obj = await store.fetchOrCreateDiscussionBySource(
questionDocId,
'question',
)
if (!obj) {
return
}
setDiscussionObject(obj)
}
loadDiscussion()
}, [questionDocId])

const handleEdit = async (_id: string, comment: string) => {
logger.info({ _id, comment }, 'question comment edited')
if (discussionObject) {
store.editComment(discussionObject, _id, comment)
}
}

const handleEditRequest = async () => {
logger.debug('Edit existing comment')
}

const handleDelete = async (_id: string) => {
logger.debug({ _id }, 'question comment deleted')
if (discussionObject) {
const updatedObj = await store.deleteComment(discussionObject, _id)
commentsUpdated &&
commentsUpdated(
transformToUserComments(updatedObj?.comments || [], activeUser),
)
}
}

return (
<Box mt={5}>
<CommentList comments={comments} />
</Box>
<Card
sx={{
marginTop: 5,
padding: 4,
}}
>
<DiscussionContainer
comments={comments as any}
maxLength={MAX_COMMENT_LENGTH}
comment={comment}
onChange={setComment}
onMoreComments={() => {}}
handleEdit={handleEdit}
handleEditRequest={handleEditRequest}
handleDelete={handleDelete}
onSubmit={() => {
onSubmit(comment)
setComment('')
}}
isLoggedIn={!!activeUser}
/>
</Card>
)
}

const transformToUserComments = (
comments: IDiscussionComment[],
loggedInUser: IUserPPDB | null | undefined,
) =>
comments.map((c) => ({
...c,
isEditable: c._creatorId === loggedInUser?._id,
}))
Loading

0 comments on commit 3e1f44f

Please sign in to comment.