Skip to content

Commit

Permalink
Merge pull request #90 from game-node-app/dev
Browse files Browse the repository at this point in the history
Lots of work on the MVP of review comments
  • Loading branch information
Lamarcke authored May 27, 2024
2 parents 51e71d7 + b8a1d94 commit 13705cd
Show file tree
Hide file tree
Showing 45 changed files with 1,243 additions and 348 deletions.
46 changes: 14 additions & 32 deletions src/components/activity/input/ActivityItemLikes.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
import React from "react";
import useUserId from "@/components/auth/hooks/useUserId";
import { useUserLike } from "@/components/statistics/hooks/useUserLike";
import { StatisticsActionDto } from "@/wrapper/server";
import sourceType = StatisticsActionDto.sourceType;
import { ActionIcon, Group, Text } from "@mantine/core";
import { redirectToAuth } from "supertokens-auth-react";
import { IconThumbUp } from "@tabler/icons-react";
import useOnMobile from "@/components/general/hooks/useOnMobile";
import {
Activity,
FindOneStatisticsDto,
StatisticsActionDto,
} from "@/wrapper/server";
import ItemLikesButton from "@/components/statistics/input/ItemLikesButton";
import sourceType = FindOneStatisticsDto.sourceType;

interface Props {
activityId: string;
activity: Activity;
}

const ActivityItemLikes = ({ activityId }: Props) => {
const userId = useUserId();
const [likesCount, isLiked, toggleLike] = useUserLike({
sourceId: activityId,
targetUserId: userId,
sourceType: sourceType.ACTIVITY,
});
const ActivityItemLikes = ({ activity }: Props) => {
return (
<ActionIcon
onClick={async () => {
if (!userId) {
await redirectToAuth();
return;
}
toggleLike();
}}
variant={isLiked ? "filled" : "transparent"}
size={"lg"}
color={isLiked ? "brand" : "white"}
data-disabled={!userId}
>
<IconThumbUp />
<Text>{likesCount}</Text>
</ActionIcon>
<ItemLikesButton
targetUserId={activity.profileUserId}
sourceId={activity.id}
sourceType={sourceType.ACTIVITY}
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const CollectionEntryActivityItem = ({ activity }: Props) => {
</Title>
</Link>
<Group>
<ActivityItemLikes activityId={activity.id} />
<ActivityItemLikes activity={activity} />
</Group>
</Stack>
</Box>
Expand Down
2 changes: 1 addition & 1 deletion src/components/activity/item/ReviewActivityItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const ReviewActivityItem = ({ activity }: Props) => {
size={"md"}
/>
<Group>
<ActivityItemLikes activityId={activity.id} />
<ActivityItemLikes activity={activity} />
</Group>
</Stack>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function useOwnCollectionEntryForGameId(
return null;
}
},
enabled: gameId != undefined,
}),
queryKey,
invalidate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const CollectionRemoveModal = ({ collectionId, opened, onClose }: Props) => {
}}
color={"red"}
>
I'm sure
Confirm
</Button>
</Group>
</Stack>
Expand Down
34 changes: 34 additions & 0 deletions src/components/comment/editor/CommentEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useState } from "react";
import { BubbleMenu, EditorOptions, useEditor } from "@tiptap/react";
import { StarterKit } from "@tiptap/starter-kit";
import { RichTextEditor } from "@mantine/tiptap";

interface Props extends Partial<EditorOptions> {}

export const COMMENT_EDITOR_EXTENSIONS = [StarterKit];

const CommentEditor = ({ ...editorOptions }: Props) => {
const editor = useEditor(
{
...editorOptions,
extensions: COMMENT_EDITOR_EXTENSIONS,
},
[editorOptions.content],
);

return (
<RichTextEditor editor={editor} className={"w-full h-full"}>
{editor && (
<BubbleMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
</RichTextEditor.ControlsGroup>
</BubbleMenu>
)}
<RichTextEditor.Content />
</RichTextEditor>
);
};

export default CommentEditor;
154 changes: 154 additions & 0 deletions src/components/comment/editor/CommentEditorView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React, {
MutableRefObject,
RefObject,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
ActionIcon,
Box,
Button,
Group,
LoadingOverlay,
Stack,
Text,
} from "@mantine/core";
import CommentEditor from "@/components/comment/editor/CommentEditor";
import { IconX } from "@tabler/icons-react";
import { Editor } from "@tiptap/core";
import {
useMutation,
UseMutationOptions,
useQueryClient,
} from "@tanstack/react-query";
import { CommentService } from "@/wrapper/server";
import { CreateCommentDto } from "@/wrapper/server";
import { notifications } from "@mantine/notifications";
import { useComment } from "@/components/comment/hooks/useComment";

