From 13985df8601f8aca2395a42385675cada31d2a5c Mon Sep 17 00:00:00 2001
From: Dakshesh Jain
Date: Mon, 19 Dec 2022 20:13:43 +0530
Subject: [PATCH] feat: made emoji-icon-picker
fix: google prompt coming up after leaving sign in, refractor: saving views data to db instead of local-storage
---
.../project/create-project-modal.tsx | 22 +-
.../project/settings/GeneralSettings.tsx | 40 +-
apps/app/components/sidebar/projects-list.tsx | 13 +-
.../components/socialbuttons/google-login.tsx | 11 +-
apps/app/constants/api-routes.ts | 2 +
apps/app/contexts/theme.context.tsx | 60 +-
apps/app/lib/hooks/useIssuesFilter.tsx | 1 -
apps/app/lib/hooks/useIssuesProperties.tsx | 1 +
apps/app/lib/hooks/useMyIssueFilter.tsx | 1 +
.../app/lib/hooks/useOutsideClickDetector.tsx | 19 +
apps/app/lib/services/project.service.ts | 11 +
.../projects/[projectId]/issues/index.tsx | 2 +-
.../pages/projects/[projectId]/settings.tsx | 2 +
apps/app/types/issues.d.ts | 1 +
apps/app/types/projects.d.ts | 1 +
apps/app/ui/emoji-icon-picker/emojis.json | 1090 +++++++++++++++++
apps/app/ui/emoji-icon-picker/helpers.ts | 26 +
apps/app/ui/emoji-icon-picker/index.tsx | 125 ++
apps/app/ui/emoji-icon-picker/types.d.ts | 5 +
apps/app/ui/index.ts | 1 +
20 files changed, 1388 insertions(+), 46 deletions(-)
create mode 100644 apps/app/lib/hooks/useOutsideClickDetector.tsx
create mode 100644 apps/app/ui/emoji-icon-picker/emojis.json
create mode 100644 apps/app/ui/emoji-icon-picker/helpers.ts
create mode 100644 apps/app/ui/emoji-icon-picker/index.tsx
create mode 100644 apps/app/ui/emoji-icon-picker/types.d.ts
diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx
index 5d4d9edb8..3e1d0c5a2 100644
--- a/apps/app/components/project/create-project-modal.tsx
+++ b/apps/app/components/project/create-project-modal.tsx
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
// swr
import useSWR, { mutate } from "swr";
// react hook form
-import { useForm } from "react-hook-form";
+import { useForm, Controller } from "react-hook-form";
// headless
import { Dialog, Transition } from "@headlessui/react";
// services
@@ -18,7 +18,7 @@ import { PROJECTS_LIST, WORKSPACE_MEMBERS } from "constants/fetch-keys";
import useUser from "lib/hooks/useUser";
import useToast from "lib/hooks/useToast";
// ui
-import { Button, Input, TextArea, Select } from "ui";
+import { Button, Input, TextArea, Select, EmojiIconPicker } from "ui";
// types
import { IProject } from "types";
@@ -32,6 +32,7 @@ const defaultValues: Partial = {
identifier: "",
description: "",
network: 0,
+ icon: "",
};
const IsGuestCondition: React.FC<{
@@ -83,6 +84,7 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => {
reset,
setError,
clearErrors,
+ control,
watch,
setValue,
} = useForm({
@@ -201,6 +203,22 @@ const CreateProjectModal: React.FC = ({ isOpen, setIsOpen }) => {
+
+
+ (
+
+ )}
+ />
+
;
isSubmitting: boolean;
+ control: Control
;
};
const NETWORK_CHOICES = { "0": "Secret", "2": "Public" };
-const GeneralSettings: React.FC = ({ register, errors, setError, isSubmitting }) => {
+const GeneralSettings: React.FC = ({
+ register,
+ errors,
+ setError,
+ isSubmitting,
+ control,
+}) => {
const { activeWorkspace } = useUser();
const checkIdentifier = (slug: string, value: string) => {
@@ -44,8 +52,26 @@ const GeneralSettings: React.FC = ({ register, errors, setError, isSubmit
This information will be displayed to every member of the project.
-
-
+
+
+
+
+ (
+
+ )}
+ />
+
+
+
= ({ register, errors, setError, isSubmit
}}
/>
-
+
-
+
= ({ navigation, sidebarCollapse }) => {
sidebarCollapse ? "justify-center" : ""
}`}
>
-
- {project?.name.charAt(0)}
-
+ {project.icon ? (
+
+ {String.fromCodePoint(parseInt(project.icon))}
+
+ ) : (
+
+ {project?.name.charAt(0)}
+
+ )}
+
{!sidebarCollapse && (
{project?.name}
diff --git a/apps/app/components/socialbuttons/google-login.tsx b/apps/app/components/socialbuttons/google-login.tsx
index 62d9402c7..6c39c58af 100644
--- a/apps/app/components/socialbuttons/google-login.tsx
+++ b/apps/app/components/socialbuttons/google-login.tsx
@@ -1,4 +1,4 @@
-import { FC, CSSProperties, useEffect, useRef, useCallback } from "react";
+import { FC, CSSProperties, useEffect, useRef, useCallback, useState } from "react";
// next
import Script from "next/script";
@@ -11,9 +11,10 @@ export interface IGoogleLoginButton {
export const GoogleLoginButton: FC = (props) => {
const googleSignInButton = useRef(null);
+ const [gsiScriptLoaded, setGsiScriptLoaded] = useState(false);
const loadScript = useCallback(() => {
- if (!googleSignInButton.current) return;
+ if (!googleSignInButton.current || gsiScriptLoaded) return;
window?.google?.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENTID || "",
callback: props.onSuccess as any,
@@ -30,12 +31,16 @@ export const GoogleLoginButton: FC = (props) => {
} as GsiButtonConfiguration // customization attributes
);
window?.google?.accounts.id.prompt(); // also display the One Tap dialog
- }, [props.onSuccess]);
+ setGsiScriptLoaded(true);
+ }, [props.onSuccess, gsiScriptLoaded]);
useEffect(() => {
if (window?.google?.accounts?.id) {
loadScript();
}
+ return () => {
+ window?.google?.accounts.id.cancel();
+ };
}, [loadScript]);
return (
diff --git a/apps/app/constants/api-routes.ts b/apps/app/constants/api-routes.ts
index 9e282562a..24de30251 100644
--- a/apps/app/constants/api-routes.ts
+++ b/apps/app/constants/api-routes.ts
@@ -64,6 +64,8 @@ export const PROJECT_MEMBERS = (workspaceSlug: string, projectId: string) =>
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`;
export const PROJECT_MEMBER_DETAIL = (workspaceSlug: string, projectId: string, memberId: string) =>
`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`;
+export const PROJECT_MEMBER_ME = (workspaceSlug: string, projectId: string) =>
+ `/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`;
export const PROJECT_VIEW_ENDPOINT = (workspaceSlug: string, projectId: string) =>
`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-views/`;
diff --git a/apps/app/contexts/theme.context.tsx b/apps/app/contexts/theme.context.tsx
index ee0b88e83..b363524b8 100644
--- a/apps/app/contexts/theme.context.tsx
+++ b/apps/app/contexts/theme.context.tsx
@@ -68,13 +68,9 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
collapsed: !state.collapsed,
};
- localStorage.setItem("theme", JSON.stringify(newState));
return newState;
case REHYDRATE_THEME: {
- let newState: any = localStorage.getItem("theme");
- if (newState !== null) {
- newState = JSON.parse(newState);
- }
+ const newState = payload;
return { ...initialState, ...newState };
}
case SET_ISSUE_VIEW: {
@@ -82,7 +78,6 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
issueView: payload?.issueView || "list",
};
- localStorage.setItem("theme", JSON.stringify(newState));
return {
...state,
...newState,
@@ -93,7 +88,6 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
groupByProperty: payload?.groupByProperty || null,
};
- localStorage.setItem("theme", JSON.stringify(newState));
return {
...state,
...newState,
@@ -104,7 +98,6 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
orderBy: payload?.orderBy || null,
};
- localStorage.setItem("theme", JSON.stringify(newState));
return {
...state,
...newState,
@@ -115,7 +108,6 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
filterIssue: payload?.filterIssue || null,
};
- localStorage.setItem("theme", JSON.stringify(newState));
return {
...state,
...newState,
@@ -127,6 +119,10 @@ export const reducer: ReducerFunctionType = (state, action) => {
}
};
+const saveDataToServer = async (workspaceSlug: string, projectID: string, state: any) => {
+ await projectService.setProjectView(workspaceSlug, projectID, state);
+};
+
export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
@@ -145,16 +141,6 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
});
}, []);
- const saveDataToServer = useCallback(() => {
- if (!activeProject || !activeWorkspace) return;
- projectService
- .setProjectView(activeWorkspace.slug, activeProject.id, state)
- .then((res) => {
- console.log("saved", res);
- })
- .catch((error) => {});
- }, [activeProject, activeWorkspace, state]);
-
const setIssueView = useCallback(
(display: "list" | "kanban") => {
dispatch({
@@ -163,9 +149,14 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
issueView: display,
},
});
- saveDataToServer();
+
+ if (!activeWorkspace || !activeProject) return;
+ saveDataToServer(activeWorkspace.slug, activeProject.id, {
+ ...state,
+ issueView: display,
+ });
},
- [saveDataToServer]
+ [activeProject, activeWorkspace, state]
);
const setGroupByProperty = useCallback(
@@ -176,9 +167,14 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
groupByProperty: property,
},
});
- saveDataToServer();
+
+ if (!activeWorkspace || !activeProject) return;
+ saveDataToServer(activeWorkspace.slug, activeProject.id, {
+ ...state,
+ groupByProperty: property,
+ });
},
- [saveDataToServer]
+ [activeProject, activeWorkspace, state]
);
const setOrderBy = useCallback(
@@ -189,11 +185,12 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
orderBy: property,
},
});
- saveDataToServer();
- },
- [saveDataToServer]
- );
+ if (!activeWorkspace || !activeProject) return;
+ saveDataToServer(activeWorkspace.slug, activeProject.id, state);
+ },
+ [activeProject, activeWorkspace, state]
+ );
const setFilterIssue = useCallback(
(property: "activeIssue" | "backlogIssue" | null) => {
dispatch({
@@ -202,9 +199,14 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
filterIssue: property,
},
});
- saveDataToServer();
+
+ if (!activeWorkspace || !activeProject) return;
+ saveDataToServer(activeWorkspace.slug, activeProject.id, {
+ ...state,
+ filterIssue: property,
+ });
},
- [saveDataToServer]
+ [activeProject, activeWorkspace, state]
);
useEffect(() => {
diff --git a/apps/app/lib/hooks/useIssuesFilter.tsx b/apps/app/lib/hooks/useIssuesFilter.tsx
index ca75e54b7..30bc42db9 100644
--- a/apps/app/lib/hooks/useIssuesFilter.tsx
+++ b/apps/app/lib/hooks/useIssuesFilter.tsx
@@ -1,4 +1,3 @@
-// hooks
import useTheme from "./useTheme";
import useUser from "./useUser";
// commons
diff --git a/apps/app/lib/hooks/useIssuesProperties.tsx b/apps/app/lib/hooks/useIssuesProperties.tsx
index ccd94e6b6..223eafe2a 100644
--- a/apps/app/lib/hooks/useIssuesProperties.tsx
+++ b/apps/app/lib/hooks/useIssuesProperties.tsx
@@ -18,6 +18,7 @@ const initialValues: Properties = {
start_date: false,
target_date: false,
cycle: false,
+ children_count: false,
};
const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
diff --git a/apps/app/lib/hooks/useMyIssueFilter.tsx b/apps/app/lib/hooks/useMyIssueFilter.tsx
index 2d16f1442..da8055a5f 100644
--- a/apps/app/lib/hooks/useMyIssueFilter.tsx
+++ b/apps/app/lib/hooks/useMyIssueFilter.tsx
@@ -18,6 +18,7 @@ const initialValues: Properties = {
start_date: false,
target_date: false,
cycle: false,
+ children_count: false,
};
const useMyIssuesProperties = (issues?: IIssue[]) => {
diff --git a/apps/app/lib/hooks/useOutsideClickDetector.tsx b/apps/app/lib/hooks/useOutsideClickDetector.tsx
new file mode 100644
index 000000000..f20666f8c
--- /dev/null
+++ b/apps/app/lib/hooks/useOutsideClickDetector.tsx
@@ -0,0 +1,19 @@
+import React, { useEffect } from "react";
+
+const useOutsideClickDetector = (ref: React.RefObject, callback: () => void) => {
+ const handleClick = (event: MouseEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node)) {
+ callback();
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener("click", handleClick);
+
+ return () => {
+ document.removeEventListener("click", handleClick);
+ };
+ });
+};
+
+export default useOutsideClickDetector;
diff --git a/apps/app/lib/services/project.service.ts b/apps/app/lib/services/project.service.ts
index 98891c796..92996f0e5 100644
--- a/apps/app/lib/services/project.service.ts
+++ b/apps/app/lib/services/project.service.ts
@@ -11,6 +11,7 @@ import {
PROJECT_MEMBER_DETAIL,
USER_PROJECT_INVITATIONS,
PROJECT_VIEW_ENDPOINT,
+ PROJECT_MEMBER_ME,
} from "constants/api-routes";
// services
import APIService from "lib/services/api.service";
@@ -132,6 +133,16 @@ class ProjectServices extends APIService {
});
}
+ async projectMemberMe(workspacSlug: string, projectId: string): Promise {
+ return this.get(PROJECT_MEMBER_ME(workspacSlug, projectId))
+ .then((response) => {
+ return response?.data;
+ })
+ .catch((error) => {
+ throw error?.response?.data;
+ });
+ }
+
async getProjectMember(
workspacSlug: string,
projectId: string,
diff --git a/apps/app/pages/projects/[projectId]/issues/index.tsx b/apps/app/pages/projects/[projectId]/issues/index.tsx
index 9606fffcf..f0576eedc 100644
--- a/apps/app/pages/projects/[projectId]/issues/index.tsx
+++ b/apps/app/pages/projects/[projectId]/issues/index.tsx
@@ -142,7 +142,7 @@ const ProjectIssues: NextPage = () => {
setFilterIssue,
orderBy,
filterIssue,
- } = useIssuesFilter(projectIssues?.results ?? []);
+ } = useIssuesFilter(projectIssues?.results.filter((p) => p.parent === null) ?? []);
useEffect(() => {
if (!isOpen) {
diff --git a/apps/app/pages/projects/[projectId]/settings.tsx b/apps/app/pages/projects/[projectId]/settings.tsx
index 18d5ed567..5878082d1 100644
--- a/apps/app/pages/projects/[projectId]/settings.tsx
+++ b/apps/app/pages/projects/[projectId]/settings.tsx
@@ -100,6 +100,7 @@ const ProjectSettings: NextPage = () => {
description: formData.description,
default_assignee: formData.default_assignee,
project_lead: formData.project_lead,
+ icon: formData.icon,
};
await projectServices
.updateProject(activeWorkspace.slug, projectId as string, payload)
@@ -186,6 +187,7 @@ const ProjectSettings: NextPage = () => {