first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
import {User} from '@common/auth/user';
import React, {Fragment, useContext, useState} from 'react';
import {Checkbox} from '@common/ui/forms/toggle/checkbox';
import {UserAvatar} from '@common/ui/images/user-avatar';
import {FormattedRelativeTime} from '@common/i18n/formatted-relative-time';
import {queryClient} from '@common/http/query-client';
import {Button} from '@common/ui/buttons/button';
import {Trans} from '@common/i18n/trans';
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
import {SiteConfigContext} from '@common/core/settings/site-config-context';
import {Link} from 'react-router-dom';
import {LinkStyle} from '@common/ui/buttons/external-link';
import {NormalizedModel} from '@common/datatable/filters/normalized-model';
import {Review} from '@app/titles/models/review';
import {TitleRating} from '@app/reviews/title-rating';
import {useUpdateReview} from '@app/admin/reviews/requests/use-update-review';
import {useForm} from 'react-hook-form';
import {CreateReviewPayload} from '@app/reviews/requests/use-create-review';
import {Form} from '@common/ui/forms/form';
import {StarSelector} from '@app/reviews/review-list/star-selector';
import {DeleteReviewsButton} from '@app/admin/reviews/delete-reviews-button';
import {BulletSeparatedItems} from '@app/titles/bullet-separated-items';
interface Props {
review: Review;
isSelected: boolean;
onToggle: () => void;
}
export function ReviewDatatableItem({review, isSelected, onToggle}: Props) {
const [isEditing, setIsEditing] = useState(false);
const helpfulCount = review.helpful_count || 1;
const totalFeedbackCount =
review.helpful_count + review.not_helpful_count || 1;
return (
<div className="border-b p-14">
{review.reviewable && (
<ReviewableHeader
isSelected={isSelected}
onToggle={onToggle}
reviewable={review.reviewable}
/>
)}
<div className="flex items-start gap-10 pt-14 md:pl-20">
<UserAvatar className="flex-shrink-0" user={review.user} size="md" />
<div className="min-w-0 flex-auto overflow-hidden">
<ReviewHeader review={review} />
{isEditing ? (
<EditReviewForm
review={review}
onClose={isSaved => {
setIsEditing(false);
if (isSaved) {
queryClient.invalidateQueries({queryKey: ['comment']});
}
}}
/>
) : (
<Fragment>
<div className="my-14">
<TitleRating className="mb-8" score={review.score} />
{review.title && (
<div className="mb-8 text-base font-medium">
{review.title}
</div>
)}
<div className="whitespace-break-spaces text-sm">
{review.body}
</div>
<div className="mt-8 text-xs text-muted">
<BulletSeparatedItems>
<Trans
message=":helpfulCount out of :total people found this helpful"
values={{helpfulCount, total: totalFeedbackCount}}
/>
{review.reports_count ? (
<Trans
message=":count reports"
values={{count: review.reports_count || 0}}
/>
) : null}
</BulletSeparatedItems>
</div>
</div>
<div>
<DeleteReviewsButton reviewIds={[review.id]} />
<Button
variant="outline"
size="xs"
onClick={() => setIsEditing(true)}
>
<Trans message="Edit" />
</Button>
</div>
</Fragment>
)}
</div>
</div>
</div>
);
}
interface ReviewableHeaderProps {
isSelected: boolean;
onToggle: Props['onToggle'];
reviewable: NormalizedModel;
}
function ReviewableHeader({
isSelected,
onToggle,
reviewable,
}: ReviewableHeaderProps) {
return (
<div className="flex items-center">
<div className="mr-14">
<Checkbox checked={isSelected} onChange={() => onToggle()} />
</div>
{reviewable.image && (
<img
className="mr-6 h-20 w-20 overflow-hidden rounded object-cover"
src={reviewable.image}
alt=""
/>
)}
<div className="mr-4 text-sm">{reviewable.name}</div>
<div className="text-xs text-muted">({reviewable.model_type})</div>
</div>
);
}
interface CommentHeaderProps {
review: Review;
}
function ReviewHeader({review}: CommentHeaderProps) {
return (
<div className="flex items-center gap-4 text-sm">
<div>
{review.user && (
<UserDisplayName user={review.user} show="display_name" />
)}
</div>
<div>&bull;</div>
<time>
<FormattedRelativeTime date={review.created_at} />
</time>
{review.user && (
<div className="ml-auto hidden md:block">
{<UserDisplayName user={review.user} show="email" />}
</div>
)}
</div>
);
}
interface EditReviewFormProps {
review: Review;
onClose: (saved: boolean) => void;
}
function EditReviewForm({review, onClose}: EditReviewFormProps) {
const [content, setContent] = useState(review.body);
const updateReview = useUpdateReview(review);
const form = useForm<CreateReviewPayload>({
defaultValues: {
score: review.score,
title: review.title,
body: review.body,
},
});
return (
<Form
className="mt-24"
form={form}
onSubmit={newValues => {
updateReview.mutate(newValues, {onSuccess: () => onClose(true)});
}}
>
<StarSelector
className="-ml-8 mb-12"
count={10}
value={form.watch('score')}
onValueChange={newScore => {
form.setValue('score', newScore);
}}
/>
<FormTextField
name="title"
className="mb-24"
label={<Trans message="Title" />}
labelSuffix={<Trans message="10 character minimum" />}
autoFocus
minLength={10}
required
/>
<FormTextField
className="mb-24"
name="body"
label={<Trans message="Review" />}
labelSuffix={<Trans message="100 character minimum" />}
inputElementType="textarea"
rows={5}
minLength={100}
required
/>
<Button
size="xs"
variant="outline"
color="primary"
type="submit"
className="mr-6"
disabled={updateReview.isPending}
>
<Trans message="Save" />
</Button>
<Button
size="xs"
variant="outline"
className="mr-6"
onClick={e => onClose(false)}
disabled={updateReview.isPending}
>
<Trans message="Cancel" />
</Button>
</Form>
);
}
interface UserDisplayNameProps {
user: User;
show: 'display_name' | 'email';
}
function UserDisplayName({user, show}: UserDisplayNameProps) {
const {auth} = useContext(SiteConfigContext);
if (auth.getUserProfileLink) {
return (
<Link
to={auth.getUserProfileLink(user)}
className={LinkStyle}
target="_blank"
>
{user[show]}
</Link>
);
}
return <div className="text-muted">{user[show]}</div>;
}