Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comment deletion functionality #154

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/components/Clickable/Clickable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import {ButtonHTMLAttributes, ComponentProps, FC, ReactNode} from 'react'

import styles from './Clickable.module.scss'

interface ButtonProps {
type ButtonProps = {
onClick?: () => void
disabled?: boolean
children: ReactNode
type?: ButtonHTMLAttributes<HTMLButtonElement>['type']
}
} & Pick<ButtonHTMLAttributes<HTMLButtonElement>, 'type'>

export const Button: FC<ButtonProps> = ({children, onClick, disabled, type}) => {
return (
Expand Down
30 changes: 30 additions & 0 deletions src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Dialog as MuiDialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from '@mui/material'
import {FC, ReactNode} from 'react'

type DialogProps = {
open: boolean
close: () => void
title?: ReactNode
contentText?: ReactNode
actions?: ReactNode
}

// inspired by: https://mui.com/material-ui/react-dialog/#alerts
export const Dialog: FC<DialogProps> = ({open, close, title, contentText, actions}) => {
return (
<MuiDialog
open={open}
onClose={close}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{title && <DialogTitle id="alert-dialog-title">{title}</DialogTitle>}
{contentText && (
<DialogContent>
<DialogContentText id="alert-dialog-description">{contentText}</DialogContentText>
</DialogContent>
)}
{actions && <DialogActions>{actions}</DialogActions>}
</MuiDialog>
)
}
129 changes: 78 additions & 51 deletions src/components/Problems/Discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import clsx from 'clsx'
import {FC, useState} from 'react'

import {Comment, CommentState} from '@/types/api/competition'
import {Profile} from '@/types/api/personal'
import {AuthContainer} from '@/utils/AuthContainer'
import {useHasPermissions} from '@/utils/useHasPermissions'

import {Button} from '../Clickable/Clickable'
import {Dialog} from '../Dialog/Dialog'
import {Loading} from '../Loading/Loading'
import styles from './Discussion.module.scss'
import {SideContainer} from './SideContainer'
Expand All @@ -23,25 +25,26 @@ export const Discussion: FC<DiscussionProps> = ({problemId, problemNumber, close
const [commentText, setCommentText] = useState('')
const [hiddenResponseText, setHiddenResponseText] = useState('')
const [hiddenResponseDialogId, sethiddenResponseDialogId] = useState(-1)
const [deleteDialogId, setDeleteDialogId] = useState<number | undefined>()

const queryKey = ['competition', 'problem', problemId, 'comments']
const {data: commentsData, isLoading: commentsIsLoading} = useQuery({
queryKey,
queryFn: () => axios.get<Comment[]>(`/api/competition/problem/${problemId}/comments`),
})
const comments = commentsData?.data.map((comment) => ({
id: comment.id,
can_edit: comment.edit_allowed,
text: comment.text,
state: comment.state,
hidden_response: comment.hidden_response,
posted_by: comment.posted_by_name,
}))
const comments = commentsData?.data

const {hasPermissions} = useHasPermissions()

const {isAuthed} = AuthContainer.useContainer()

const {data} = useQuery({
queryKey: ['personal', 'profiles', 'myprofile'],
queryFn: () => axios.get<Profile>(`/api/personal/profiles/myprofile`),
enabled: isAuthed,
})
const userId = data?.data.id

const queryClient = useQueryClient()

const invalidateCommentsAndCount = async () => {
Expand Down Expand Up @@ -77,7 +80,7 @@ export const Discussion: FC<DiscussionProps> = ({problemId, problemNumber, close
},
})

const {mutate: deleteComment} = useMutation({
const {mutate: confirmDeleteComment} = useMutation({
mutationFn: (id: number) => axios.delete(`/api/competition/comment/${id}`),
onSuccess: () => {
invalidateCommentsAndCount()
Expand All @@ -91,57 +94,81 @@ export const Discussion: FC<DiscussionProps> = ({problemId, problemNumber, close
setHiddenResponseText(e.currentTarget.value)
}

const close = () => setDeleteDialogId(undefined)
const agree = () => {
deleteDialogId !== undefined ? confirmDeleteComment(deleteDialogId) : undefined
close()
}

return (
<SideContainer title={'Diskusia - úloha ' + problemNumber} onClose={closeDiscussion}>
{/* delete comment dialog */}
<Dialog
open={deleteDialogId !== undefined}
close={close}
title="Vymazať komentár?"
contentText="Komentár bude nenávratne vymazaný."
actions={
<>
<Button onClick={close}>Zavrieť</Button>
<Button onClick={agree}>Potvrdiť</Button>
</>
}
/>
<div className={styles.container}>
<div className={styles.comments}>
{commentsIsLoading && <Loading />}
{comments &&
comments.map((comment) => (
<div
className={clsx(styles.comment, comment.state !== CommentState.Published && styles.notPublished)}
key={comment.id}
>
<div className={styles.title}>
<div>{comment.posted_by}</div>
</div>
<div>{comment.text}</div>
{comment.hidden_response && (
<>
<div className={styles.title}>
<div>Vedúci:</div>
comments.map((comment) => {
const isPostedByMe = userId === comment.posted_by

return (
<div
className={clsx(styles.comment, comment.state !== CommentState.Published && styles.notPublished)}
key={comment.id}
>
<div className={styles.title}>
<div>{comment.posted_by_name}</div>
</div>
<div>{comment.text}</div>
{comment.hidden_response && (
<>
<div className={styles.title}>
<div>Vedúci:</div>
</div>
<div>{comment.hidden_response}</div>
</>
)}
{comment.state === CommentState.WaitingForReview && <div>* komentár čaká na schválenie</div>}
{comment.state === CommentState.Hidden && <div>* tento komentár nie je verejný</div>}
{hiddenResponseDialogId === comment.id ? (
<div className={styles.inputContainer}>
<textarea
className={styles.textArea}
value={hiddenResponseText}
onChange={handleHiddenResponseChange}
/>
<div className={styles.commentActions}>
<Button onClick={() => hideComment({id: comment.id, hiddenResponseText})}>Odoslať</Button>
</div>
</div>
<div>{comment.hidden_response}</div>
</>
)}
{comment.state === CommentState.WaitingForReview && <div>* komentár čaká na schválenie</div>}
{comment.state === CommentState.Hidden && <div>* tento komentár nie je verejný</div>}
{hiddenResponseDialogId === comment.id ? (
<div className={styles.inputContainer}>
<textarea
className={styles.textArea}
value={hiddenResponseText}
onChange={handleHiddenResponseChange}
/>
) : (
<div className={styles.commentActions}>
<Button onClick={() => hideComment({id: comment.id, hiddenResponseText})}>Odoslať</Button>
{comment.state !== CommentState.Published && hasPermissions && (
<Button onClick={() => publishComment(comment.id)}>Zverejniť</Button>
)}
{comment.state !== CommentState.Hidden && hasPermissions && (
<Button onClick={() => sethiddenResponseDialogId(comment.id)}>Skryť</Button>
)}
{/* veduci moze zmazat svoj komentar v hocijakom stave, ucastnik moze zmazat svoj nepublishnuty komentar */}
{isPostedByMe && (hasPermissions || comment.state !== CommentState.Published) && (
<Button onClick={() => setDeleteDialogId(comment.id)}>Vymazať</Button>
)}
</div>
</div>
) : (
<div className={styles.commentActions}>
{comment.state !== CommentState.Published && hasPermissions && (
<Button onClick={() => publishComment(comment.id)}>Zverejniť</Button>
)}
{comment.state !== CommentState.Hidden && hasPermissions && (
<Button onClick={() => sethiddenResponseDialogId(comment.id)}>Skryť</Button>
)}
{comment.state === CommentState.WaitingForReview && !hasPermissions && (
<Button onClick={() => deleteComment(comment.id)}>Vymazať</Button>
)}
</div>
)}
</div>
))}
)}
</div>
)
})}
</div>
<div className={styles.submitInputContainer}>
<textarea className={styles.textArea} value={commentText} onChange={handleCommentChange} />
Expand Down
1 change: 1 addition & 0 deletions src/types/api/personal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface SchoolProfile {
}

export interface Profile {
id: number
first_name: string
last_name: string
nickname: string | null
Expand Down
Loading