import {Review} from '@app/titles/models/review'; import {UserAvatar} from '@common/ui/images/user-avatar'; import {FormattedRelativeTime} from '@common/i18n/formatted-relative-time'; import {TitleRating} from '@app/reviews/title-rating'; import {Trans} from '@common/i18n/trans'; import React, { Fragment, ReactElement, useContext, useEffect, useRef, useState, } from 'react'; import {Link, useLocation} from 'react-router-dom'; import {Button} from '@common/ui/buttons/button'; import {useSubmitReviewFeedback} from '@app/reviews/requests/use-submit-review-feedback'; import clsx from 'clsx'; import {useAuth} from '@common/auth/use-auth'; import {useAuthClickCapture} from '@app/use-auth-click-capture'; import {IconButton} from '@common/ui/buttons/icon-button'; import {Tooltip} from '@common/ui/tooltip/tooltip'; import {useSubmitReport} from '@common/reports/requests/use-submit-report'; import {useDeleteReport} from '@common/reports/requests/use-delete-report'; import {ShareIcon} from '@common/icons/material/Share'; import useClipboard from 'react-use-clipboard'; import {useSettings} from '@common/core/settings/use-settings'; import {toast} from '@common/ui/toast/toast'; import {message} from '@common/i18n/message'; import { Menu, MenuItem, MenuTrigger, } from '@common/ui/navigation/menu/menu-trigger'; import {MoreVertIcon} from '@common/icons/material/MoreVert'; import {DialogTrigger} from '@common/ui/overlays/dialog/dialog-trigger'; import {ConfirmationDialog} from '@common/ui/overlays/dialog/confirmation-dialog'; import {useDeleteReviews} from '@app/reviews/requests/use-delete-reviews'; import {User} from '@common/auth/user'; import {SiteConfigContext} from '@common/core/settings/site-config-context'; import {useIsMobileMediaQuery} from '@common/utils/hooks/is-mobile-media-query'; interface Props { review: Review; isShared?: boolean; hideShareButton?: boolean; avatar?: ReactElement; } export function ReviewListItem({ review, isShared, hideShareButton, avatar, }: Props) { const isMobile = useIsMobileMediaQuery(); const ref = useRef(null); const scrolled = useRef(false); useEffect(() => { if (isShared && !scrolled.current) { setTimeout(() => { ref.current?.scrollIntoView({behavior: 'smooth'}); scrolled.current = true; }, 50); } }, [isShared]); return (
{isShared && (
)}
{!isMobile && (avatar || )}
{review.user && }
{review.title && (
{review.title}
)}
{review.body}
{!hideShareButton && }
); } interface ShareButtonProps { review: Review; } function ShareButton({review}: ShareButtonProps) { const {base_url} = useSettings(); const location = useLocation(); const url = `${base_url}${location.pathname}?reviewId=${review.id}`; const [, copyLink] = useClipboard(url); return ( }> { copyLink(); toast(message('Review link copied to clipboard')); }} > ); } interface FeedbackProps { review: Review; } function Feedback({review}: FeedbackProps) { const {user} = useAuth(); const authHandler = useAuthClickCapture(); const submitFeedback = useSubmitReviewFeedback(review); const isDisabled = submitFeedback.isPending || (user != null && user.id === review.user_id); const [helpfulCount, setHelpfulCount] = useState(review.helpful_count || 1); const [total, setTotal] = useState( review.helpful_count + review.not_helpful_count || 1, ); let initialFeedback: string | undefined; if (review.current_user_feedback != null) { initialFeedback = review.current_user_feedback ? 'helpful' : 'not_helpful'; } const [currentFeedback, setCurrentFeedback] = useState( initialFeedback, ); return (
); } interface ReviewOptionsTriggerProps { review: Review; } export function ReviewOptionsTrigger({review}: ReviewOptionsTriggerProps) { const {user, hasPermission} = useAuth(); const report = useSubmitReport(review); const deleteReport = useDeleteReport(review); const [isReported, setIsReported] = useState(review.current_user_reported); const handleReport = () => { if (isReported) { deleteReport.mutate(undefined, { onSuccess: () => setIsReported(false), }); } else { report.mutate({}, {onSuccess: () => setIsReported(true)}); } }; const deleteReview = useDeleteReviews(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const showDeleteButton = (user && review.user_id === user.id) || hasPermission('reviews.delete'); const handleDelete = (isConfirmed: boolean) => { setIsDeleteDialogOpen(false); if (isConfirmed) { deleteReview.mutate({reviewIds: [review.id]}); } }; return ( handleReport()}> {isReported ? ( ) : ( )} {showDeleteButton && ( setIsDeleteDialogOpen(true)} > )} handleDelete(isConfirmed)} > } body={ } confirm={} /> ); } interface UserDisplayNameProps { user: User; } function UserDisplayName({user}: UserDisplayNameProps) { const isMobile = useIsMobileMediaQuery(); const {auth} = useContext(SiteConfigContext); const sharedClassName = 'flex items-center gap-8 text-base font-medium'; if (auth.getUserProfileLink) { return ( {isMobile && } {user.display_name} ); } return (
{isMobile && } {user.display_name}
); }