89
resources/client/profile/panels/profile-comments-panel.tsx
Executable file
89
resources/client/profile/panels/profile-comments-panel.tsx
Executable file
@@ -0,0 +1,89 @@
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
import React, {Fragment} from 'react';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
import {RateReviewIcon} from '@common/icons/material/RateReview';
|
||||
import {TitlePoster} from '@app/titles/title-poster/title-poster';
|
||||
import {Title} from '@app/titles/models/title';
|
||||
import {TitleLink, TitleLinkWithEpisodeNumber} from '@app/titles/title-link';
|
||||
import {Episode} from '@app/titles/models/episode';
|
||||
import {useProfileComments} from '@app/profile/requests/use-profile-comments';
|
||||
import {Comment} from '@common/comments/comment';
|
||||
import {ThumbUpIcon} from '@common/icons/material/ThumbUp';
|
||||
import {FormattedRelativeTime} from '@common/i18n/formatted-relative-time';
|
||||
|
||||
export function ProfileCommentsPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const commentsQuery = useProfileComments();
|
||||
|
||||
if (commentsQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<RateReviewIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="No comments yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Follow :user for updates on comments they post in the future."
|
||||
values={{user: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (commentsQuery.data) {
|
||||
return (
|
||||
<Fragment>
|
||||
{commentsQuery.items.map(comment => (
|
||||
<CommentListItem key={comment.id} comment={comment} />
|
||||
))}
|
||||
<InfiniteScrollSentinel query={commentsQuery} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={commentsQuery} />;
|
||||
}
|
||||
|
||||
interface CommentListItemProps {
|
||||
comment: Comment;
|
||||
}
|
||||
function CommentListItem({comment}: CommentListItemProps) {
|
||||
const commentable = comment.commentable as Title | Episode;
|
||||
const title =
|
||||
commentable.model_type === 'episode' ? commentable.title! : commentable;
|
||||
return (
|
||||
<div className="mb-24 flex items-start gap-24 border-b pb-24">
|
||||
<TitlePoster title={title} size="w-90" srcSize="sm" />
|
||||
<div>
|
||||
<div className="text-lg font-semibold">
|
||||
{commentable.model_type === 'episode' ? (
|
||||
<TitleLinkWithEpisodeNumber
|
||||
title={title}
|
||||
episode={commentable}
|
||||
target="_blank"
|
||||
/>
|
||||
) : (
|
||||
<TitleLink title={title} target="_blank" />
|
||||
)}
|
||||
</div>
|
||||
<time className="mt-12 block text-xs text-muted">
|
||||
<FormattedRelativeTime date={comment.created_at} />
|
||||
</time>
|
||||
<p className="mt-8 whitespace-pre-line text-sm">{comment.content}</p>
|
||||
{comment.upvotes ? (
|
||||
<div className="mt-12 flex items-center gap-8 text-muted">
|
||||
<ThumbUpIcon size="sm" />
|
||||
<div>{comment.upvotes}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
resources/client/profile/panels/profile-followed-users-panel.tsx
Executable file
46
resources/client/profile/panels/profile-followed-users-panel.tsx
Executable file
@@ -0,0 +1,46 @@
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import React, {Fragment} from 'react';
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {BookmarkBorderIcon} from '@common/icons/material/BookmarkBorder';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
import {FollowerListItem} from '@app/profile/follower-list-item';
|
||||
import {useProfileFollowedUsers} from '@app/profile/requests/use-profile-followed-users';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
|
||||
export function ProfileFollowedUsersPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const followedUsersQuery = useProfileFollowedUsers();
|
||||
|
||||
if (followedUsersQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<BookmarkBorderIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="Not following anyone yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Check back later to see users :user is following."
|
||||
values={{user: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (followedUsersQuery.data) {
|
||||
return (
|
||||
<Fragment>
|
||||
{followedUsersQuery.items.map(followedUser => (
|
||||
<FollowerListItem key={followedUser.id} follower={followedUser} />
|
||||
))}
|
||||
<InfiniteScrollSentinel query={followedUsersQuery} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={followedUsersQuery} />;
|
||||
}
|
||||
46
resources/client/profile/panels/profile-followers-panel.tsx
Executable file
46
resources/client/profile/panels/profile-followers-panel.tsx
Executable file
@@ -0,0 +1,46 @@
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import React, {Fragment} from 'react';
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {BookmarkBorderIcon} from '@common/icons/material/BookmarkBorder';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
import {useProfileFollowers} from '@app/profile/requests/use-profile-followers';
|
||||
import {FollowerListItem} from '@app/profile/follower-list-item';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
|
||||
export function ProfileFollowersPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const followersQuery = useProfileFollowers();
|
||||
|
||||
if (followersQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<BookmarkBorderIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="No followers yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Be the first to follow :name."
|
||||
values={{name: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (followersQuery.data) {
|
||||
return (
|
||||
<Fragment>
|
||||
{followersQuery.items.map(follower => (
|
||||
<FollowerListItem key={follower.id} follower={follower} />
|
||||
))}
|
||||
<InfiniteScrollSentinel query={followersQuery} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={followersQuery} />;
|
||||
}
|
||||
51
resources/client/profile/panels/profile-lists-panel.tsx
Executable file
51
resources/client/profile/panels/profile-lists-panel.tsx
Executable file
@@ -0,0 +1,51 @@
|
||||
import {useProfileLists} from '@app/profile/requests/use-profile-lists';
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {ListAltIcon} from '@common/icons/material/ListAlt';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {UserListIndexItem} from '@app/user-lists/pages/user-lists-index-page/user-list-index-item';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
import React from 'react';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
|
||||
export function ProfileListsPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const listsQuery = useProfileLists();
|
||||
|
||||
if (listsQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<ListAltIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="No lists yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Follow :user for updates on lists they create in the future."
|
||||
values={{user: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (listsQuery.data) {
|
||||
return (
|
||||
<div>
|
||||
{listsQuery.items.map(list => (
|
||||
<UserListIndexItem
|
||||
key={list.id}
|
||||
list={list}
|
||||
user={user}
|
||||
showVisibility={false}
|
||||
/>
|
||||
))}
|
||||
<InfiniteScrollSentinel query={listsQuery} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={listsQuery} />;
|
||||
}
|
||||
69
resources/client/profile/panels/profile-ratings-panel.tsx
Executable file
69
resources/client/profile/panels/profile-ratings-panel.tsx
Executable file
@@ -0,0 +1,69 @@
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
import React, {Fragment} from 'react';
|
||||
import {StarIcon} from '@common/icons/material/Star';
|
||||
import {useProfileRatings} from '@app/profile/requests/use-profile-ratings';
|
||||
import {ContentGridLayout} from '@app/channels/content-grid/content-grid-layout';
|
||||
import {Title} from '@app/titles/models/title';
|
||||
import {TitlePortraitGridItem} from '@app/channels/content-grid/title-grid-item';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
import {Episode} from '@app/titles/models/episode';
|
||||
import {EpisodePortraitGridItem} from '@app/channels/content-grid/episode-grid-item';
|
||||
|
||||
export function ProfileRatingsPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const ratingsQuery = useProfileRatings();
|
||||
|
||||
if (ratingsQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<StarIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="No ratings yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Follow :user for updates on titles they rate in the future."
|
||||
values={{user: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (ratingsQuery.data) {
|
||||
return (
|
||||
<Fragment>
|
||||
<ContentGridLayout variant="portrait">
|
||||
{ratingsQuery.items.map(review => {
|
||||
const reviewable = review.reviewable as Title | Episode;
|
||||
if (reviewable.model_type === 'episode') {
|
||||
return (
|
||||
<EpisodePortraitGridItem
|
||||
key={review.id}
|
||||
item={reviewable}
|
||||
title={reviewable.title!}
|
||||
rating={review.score}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TitlePortraitGridItem
|
||||
item={review.reviewable as Title}
|
||||
key={review.id}
|
||||
rating={review.score}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ContentGridLayout>
|
||||
<InfiniteScrollSentinel query={ratingsQuery} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={ratingsQuery} />;
|
||||
}
|
||||
93
resources/client/profile/panels/profile-reviews-panel.tsx
Executable file
93
resources/client/profile/panels/profile-reviews-panel.tsx
Executable file
@@ -0,0 +1,93 @@
|
||||
import {IllustratedMessage} from '@common/ui/images/illustrated-message';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel';
|
||||
import {PageStatus} from '@common/http/page-status';
|
||||
import React, {Fragment} from 'react';
|
||||
import {useUserProfile} from '@app/profile/requests/use-user-profile';
|
||||
import {RateReviewIcon} from '@common/icons/material/RateReview';
|
||||
import {useProfileReviews} from '@app/profile/requests/use-profile-reviews';
|
||||
import {TitlePoster} from '@app/titles/title-poster/title-poster';
|
||||
import {Title} from '@app/titles/models/title';
|
||||
import {Review} from '@app/titles/models/review';
|
||||
import {TitleRating} from '@app/reviews/title-rating';
|
||||
import {TitleLink, TitleLinkWithEpisodeNumber} from '@app/titles/title-link';
|
||||
import {Episode} from '@app/titles/models/episode';
|
||||
|
||||
export function ProfileReviewsPanel() {
|
||||
const userQuery = useUserProfile();
|
||||
const user = userQuery.data!.user;
|
||||
const reviewsQuery = useProfileReviews();
|
||||
|
||||
if (reviewsQuery.noResults) {
|
||||
return (
|
||||
<IllustratedMessage
|
||||
imageHeight="h-auto"
|
||||
imageMargin="mb-14"
|
||||
image={<RateReviewIcon className="text-muted" />}
|
||||
size="sm"
|
||||
title={<Trans message="No reviews yet" />}
|
||||
description={
|
||||
<Trans
|
||||
message="Follow :user for updates on titles they review in the future."
|
||||
values={{user: user.display_name}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (reviewsQuery.data) {
|
||||
return (
|
||||
<Fragment>
|
||||
{reviewsQuery.items.map(review => (
|
||||
<ReviewListItem key={review.id} review={review} />
|
||||
))}
|
||||
<InfiniteScrollSentinel query={reviewsQuery} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageStatus query={reviewsQuery} />;
|
||||
}
|
||||
|
||||
interface ReviewListItemProps {
|
||||
review: Review;
|
||||
}
|
||||
function ReviewListItem({review}: ReviewListItemProps) {
|
||||
const totalVotes = review.helpful_count + review.not_helpful_count;
|
||||
const reviewable = review.reviewable as Title | Episode;
|
||||
const title =
|
||||
reviewable.model_type === 'episode' ? reviewable.title! : reviewable;
|
||||
return (
|
||||
<div className="mb-24 flex items-start gap-24 border-b pb-24">
|
||||
<TitlePoster title={title} size="w-90" srcSize="sm" />
|
||||
<div>
|
||||
<div className="text-lg font-semibold">
|
||||
{reviewable.model_type === 'episode' ? (
|
||||
<TitleLinkWithEpisodeNumber
|
||||
title={title}
|
||||
episode={reviewable}
|
||||
target="_blank"
|
||||
/>
|
||||
) : (
|
||||
<TitleLink title={title} target="_blank" />
|
||||
)}
|
||||
</div>
|
||||
<TitleRating className="mb-8 mt-14" score={review.score} />
|
||||
<div className="text-base font-semibold">{review.title}</div>
|
||||
<p className="mt-10 whitespace-pre-line text-sm">{review.body}</p>
|
||||
{totalVotes ? (
|
||||
<div className="mt-12 text-xs text-muted">
|
||||
<Trans
|
||||
message=":helpfulCount out of :total people found this helpful."
|
||||
values={{
|
||||
helpfulCount: review.helpful_count,
|
||||
total: review.helpful_count + review.not_helpful_count,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user