diff --git a/web/components/issues/issue-layouts/properties/dropdown-template.tsx b/web/components/issues/issue-layouts/properties/dropdown-template.tsx deleted file mode 100644 index 25cbaa942..000000000 --- a/web/components/issues/issue-layouts/properties/dropdown-template.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from "react"; -// headless ui -import { Combobox } from "@headlessui/react"; -// lucide icons -import { ChevronDown, Search, X, Check } from "lucide-react"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; - -interface IFiltersOption { - id: string; - title: string; -} - -export interface IIssuePropertyState { - options: IFiltersOption[]; - value?: any; - onChange?: (id: any, data: IFiltersOption) => void; - disabled?: boolean; - - className?: string; - buttonClassName?: string; - optionsClassName?: string; - dropdownArrow?: boolean; - - children?: any; -} - -export const IssuePropertyState: React.FC = ({ - options, - value, - onChange, - disabled, - - className, - buttonClassName, - optionsClassName, - dropdownArrow = true, - - children, -}) => { - const dropdownBtn = React.useRef(null); - const dropdownOptions = React.useRef(null); - - const [isOpen, setIsOpen] = React.useState(false); - const [search, setSearch] = React.useState(""); - - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - - const selectedOption: IFiltersOption | null | undefined = - (value && options.find((person: IFiltersOption) => person.id === value)) || null; - - const filteredOptions: IFiltersOption[] = - search === "" - ? options && options.length > 0 - ? options - : [] - : options && options.length > 0 - ? options.filter((person: IFiltersOption) => - person.title.toLowerCase().replace(/\s+/g, "").includes(search.toLowerCase().replace(/\s+/g, "")) - ) - : []; - - return ( - { - if (onChange && selectedOption) onChange(data, selectedOption); - }} - disabled={disabled} - > - {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - } else if (isOpen) setIsOpen(false); - - return ( - <> - - {children ? ( - children - ) : ( -
{(selectedOption && selectedOption?.title) || "Select option"}
- )} - - {dropdownArrow && !disabled && ( -
- -
- )} -
- - {options && options.length > 0 ? ( -
- -
-
- -
- -
- setSearch(e.target.value)} - placeholder="Search" - displayValue={(assigned: any) => assigned?.name} - /> -
- - {search && search.length > 0 && ( -
setSearch("")} - className="flex-shrink-0 flex justify-center items-center w-[16px] h-[16px] rounded-sm cursor-pointer hover:bg-custom-background-80" - > - -
- )} -
- -
- {filteredOptions ? ( - filteredOptions.length > 0 ? ( - filteredOptions.map((option) => ( - - `cursor-pointer select-none truncate rounded px-1 py-1.5 ${ - active || selected ? "bg-custom-background-80" : "" - } ${selected ? "text-custom-text-100" : "text-custom-text-200"}` - } - > - {({ selected }) => ( -
-
{option.title}
- {selected && ( -
- -
- )} -
- )} -
- )) - ) : ( - -

No matching results

-
- ) - ) : ( -

Loading...

- )} -
-
-
- ) : ( -

No options available.

- )} - - ); - }} -
- ); -}; diff --git a/web/components/issues/issue-layouts/properties/labels.tsx b/web/components/issues/issue-layouts/properties/labels.tsx index dcc884b19..a623d26cd 100644 --- a/web/components/issues/issue-layouts/properties/labels.tsx +++ b/web/components/issues/issue-layouts/properties/labels.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react-lite"; // components -import { LabelSelect } from "components/project"; +import { LabelSelect } from "components/labels"; // types import { IIssueLabels } from "types"; diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx index 603928494..eaa6dab98 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/label-column.tsx @@ -1,7 +1,7 @@ import React from "react"; // components -import { LabelSelect } from "components/project"; +import { LabelSelect } from "components/labels"; // hooks import useSubIssue from "hooks/use-sub-issue"; // types diff --git a/web/components/issues/view-select/assignee.tsx b/web/components/issues/view-select/assignee.tsx deleted file mode 100644 index 3be22b25d..000000000 --- a/web/components/issues/view-select/assignee.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useState } from "react"; -import { useRouter } from "next/router"; -// services -import { TrackEventService } from "services/track_event.service"; -// ui -import { AssigneesList, Avatar } from "components/ui"; -import { CustomSearchSelect, Tooltip } from "@plane/ui"; -import { User2 } from "lucide-react"; -// types -import { IUser, IIssue } from "types"; -// hooks -import useProjectMembers from "hooks/use-project-members"; - -type Props = { - issue: IIssue; - partialUpdateIssue: (formData: Partial, issue: IIssue) => void; - position?: "left" | "right"; - tooltipPosition?: "top" | "bottom"; - selfPositioned?: boolean; - customButton?: boolean; - user: IUser | undefined; - isNotAllowed: boolean; -}; - -const trackEventService = new TrackEventService(); - -export const ViewAssigneeSelect: React.FC = ({ - issue, - partialUpdateIssue, - // position = "left", - // selfPositioned = false, - tooltipPosition = "top", - user, - isNotAllowed, - customButton = false, -}) => { - const [fetchAssignees, setFetchAssignees] = useState(false); - - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { members } = useProjectMembers(workspaceSlug?.toString(), issue.project, fetchAssignees); - - const options = members?.map((member: any) => ({ - value: member.member.id, - query: member.member.display_name, - content: ( -
- - {member.member.display_name} -
- ), - })); - - const assigneeLabel = ( - 0 - ? issue.assignee_details.map((assignee) => assignee?.display_name).join(", ") - : "No Assignee" - } - > -
- {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( -
- -
- ) : ( -
- -
- )} -
-
- ); - - return ( - { - const newData = issue.assignees ?? []; - - if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); - else newData.push(data); - - partialUpdateIssue({ assignees: data }, issue); - - trackEventService.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_ASSIGNEE", - user as IUser - ); - }} - options={options} - {...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })} - multiple - noChevron - disabled={isNotAllowed} - onOpen={() => setFetchAssignees(true)} - width="w-full min-w-[12rem]" - /> - ); -}; diff --git a/web/components/issues/view-select/index.ts b/web/components/issues/view-select/index.ts index 99191eb3d..8eb88cb0d 100644 --- a/web/components/issues/view-select/index.ts +++ b/web/components/issues/view-select/index.ts @@ -1,6 +1,3 @@ -export * from "./assignee"; export * from "./due-date"; export * from "./estimate"; -export * from "./label"; -export * from "./priority"; -export * from "./start-date"; \ No newline at end of file +export * from "./start-date"; diff --git a/web/components/issues/view-select/label.tsx b/web/components/issues/view-select/label.tsx deleted file mode 100644 index c7f71e4f1..000000000 --- a/web/components/issues/view-select/label.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { useState, FC } from "react"; -import { useRouter } from "next/router"; -import useSWR from "swr"; -// services -import { IssueLabelService } from "services/issue"; -// component -import { CreateLabelModal } from "components/labels"; -// ui -import { CustomSearchSelect, Tooltip } from "@plane/ui"; -// icons -import { Plus, Tag } from "lucide-react"; -// types -import { IUser, IIssue, IIssueLabels } from "types"; -// fetch-keys -import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; - -type Props = { - issue: IIssue; - partialUpdateIssue: (formData: Partial, issue: IIssue) => void; - position?: "left" | "right"; - selfPositioned?: boolean; - tooltipPosition?: "top" | "bottom"; - customButton?: boolean; - user: IUser | undefined; - isNotAllowed: boolean; -}; - -const issueLabelStore = new IssueLabelService(); - -export const ViewLabelSelect: FC = ({ - issue, - partialUpdateIssue, - // position = "left", - // selfPositioned = false, - tooltipPosition = "top", - user, - isNotAllowed, - customButton = false, -}) => { - const [labelModal, setLabelModal] = useState(false); - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { data: issueLabels } = useSWR( - projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null, - workspaceSlug && projectId - ? () => issueLabelStore.getProjectIssueLabels(workspaceSlug as string, projectId as string) - : null - ); - - const options = issueLabels?.map((label) => ({ - value: label.id, - query: label.name, - content: ( -
- - {label.name} -
- ), - })); - - const labelsLabel = ( - 0 - ? issue.labels - .map((labelId) => { - const label = issueLabels?.find((l) => l.id === labelId); - - return label?.name ?? ""; - }) - .join(", ") - : "No label" - } - > -
- {issue.labels.length > 0 ? ( - <> - {issue.labels.slice(0, 4).map((labelId, index) => { - const label = issueLabels?.find((l) => l.id === labelId); - - return ( -
- -
- ); - })} - {issue.labels.length > 4 ? +{issue.labels.length - 4} : null} - - ) : ( - <> - - - )} -
-
- ); - - const footerOption = ( - - ); - - return ( - <> - {projectId && ( - setLabelModal(false)} - projectId={projectId.toString()} - user={user} - /> - )} - { - partialUpdateIssue({ labels: data }, issue); - }} - options={options} - {...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })} - multiple - noChevron - disabled={isNotAllowed} - footerOption={footerOption} - width="w-full min-w-[12rem]" - /> - - ); -}; diff --git a/web/components/issues/view-select/priority.tsx b/web/components/issues/view-select/priority.tsx deleted file mode 100644 index e5da02b68..000000000 --- a/web/components/issues/view-select/priority.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React from "react"; -import { useRouter } from "next/router"; -// services -import { TrackEventService } from "services/track_event.service"; -// ui -import { CustomSelect, Tooltip, PriorityIcon } from "@plane/ui"; -// helpers -import { capitalizeFirstLetter } from "helpers/string.helper"; -// types -import { IUser, IIssue, TIssuePriorities } from "types"; -// constants -import { PRIORITIES } from "constants/project"; - -type Props = { - issue: IIssue; - partialUpdateIssue: (formData: Partial, issue: IIssue) => void; - position?: "left" | "right"; - tooltipPosition?: "top" | "bottom"; - selfPositioned?: boolean; - noBorder?: boolean; - user: IUser | undefined; - isNotAllowed: boolean; -}; - -const trackEventService = new TrackEventService(); - -export const ViewPrioritySelect: React.FC = ({ - issue, - partialUpdateIssue, - // position = "left", - tooltipPosition = "top", - // selfPositioned = false, - noBorder = false, - user, - isNotAllowed, -}) => { - const router = useRouter(); - const { workspaceSlug } = router.query; - - return ( - { - partialUpdateIssue({ priority: data }, issue); - trackEventService.trackIssuePartialPropertyUpdateEvent( - { - workspaceSlug, - workspaceId: issue.workspace, - projectId: issue.project_detail.id, - projectIdentifier: issue.project_detail.identifier, - projectName: issue.project_detail.name, - issueId: issue.id, - }, - "ISSUE_PROPERTY_UPDATE_PRIORITY", - user as IUser - ); - }} - maxHeight="md" - customButton={ - - } - noChevron - disabled={isNotAllowed} - > - {PRIORITIES?.map((priority) => ( - - <> - - {priority ?? "None"} - - - ))} - - ); -}; diff --git a/web/components/labels/index.ts b/web/components/labels/index.ts index 0c0629d92..4a012c6ba 100644 --- a/web/components/labels/index.ts +++ b/web/components/labels/index.ts @@ -1,6 +1,7 @@ export * from "./create-label-modal"; export * from "./create-update-label-inline"; export * from "./delete-label-modal"; +export * from "./label-select"; export * from "./labels-list-modal"; export * from "./single-label-group"; export * from "./single-label"; diff --git a/web/components/project/label-select.tsx b/web/components/labels/label-select.tsx similarity index 100% rename from web/components/project/label-select.tsx rename to web/components/labels/label-select.tsx diff --git a/web/components/project/index.ts b/web/components/project/index.ts index 8fd14b987..f3b44c143 100644 --- a/web/components/project/index.ts +++ b/web/components/project/index.ts @@ -7,7 +7,6 @@ export * from "./delete-project-modal"; export * from "./form-loader"; export * from "./form"; export * from "./join-project-modal"; -export * from "./label-select"; export * from "./leave-project-modal"; export * from "./member-select"; export * from "./members-select"; diff --git a/web/contexts/theme.context.tsx b/web/contexts/theme.context.tsx deleted file mode 100644 index e0c32328e..000000000 --- a/web/contexts/theme.context.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { createContext, useCallback, useReducer, useEffect } from "react"; -import { useRouter } from "next/router"; -// swr -import useSWR from "swr"; -// components -import ToastAlert from "components/toast-alert"; -// hooks -import useUserAuth from "hooks/use-user-auth"; -// services -import { ProjectService } from "services/project"; -// fetch-keys -import { USER_PROJECT_VIEW } from "constants/fetch-keys"; -// helper -import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; -// constants - -export const themeContext = createContext({} as ContextType); - -// services -const projectService = new ProjectService(); - -type ThemeProps = { - collapsed: boolean; -}; - -type ReducerActionType = { - type: "TOGGLE_SIDEBAR" | "REHYDRATE_THEME"; - payload?: Partial; -}; - -type ContextType = { - collapsed: boolean; - toggleCollapsed: () => void; -}; - -type StateType = { - collapsed: boolean; -}; -type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; - -export const initialState: StateType = { - collapsed: false, -}; - -export const reducer: ReducerFunctionType = (state, action) => { - const { type, payload } = action; - - switch (type) { - case "TOGGLE_SIDEBAR": - const newState = { - ...state, - collapsed: !state.collapsed, - }; - localStorage.setItem("collapsed", JSON.stringify(newState.collapsed)); - return newState; - - case "REHYDRATE_THEME": { - let collapsed: any = localStorage.getItem("collapsed"); - collapsed = collapsed ? JSON.parse(collapsed) : false; - return { ...initialState, ...payload, collapsed }; - } - - default: { - return state; - } - } -}; - -export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [state, dispatch] = useReducer(reducer, initialState); - const { user } = useUserAuth(null); - - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - - const { data: myViewProps } = useSWR( - workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, - workspaceSlug && projectId - ? () => projectService.projectMemberMe(workspaceSlug as string, projectId as string) - : null - ); - - const toggleCollapsed = useCallback(() => { - dispatch({ - type: "TOGGLE_SIDEBAR", - }); - }, []); - - useEffect(() => { - dispatch({ - type: "REHYDRATE_THEME", - payload: myViewProps?.view_props as any, - }); - }, [myViewProps]); - - useEffect(() => { - const theme = localStorage.getItem("theme"); - - if (theme) { - if (theme === "custom") { - if (user && user.theme.palette) { - applyTheme( - user.theme.palette !== ",,,," ? user.theme.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", - user.theme.darkPalette - ); - } - } else unsetCustomCssVariables(); - } - }, [user]); - - return ( - - - {children} - - ); -}; diff --git a/web/contexts/workspace.context.tsx b/web/contexts/workspace.context.tsx deleted file mode 100644 index f389381f9..000000000 --- a/web/contexts/workspace.context.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { FC, ReactElement, createContext } from "react"; - -import { useRouter } from "next/router"; - -import useSWR from "swr"; - -// services -import { WorkspaceService } from "services/workspace.service"; -// types -import { IWorkspace } from "types"; -// constants -import { USER_WORKSPACES } from "constants/fetch-keys"; - -export interface WorkspaceProviderProps { - children: ReactElement; -} - -export interface WorkspaceContextProps { - workspaces: IWorkspace[]; - activeWorkspace: IWorkspace | undefined; - mutateWorkspaces: () => void; -} - -// services -const workspaceService = new WorkspaceService(); - -export const WorkspaceContext = createContext({} as WorkspaceContextProps); - -export const WorkspaceProvider: FC = (props) => { - const { children } = props; - // router - const router = useRouter(); - const { workspaceSlug } = router.query; - // API to fetch user information - const { data = [], error, mutate } = useSWR(USER_WORKSPACES, () => workspaceService.userWorkspaces()); - // active workspace - const activeWorkspace = data?.find((w) => w.slug === workspaceSlug); - - return ( - - {children} - - ); -};