interface Props {
/**
* If available, user will be able to modify this comment. <br>
* Ideally, should be cleared when 'onCancel' is called.
*/
commentId?: string;
onCancel: () => void;
sourceType: CreateCommentDto.sourceType;
sourceId: string;
editorContainerRef?: RefObject<HTMLDivElement>;
}

const CommentEditorView = ({
commentId,
sourceType,
sourceId,
onCancel,
editorContainerRef,
}: Props) => {
const queryClient = useQueryClient();
const editorRef = useRef<Editor>();
const commentQuery = useComment(commentId, sourceType);
const [previousContent, setPreviousContent] = useState<string | undefined>(
undefined,
);
const [shouldShowActionButtons, setShouldShowActionButtons] =
useState<boolean>(false);

const clearEditor = () => {
editorRef.current?.commands.clearContent();
setShouldShowActionButtons(false);
onCancel();
};

const commentMutation = useMutation({
mutationFn: async () => {
if (editorRef.current == undefined) return;

const content = editorRef.current?.getHTML();
if (commentId) {
return CommentService.commentControllerUpdate(commentId, {
sourceType,
content: content,
});
}

return CommentService.commentControllerCreate({
sourceId,
sourceType,
content: content,
});
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ["comments", sourceType, sourceId],
});
},
onSuccess: () => {
notifications.show({
color: "green",
message: "Successfully submitted your comment!",
});
clearEditor();
},
});

const isUpdateAction =
commentId != undefined && commentQuery.data != undefined;

useEffect(() => {
if (commentId == undefined && previousContent != undefined) {
setPreviousContent(undefined);
}

if (commentId != undefined && commentQuery.data != undefined) {
setPreviousContent(commentQuery.data.content);
setShouldShowActionButtons(true);
}
}, [commentId, commentQuery.data, previousContent]);

return (
<Stack className={"w-full h-full relative"} ref={editorContainerRef}>
<LoadingOverlay visible={commentQuery.isLoading} />
{isUpdateAction && (
<Text c={"dimmed"}>
You are currently editing one of your previous comments.
</Text>
)}
<CommentEditor
content={previousContent}
onUpdate={(props) => {
setShouldShowActionButtons(true);
}}
onCreate={(props) => {
editorRef.current = props.editor;
}}
/>
{shouldShowActionButtons && (
<Group className={"w-full justify-end"}>
<ActionIcon
size={"lg"}
variant={"default"}
onClick={() => {
clearEditor();
}}
>
<IconX />
</ActionIcon>
<Button
type={"button"}
loading={commentMutation.isPending}
onClick={() => {
commentMutation.mutate();
}}
>
{isUpdateAction ? "Update" : "Submit"}
</Button>
</Group>
)}
</Stack>
);
};

export default CommentEditorView;
29 changes: 29 additions & 0 deletions src/components/comment/hooks/useComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { CommentService, FindAllCommentsDto } from "@/wrapper/server";
import sourceType = FindAllCommentsDto.sourceType;
import { useQuery } from "@tanstack/react-query";

/**
* Retrieves a single comment based on criteria.
* Will only be enabled if commentId is not null.
*/
export function useComment(
commentId: string | undefined,
sourceType: sourceType,
) {
return useQuery({
queryKey: ["comment", "findOne", sourceType, commentId],
queryFn: async () => {
if (!commentId) {
return null;
}

return CommentService.commentControllerFindOneById(
sourceType.valueOf(),
commentId,
);
},
enabled: commentId != undefined,
retry: 1,
staleTime: Infinity,
});
}
53 changes: 53 additions & 0 deletions src/components/comment/hooks/useComments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { ExtendedUseQueryResult } from "@/util/types/ExtendedUseQueryResult";
import {
CommentService,
FindAllCommentsDto,
FindCommentsPaginatedResponseDto,
} from "@/wrapper/server";

const DEFAULT_USE_COMMENTS_ORDER_BY = {
createdAt: "DESC",
};

export interface UseCommentsProps extends FindAllCommentsDto {
enabled?: boolean;
offset?: number;
limit?: number;
}

export function useComments({
enabled = true,
sourceId,
sourceType,
offset = 0,
limit = 10,
orderBy = DEFAULT_USE_COMMENTS_ORDER_BY,
}: UseCommentsProps): ExtendedUseQueryResult<FindCommentsPaginatedResponseDto> {
const queryClient = useQueryClient();
const queryKey = ["comments", sourceType, sourceId, offset, limit, orderBy];

const invalidate = () => {
queryClient.invalidateQueries({
queryKey: queryKey.slice(0, 2),
});
};
return {
...useQuery({
queryKey,
queryFn: async () => {
return CommentService.commentControllerFindAll({
sourceId,
sourceType,
orderBy,
limit,
offset,
});
},
enabled,
retry: 1,
}),
queryKey,
invalidate,
};
}
Loading

0 comments on commit 13705cd

Please sign in to comment.