From 1732945ec6bc2017c2508569110d8be5652c29e6 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Fri, 1 Sep 2023 20:38:53 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20speeding=20up=20reactions=20and=20votes?= =?UTF-8?q?=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../issues/board-views/block-state.tsx | 6 +- apps/space/components/issues/navbar/theme.tsx | 13 +- .../issues/peek-overview/header.tsx | 4 +- .../issues/peek-overview/issue-activity.tsx | 2 +- .../peek-overview/issue-emoji-reactions.tsx | 6 +- apps/space/store/issue_details.ts | 173 ++++++++++++------ apps/space/store/user.ts | 18 +- apps/space/types/issue.ts | 30 +-- apps/space/types/user.ts | 20 ++ 9 files changed, 187 insertions(+), 85 deletions(-) diff --git a/apps/space/components/issues/board-views/block-state.tsx b/apps/space/components/issues/board-views/block-state.tsx index 6638566f9..16792c81b 100644 --- a/apps/space/components/issues/board-views/block-state.tsx +++ b/apps/space/components/issues/board-views/block-state.tsx @@ -1,5 +1,3 @@ -"use client"; - // constants import { issueGroupFilter } from "constants/data"; @@ -8,8 +6,8 @@ export const IssueBlockState = ({ state }: any) => { if (stateGroup === null) return <>; return ( -
-
+
+
{state?.name}
diff --git a/apps/space/components/issues/navbar/theme.tsx b/apps/space/components/issues/navbar/theme.tsx index faea53ba3..7efb561a4 100644 --- a/apps/space/components/issues/navbar/theme.tsx +++ b/apps/space/components/issues/navbar/theme.tsx @@ -1,25 +1,32 @@ -"use client"; - // next theme import { useTheme } from "next-themes"; // mobx react lite import { observer } from "mobx-react-lite"; +import { useEffect, useState } from "react"; export const NavbarTheme = observer(() => { + const [appTheme, setAppTheme] = useState("light"); + const { setTheme, theme } = useTheme(); const handleTheme = () => { setTheme(theme === "light" ? "dark" : "light"); }; + useEffect(() => { + if (!theme) return; + + setAppTheme(theme); + }, [theme]); + return ( ); }); diff --git a/apps/space/components/issues/peek-overview/header.tsx b/apps/space/components/issues/peek-overview/header.tsx index 7d2e76668..79de3978b 100644 --- a/apps/space/components/issues/peek-overview/header.tsx +++ b/apps/space/components/issues/peek-overview/header.tsx @@ -70,7 +70,7 @@ export const PeekOverviewHeader: React.FC = (props) => {
{issueDetailStore.peekMode === "side" && ( -
{(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
-
diff --git a/apps/space/components/issues/peek-overview/issue-activity.tsx b/apps/space/components/issues/peek-overview/issue-activity.tsx index 47e9e4d0f..fde2fd878 100644 --- a/apps/space/components/issues/peek-overview/issue-activity.tsx +++ b/apps/space/components/issues/peek-overview/issue-activity.tsx @@ -47,7 +47,7 @@ export const PeekOverviewIssueActivity: React.FC = observer((props) => { )} ) : ( -
+

Sign in to add your comment diff --git a/apps/space/components/issues/peek-overview/issue-emoji-reactions.tsx b/apps/space/components/issues/peek-overview/issue-emoji-reactions.tsx index 1a81cecdf..3d2bfadac 100644 --- a/apps/space/components/issues/peek-overview/issue-emoji-reactions.tsx +++ b/apps/space/components/issues/peek-overview/issue-emoji-reactions.tsx @@ -24,9 +24,7 @@ export const IssueEmojiReactions: React.FC = observer(() => { if (!workspace_slug || !project_slug || !issueId) return; const userReaction = reactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex); if (userReaction) return; - issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, { - reaction: reactionHex, - }); + issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex); }; const handleReactionClick = (reactionHex: string) => { @@ -57,6 +55,7 @@ export const IssueEmojiReactions: React.FC = observer(() => { if (reactions.length > 0) return ( {reactions @@ -74,7 +73,6 @@ export const IssueEmojiReactions: React.FC = observer(() => { handleReactionClick(reaction); }); }} - key={reaction} className={`flex items-center gap-1 text-custom-text-100 text-sm h-full px-2 py-1 rounded-md ${ reactions?.some((r) => r.actor_detail.id === user?.id && r.reaction === reaction) ? "bg-custom-primary-100/10" diff --git a/apps/space/store/issue_details.ts b/apps/space/store/issue_details.ts index 68f9af1ca..b8722d51e 100644 --- a/apps/space/store/issue_details.ts +++ b/apps/space/store/issue_details.ts @@ -1,9 +1,10 @@ import { makeObservable, observable, action, runInAction } from "mobx"; +import { v4 as uuidv4 } from "uuid"; // store import { RootStore } from "./root"; // services import IssueService from "services/issue.service"; -import { IIssue } from "types/issue"; +import { IIssue, IVote } from "types/issue"; export type IPeekMode = "side" | "modal" | "full"; @@ -32,8 +33,8 @@ export interface IIssueDetailStore { ) => Promise; deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void; // issue reactions - addIssueReaction: (workspaceId: string, projectId: string, issueId: string, data: any) => void; - removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionId: string) => void; + addIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void; + removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void; // issue votes addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise; removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise; @@ -174,84 +175,144 @@ class IssueDetailStore implements IssueDetailStore { } }; - addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { + addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { try { - const issueVoteResponse = await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, data); + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + reactions: [ + ...this.details[issueId].reactions, + { + id: uuidv4(), + issue: issueId, + reaction: reactionHex, + actor_detail: this.rootStore.user.currentActor, + }, + ], + }, + }; + }); - if (issueVoteResponse) { - runInAction(() => { - this.details = { - ...this.details, - [issueId]: { - ...this.details[issueId], - reactions: [...this.details[issueId].reactions, issueVoteResponse], - }, - }; - }); - } + await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, { + reaction: reactionHex, + }); } catch (error) { console.log("Failed to add issue vote"); + const issueReactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + reactions: issueReactions, + }, + }; + }); } }; - removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionId: string) => { + removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => { try { - await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionId); - const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + const newReactions = this.details[issueId].reactions.filter( + (_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.currentUser?.id) + ); - if (reactions) { - runInAction(() => { - this.details = { - ...this.details, - [issueId]: { - ...this.details[issueId], - reactions: reactions, - }, - }; - }); - } + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + reactions: newReactions, + }, + }; + }); + + await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex); } catch (error) { console.log("Failed to remove issue reaction"); + const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId); + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + reactions: reactions, + }, + }; + }); } }; addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => { - try { - const issueVoteResponse = await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data); - const issueDetails = await this.issueService.getIssueById(workspaceSlug, projectId, issueId); + const newVote: IVote = { + actor: this.rootStore.user.currentUser?.id ?? "", + actor_detail: this.rootStore.user.currentActor, + issue: issueId, + project: projectId, + workspace: workspaceSlug, + vote: data.vote, + }; - if (issueVoteResponse) { - runInAction(() => { - this.details = { - ...this.details, - [issueId]: { - ...issueDetails, - }, - }; - }); - } + const filteredVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.currentUser?.id); + + try { + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + votes: [...filteredVotes, newVote], + }, + }; + }); + + await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data); } catch (error) { console.log("Failed to add issue vote"); + const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); + + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + votes: issueVotes, + }, + }; + }); } }; removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => { - try { - await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId); - const issueDetails = await this.issueService.getIssueById(workspaceSlug, projectId, issueId); + const newVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.currentUser?.id); - if (issueDetails) { - runInAction(() => { - this.details = { - ...this.details, - [issueId]: { - ...issueDetails, - }, - }; - }); - } + try { + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + votes: newVotes, + }, + }; + }); + + await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId); } catch (error) { console.log("Failed to remove issue vote"); + const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId); + + runInAction(() => { + this.details = { + ...this.details, + [issueId]: { + ...this.details[issueId], + votes: issueVotes, + }, + }; + }); } }; } diff --git a/apps/space/store/user.ts b/apps/space/store/user.ts index b1a87d133..8d34b0bdc 100644 --- a/apps/space/store/user.ts +++ b/apps/space/store/user.ts @@ -2,14 +2,18 @@ import { observable, action, computed, makeObservable, runInAction } from "mobx"; // service import UserService from "services/user.service"; +import { ActorDetail } from "types/issue"; +// types +import { IUser } from "types/user"; export interface IUserStore { currentUser: any | null; fetchCurrentUser: () => void; + currentActor: () => any; } class UserStore implements IUserStore { - currentUser: any | null = null; + currentUser: IUser | null = null; // root store rootStore; // service @@ -22,6 +26,7 @@ class UserStore implements IUserStore { // actions setCurrentUser: action, // computed + currentActor: computed, }); this.rootStore = _rootStore; this.userService = new UserService(); @@ -33,6 +38,17 @@ class UserStore implements IUserStore { }); }; + get currentActor(): any { + return { + avatar: this.currentUser?.avatar, + display_name: this.currentUser?.display_name, + first_name: this.currentUser?.first_name, + id: this.currentUser?.id, + is_bot: false, + last_name: this.currentUser?.last_name, + }; + } + /** * * @param callback diff --git a/apps/space/types/issue.ts b/apps/space/types/issue.ts index 323df995c..754da6ae3 100644 --- a/apps/space/types/issue.ts +++ b/apps/space/types/issue.ts @@ -42,14 +42,7 @@ export interface IIssue { state: string; state_detail: any; target_date: any; - votes: { - issue: string; - vote: -1 | 1; - workspace: string; - project: string; - actor: string; - actor_detail: ActorDetail; - }[]; + votes: IVote[]; } export interface IIssueState { @@ -65,6 +58,15 @@ export interface IIssueLabel { color: string; } +export interface IVote { + issue: string; + vote: -1 | 1; + workspace: string; + project: string; + actor: string; + actor_detail: ActorDetail; +} + export interface Comment { id: string; actor_detail: ActorDetail; @@ -95,12 +97,12 @@ export interface IIssueReaction { } export interface ActorDetail { - avatar: string; - display_name: string; - first_name: string; - id: string; - is_bot: boolean; - last_name: string; + avatar?: string; + display_name?: string; + first_name?: string; + id?: string; + is_bot?: boolean; + last_name?: string; } export interface IssueDetail { diff --git a/apps/space/types/user.ts b/apps/space/types/user.ts index 45b3f3b6e..8c6d5f681 100644 --- a/apps/space/types/user.ts +++ b/apps/space/types/user.ts @@ -1,3 +1,23 @@ export interface IUser { + avatar: string; + cover_image: string | null; + created_at: Date; + created_location: string; + date_joined: Date; + email: string; + display_name: string; first_name: string; + id: string; + is_email_verified: boolean; + is_onboarded: boolean; + is_tour_completed: boolean; + last_location: string; + last_login: Date; + last_name: string; + mobile_number: string; + role: string; + token: string; + updated_at: Date; + username: string; + user_timezone: string; }