From a7b5ad55ab63f2be598d39b6e40e7bfeb875b95c Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sun, 23 Jul 2023 22:14:26 +0530 Subject: [PATCH] style: create project modal (#1628) * style: create project modal * fix: build error * fix: modal width --- .../analytics/custom-analytics/sidebar.tsx | 7 +- .../components/core/image-picker-popover.tsx | 4 +- .../components/emoji-icon-picker/index.tsx | 17 +- .../components/emoji-icon-picker/types.d.ts | 2 +- .../integration/jira/import-users.tsx | 2 +- .../app/components/issues/select/assignee.tsx | 1 - .../modules/sidebar-select/select-members.tsx | 2 +- .../project/create-project-modal.tsx | 350 +++++++++++------- .../project/delete-project-modal.tsx | 22 +- .../project/single-project-card.tsx | 2 +- .../ui/dropdowns/custom-search-select.tsx | 14 +- .../components/ui/dropdowns/custom-select.tsx | 2 +- apps/app/components/ui/text-area/index.tsx | 4 +- apps/app/constants/project.ts | 13 +- apps/app/hooks/use-workspace-members.tsx | 2 +- .../projects/[projectId]/settings/control.tsx | 4 +- .../projects/[projectId]/settings/index.tsx | 14 +- apps/app/types/projects.d.ts | 2 +- 18 files changed, 273 insertions(+), 191 deletions(-) diff --git a/apps/app/components/analytics/custom-analytics/sidebar.tsx b/apps/app/components/analytics/custom-analytics/sidebar.tsx index fbef3913c..6d413a9c9 100644 --- a/apps/app/components/analytics/custom-analytics/sidebar.tsx +++ b/apps/app/components/analytics/custom-analytics/sidebar.tsx @@ -360,11 +360,8 @@ export const AnalyticsSidebar: React.FC = ({
Network
- { - NETWORK_CHOICES[ - `${projectDetails?.network}` as keyof typeof NETWORK_CHOICES - ] - } + {NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? + ""}
diff --git a/apps/app/components/core/image-picker-popover.tsx b/apps/app/components/core/image-picker-popover.tsx index 1611f6f82..9304f9ba6 100644 --- a/apps/app/components/core/image-picker-popover.tsx +++ b/apps/app/components/core/image-picker-popover.tsx @@ -62,7 +62,7 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) return ( setIsOpen((prev) => !prev)} > {label} @@ -77,7 +77,7 @@ export const ImagePickerPopover: React.FC = ({ label, value, onChange }) leaveTo="transform opacity-0 scale-95" > -
+
{tabOptions.map((tab) => ( diff --git a/apps/app/components/emoji-icon-picker/index.tsx b/apps/app/components/emoji-icon-picker/index.tsx index 61ba80843..ffe8b33d6 100644 --- a/apps/app/components/emoji-icon-picker/index.tsx +++ b/apps/app/components/emoji-icon-picker/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; // headless ui import { Tab, Transition, Popover } from "@headlessui/react"; // react colors @@ -11,8 +11,6 @@ import icons from "./icons.json"; // helpers import { getRecentEmojis, saveRecentEmoji } from "./helpers"; import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; -// hooks -import useOutsideClickDetector from "hooks/use-outside-click-detector"; const tabOptions = [ { @@ -26,8 +24,6 @@ const tabOptions = [ ]; const EmojiIconPicker: React.FC = ({ label, value, onChange, onIconColorChange }) => { - const ref = useRef(null); - const [isOpen, setIsOpen] = useState(false); const [openColorPicker, setOpenColorPicker] = useState(false); const [activeColor, setActiveColor] = useState("rgb(var(--color-text-200))"); @@ -38,20 +34,13 @@ const EmojiIconPicker: React.FC = ({ label, value, onChange, onIconColorC setRecentEmojis(getRecentEmojis()); }, []); - useOutsideClickDetector(ref, () => { - setIsOpen(false); - }); - useEffect(() => { if (!value || value?.length === 0) onChange(getRandomEmoji()); }, [value, onChange]); return ( - - setIsOpen((prev) => !prev)} - > + + setIsOpen((prev) => !prev)} className="outline-none"> {label} { const router = useRouter(); const { workspaceSlug } = router.query; - const { workspaceMembers: members } = useWorkspaceMembers(workspaceSlug?.toString()); + const { workspaceMembers: members } = useWorkspaceMembers(workspaceSlug?.toString() ?? ""); const options = members?.map((member) => ({ value: member.member.email, diff --git a/apps/app/components/issues/select/assignee.tsx b/apps/app/components/issues/select/assignee.tsx index 6805c931e..47fe07c42 100644 --- a/apps/app/components/issues/select/assignee.tsx +++ b/apps/app/components/issues/select/assignee.tsx @@ -21,7 +21,6 @@ export const IssueAssigneeSelect: React.FC = ({ projectId, value = [], on const router = useRouter(); const { workspaceSlug } = router.query; - // fetching project members const { data: members } = useSWR( workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, workspaceSlug && projectId diff --git a/apps/app/components/modules/sidebar-select/select-members.tsx b/apps/app/components/modules/sidebar-select/select-members.tsx index c6bcb9ec8..af530503a 100644 --- a/apps/app/components/modules/sidebar-select/select-members.tsx +++ b/apps/app/components/modules/sidebar-select/select-members.tsx @@ -55,7 +55,7 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => {
{value && value.length > 0 && Array.isArray(value) ? ( diff --git a/apps/app/components/project/create-project-modal.tsx b/apps/app/components/project/create-project-modal.tsx index e97451a02..d9c6c24d3 100644 --- a/apps/app/components/project/create-project-modal.tsx +++ b/apps/app/components/project/create-project-modal.tsx @@ -2,19 +2,29 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import { mutate } from "swr"; +// react-hook-form import { useForm, Controller } from "react-hook-form"; - +// headless ui import { Dialog, Transition } from "@headlessui/react"; - // services import projectServices from "services/project.service"; -import workspaceService from "services/workspace.service"; // hooks import useToast from "hooks/use-toast"; +import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; +import useWorkspaceMembers from "hooks/use-workspace-members"; // ui -import { Input, TextArea, CustomSelect, PrimaryButton, SecondaryButton } from "components/ui"; +import { + Input, + TextArea, + CustomSelect, + PrimaryButton, + SecondaryButton, + Icon, + Avatar, + CustomSearchSelect, +} from "components/ui"; // icons import { XMarkIcon } from "@heroicons/react/24/outline"; // components @@ -25,7 +35,7 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper"; // types import { ICurrentUserResponse, IProject } from "types"; // fetch-keys -import { PROJECTS_LIST, WORKSPACE_MEMBERS_ME } from "constants/fetch-keys"; +import { PROJECTS_LIST } from "constants/fetch-keys"; // constants import { NETWORK_CHOICES } from "constants/project"; @@ -36,13 +46,14 @@ type Props = { }; const defaultValues: Partial = { - name: "", - identifier: "", - description: "", - network: 2, - emoji_and_icon: getRandomEmoji(), cover_image: "https://images.unsplash.com/photo-1575116464504-9e7652fddcb3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyODUyNTV8MHwxfHNlYXJjaHwxOHx8cGxhbmV8ZW58MHx8fHwxNjgxNDY4NTY5&ixlib=rb-4.0.3&q=80&w=1080", + description: "", + emoji_and_icon: getRandomEmoji(), + identifier: "", + name: "", + network: 2, + project_lead: null, }; const IsGuestCondition: React.FC<{ @@ -52,6 +63,7 @@ const IsGuestCondition: React.FC<{ useEffect(() => { setIsOpen(false); + setToastAlert({ title: "Error", type: "error", @@ -62,31 +74,22 @@ const IsGuestCondition: React.FC<{ return null; }; -export const CreateProjectModal: React.FC = (props) => { - const { isOpen, setIsOpen, user } = props; - +export const CreateProjectModal: React.FC = ({ isOpen, setIsOpen, user }) => { const [isChangeIdentifierRequired, setIsChangeIdentifierRequired] = useState(true); const { setToastAlert } = useToast(); - const { - query: { workspaceSlug }, - } = useRouter(); + const router = useRouter(); + const { workspaceSlug } = router.query; - const { data: myWorkspaceMembership } = useSWR( - workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null, - workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug as string) : null, - { - shouldRetryOnError: false, - } - ); + const { memberDetails } = useWorkspaceMyMembership(); + const { workspaceMembers } = useWorkspaceMembers(workspaceSlug?.toString() ?? ""); const { register, formState: { errors, isSubmitting }, handleSubmit, reset, - setError, control, watch, setValue, @@ -96,8 +99,8 @@ export const CreateProjectModal: React.FC = (props) => { reValidateMode: "onChange", }); - const projectName = watch("name") ?? ""; - const projectIdentifier = watch("identifier") ?? ""; + const projectName = watch("name"); + const projectIdentifier = watch("identifier"); useEffect(() => { if (projectName && isChangeIdentifierRequired) @@ -120,10 +123,10 @@ export const CreateProjectModal: React.FC = (props) => { else payload.emoji = formData.emoji_and_icon; await projectServices - .createProject(workspaceSlug as string, payload, user) + .createProject(workspaceSlug.toString(), payload, user) .then((res) => { mutate( - PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), + PROJECTS_LIST(workspaceSlug.toString(), { is_favorite: "all" }), (prevData) => [res, ...(prevData ?? [])], false ); @@ -135,28 +138,40 @@ export const CreateProjectModal: React.FC = (props) => { handleClose(); }) .catch((err) => { - if (err.status === 403) { + Object.keys(err.data).map((key) => setToastAlert({ type: "error", title: "Error!", - message: "You don't have permission to create project.", - }); - handleClose(); - return; - } - err = err.data; - Object.keys(err).map((key) => { - const errorMessages = err[key]; - setError(key as keyof IProject, { - message: Array.isArray(errorMessages) ? errorMessages.join(", ") : errorMessages, - }); - }); + message: err.data[key], + }) + ); }); }; - if (myWorkspaceMembership && isOpen) { - if (myWorkspaceMembership.role <= 10) return ; - } + const options = workspaceMembers?.map((member) => ({ + value: member.member.id, + query: + (member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email) + + " " + + member.member.last_name ?? "", + content: ( +
+ + {`${ + member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + : member.member.email + } ${member.member.last_name ?? ""}`} +
+ ), + })); + + const currentNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network")); + + if (memberDetails && isOpen) + if (memberDetails.role <= 10) return ; return ( @@ -184,12 +199,12 @@ export const CreateProjectModal: React.FC = (props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ +
{watch("cover_image") !== null && ( Cover Image )} @@ -199,87 +214,85 @@ export const CreateProjectModal: React.FC = (props) => {
-
-
-

Create Project

-
- { - setValue("cover_image", image); - }} - value={watch("cover_image")} - /> -
+
+ { + setValue("cover_image", image); + }} + value={watch("cover_image")} + /> +
+
+ ( + + {value ? ( + typeof value === "object" ? ( + + {value.name} + + ) : ( + renderEmoji(value) + ) + ) : ( + "Icon" + )} +
+ } + onChange={onChange} + value={value} + /> + )} + />
-
-
-
-
- ( - - {value.name} - - ) : ( - renderEmoji(value) - ) - ) : ( - "Icon" - ) - } - onChange={onChange} - value={value} - /> - )} - /> -
- -
+ +
+
+
-
- -
-
+
setIsChangeIdentifierRequired(false)} validations={{ required: "Identifier is required", validate: (value) => - /^[A-Z]+$/.test(value) || "Identifier must be uppercase text.", + /^[A-Z]+$/.test(value) || "Identifier must be in uppercase.", minLength: { value: 1, message: "Identifier must at least be of 1 character", @@ -291,47 +304,110 @@ export const CreateProjectModal: React.FC = (props) => { }} />
- ( - k === value.toString()) - ? NETWORK_CHOICES[value.toString() as keyof typeof NETWORK_CHOICES] - : "Select network" - } - width="w-full" - input - > - {Object.keys(NETWORK_CHOICES).map((key) => ( - - {NETWORK_CHOICES[key as keyof typeof NETWORK_CHOICES]} - - ))} - - )} - /> +
+