feat: app dir migration (#4743)

* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* fix: adding profile settings layout

* fix: errors on app dir.

* chore: remove pages routes.

* chore: add sign-in/ sign-up, invitations, onboarding pages.

* [WEB-1374] fix: clear changes made on modal close (#4555)

* [WEB-1480] fix: preserve page access when making a copy (#4568)

* [WEB-1465] fix: theme fluctuation on initial load. (#4638)

* [WEB-1445] fix: issue creation on sub groups when cycle/ module grouping is applied. (#4636)

* [WEB-1244] fix: add better image insertion and replacement logic in the editor (#4508)

* fix: add better image insertion and replacement logic

* refactor: image handling in editor

* chore: remove passing uploadKey around

* refactor: remove unused code

* fix: redundant files removed

* fix: add is editor ready to discard api to control behvaiours from our app

* fix: focus issues and image insertion position when not using slash command

* fix: import order fixed

* fix: notification mark all as read (#4643)

* chore: remove enter key extension (#4648)

* [WEB-1467] chore: run the API's required to bootstrap the application in parallel. (#4642)

* [WEB - 1482] fix: uploads when using block storages other than s3 and minio (#4647)

* fix: minio storage and redirection

* dev: disconnect web url and app base url configuration.

* fix: negate check while trying to discard (#4653)

* fix: email notification preferences (#4656)

* [WEB-1493] chore: product tour asset and app sidebar quick action hover (#4655)

* chore: product tour asset updated

* fix: app sidebar quick action hover

* fix: project state setting state name remove camel case logic (#4652)

* [WEB-1419] chore: enable module creation with dates older than today. (#4659)

* [WEB-1216] chore: increase module empty state for consistency. (#4658)

* fix: build errors

* [WEB-1235] chore: module and cycle sidebar graph improvement (#4650)

* chore: module and cycle sidebar graph improvement

* chore: code refactor

* [WEB-1424] chore: page and view logo implementation, and emoji/icon (#4662)

* [WEB-1424] chore: page and view logo implementation, and emoji/icon picker improvement (#4583)

* chore: added logo_props

* chore: logo props in cycles, views and modules

* chore: emoji icon picker types updated

* chore: info icon added to plane ui package

* chore: icon color adjust helper function added

* style: icon picker ui improvement and default color options updated

* chore: update page logo action added in store

* chore: emoji code to unicode helper function added

* chore: common logo renderer component added

* chore: app header project logo updated

* chore: project logo updated across platform

* chore: page logo picker added

* chore: control link component improvement

* chore: list item improvement

* chore: emoji picker component updated

* chore: space app and package logo prop type updated

* chore: migration

* chore: logo added to project view

* chore: page logo picker added in create modal and breadcrumbs

* chore: view logo picker added in create modal and updated breadcrumbs

* fix: build error

* chore: AIO docker images for preview deployments (#4605)

* fix: adding single docker base file

* action added

* fix action

* dockerfile.base modified

* action fix

* dockerfile

* fix: base aio dockerfile

* fix: dockerfile.base

* fix: dockerfile base

* fix: modified folder structure

* fix: action

* fix: dockerfile

* fix: dockerfile.base

* fix: supervisor file name changed

* fix: base dockerfile updated

* fix dockerfile base

* fix: base dockerfile

* fix: docker files

* fix: base dockerfile

* update base image

* modified docker aio base

* aio base modified to debian-12-slim

* fixes

* finalize the dockerfiles with volume exposure

* modified the aio build and dockerfile

* fix: codacy suggestions implemented

* fix: codacy fix

* update aio build action

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: merge conflict

* chore: lucide react added to planu ui package

* chore: new emoji picker component added with lucid icon and code refactor

* chore: logo component updated

* chore: emoji picker updated for pages and views

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: build error

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>

* refactor: drag handle component (#4663)

* refactor: checkbox ui component (#4665)

* [WEB-1325] chore: refactor inbox issue store to avoid data loss. (#4640)

* [WEB-1325] chore: refactor inbox issue store to avoid data loss.

* chore: inbox store improvement.

* chore: priority dropdown accepts undefined (#4666)

* chore: added buttonClassName prop to label dropdown (#4671)

* chore: created new constants for marketing website page links (#4670)

* chore: added a prop to render default state conditionally (#4669)

* [WEB-1501] dev: multiple select core components (#4667)

* dev: multiple select core components

* chore: added export statement

* chore: created a new constant for archivable state groups (#4668)

* chore: added primary variant to the alert modal (#4664)

* [WEB-1436] chore: pages improvement. (#4657)

* add empty state if no pages are available.
* set access to private in create page modal when the modal is open form private tab.

* [WEB-1440] chore: update cycle empty state to use project level access. (#4672)

* fix: checkbox ui component (#4675)

* fix: ai buttons overlapping issue (#4621)

* [WEB - 1500] chore: add extra fields on instance and create changelog table to store release change logs (#4673)

* chore: add extra fields on instance and create changelog table to store release change logs

* dev: rename new_version to latest_version

* [WEB - 1505] chore: alter instance id field (#4676)

* chore: instance id

* dev: update to max length

* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* feat: creating new app dir structure for web app

* fix: moving few pages to app dir

* fix: errors on app dir.

* chore: remove pages routes.

* chore: add sign-in/ sign-up, invitations, onboarding pages.

* fix: instance serializer

* fix: instance register script  (#4681)

* fix: instance register script

* dev: remove api key and add latest version and current version in types

* [WEB-1492] fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add (#4682)

* fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add

* fix: updated braces in conditions

* fix: inbox issue store update logic. (#4683)

* chore: update package version

* [WEB-1184] feat: issue bulk operations (#4674)

* feat: issue bulk operations

* style: bulk operations action bar

* chore: remove edition separation

* style: fix overlapping of response container in AI popover. (#4684)

* [WEB-1498] style: fix comments reaction alignment. (#4686)

* [WEB-1503] chore: add `autofocus` to name field in inline create/ update state component. (#4685)

* [WEB-1312] fix: trim file name before uploading (#4661)

* fix: trim file name before uploading

* fix: check the cursor position before inserting image

* dev: add trimming for file assets

* dev: add filename validation above if

* dev: make the validation to 50 to support user uploads

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>

* [WEB-1481] fix: multiple API calls in inbox issues on closed issues tab. (#4691)

* fix: multiple API calls on scroll and closed issues tab.

* fix: pagination loader on initial load.

* feat: Add components required for estimates (#4690)

* Add sortable, radio and typography components

* Remove stray css classes

* Prevent drag of items from other draggable

* Minor cleanup

* Update yarn.lock

* Remove radio input component as it was build on
headless ui v2.0.0 and now we are using v1.7.0

* Fix build errors

* Update dependencies in use memo.

* [WEB-1521] chore: add configuration to enable/disable sign-ups. (#4697)

* fix: regenerating lock file

* fix: docker image build errors

* fix: remove `setupInterceptors` to avoid circular dependency.

* chore: migrate all `accounts` related routes.

* chore: migrate all `profiles` related routes.

* chore: workspace invitation and onboarding migration / fixes.

* chore: installation provider migrations.

* regression: focus changing issue with the peek overview editor (#4700)

* [WEB-1459] chore: save users all / favorite project list collapse state into localstorage. (#4701)

* [WEB-1501] chore: update selected entity details on entities list change (#4702)

* chore: update selected entity detials on entities list change

* chore: addd selectionHelpers as a prop

* [WEB-1517] chore: remove drag handle from list drag block (#4698)

* remove drag handle from list drag block

* align list group header with list item

* rearrange chevron for list subissues and rearrange spaces

* adding default draggable property to control link

* remove unnecessary dependencies for useEffect

* fix: email validation  (#4707)

* fix: email validation on complete login or sign up functionality

* dev: add try catch block

* dev: split up code

* dev: empty return

* fix: cache invalidation on new members invite (#4699)

* fix: build test pull request running on non draft PRs (#4708)

* fix: cache invalidation on new members invite (#4699)

* fix: add version max length (#4713)

* chore: migrations for `routing` hooks.

* [WEB-1533] chore: fix alignment issues in List and Spreadsheet view (#4714)

* fix alignment issues in List and Spreadsheet view

* fix spreadsheet indentation

* chore: migration for workspace dashboard/ views/ analytics/ settings and active-cycles.

* chore: handle undefined identifier case

* fix: Overflowing loader in issue edit modal (#4720)

* [WEB-1529] chore: workspace sidebar updates. (#4710)

* fix: temporary fix exiting lines with slashes (#4725)

* [WEB-1537] fix: inline code block size fixed for headers, etc (#4709)

* fix: inline code block size fixed for headers, etc

* feat: persisting focus accurately post converting the code block into text

* fix: typo in error handling

* [WEB-1526] feat: add auto merge behaviour to task lists and fix infinite backspace case (#4703)

* feat: add auto merge behaviour to task lists

* fix: unhandled cases for taskItem and taskList

* fix: css task list such that toggling task list doesn't shift things

* fix: task list jumps around while trying create/delete things in between two task lists

* fix: remove filtering for generic transactions i.e. transactions with some meta data while tying to join things

* chore: migration for profile activity along with headers refactor.

* [WEB-1201] dev: dropdowns (#4721)

* chore: lodash package added

* chore: dropdown key down hook added

* dev: dropdown component

* chore: build error and code refactor

* chore: readme file updated

* chore: added disabled prop to multiple select components (#4724)

* chore: added disabled prop to mutliple select group hoc

* style: fix empty space

* fix: don't add  as a sub-issue if parent has been removed (#4731)

* fix: member list item custom menu placement (#4729)

* [WEB-1535] chore: project logo picker improvement (#4718)

* chore: emoji icon picker improvement

* chore: emoji icon picker improvement

* fix: resolved border flicker on issue title (#4727)

* chore: profile activity empty state added (#4732)

* [WEB-1481] fix: inbox issue list update after changing issue status. (#4715)

* style: fix ux copy style on project feature preview page. (#4734)

* chore: remove clear seleciton logic on escape key press (#4735)

* chore: migrations for projects and project issues.

* chore: issue and properties filter dropdown improvement (#4733)

* save all filters and properties for views (#4728)

* chore: migrations for issue details route.

* chore: migration for cycle routes.

* chore: migration for module routes.

* chore: migrations for project views routes.

* chore: migrations for project pages routes.

* chore: migration for project inbox routes.

* chore: migration for project settings routes.

* chore: migrations for draft issues routes.

* chore: migrations for project archives routes.

* chore: remove unused headers.

* temp: comment out auth constant and use-reload-confirmation code to avoid errors.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
sriram veeraghanta 2024-06-10 12:37:38 +05:30 committed by GitHub
parent 59fdd611e4
commit 5d807db69e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
417 changed files with 5196 additions and 3918 deletions

View file

@ -1,6 +1,6 @@
import React, { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter, useSearchParams } from "next/navigation";
import { IEmailCheckData } from "@plane/types";
// components
import {
@ -35,7 +35,12 @@ type TAuthRoot = {
export const AuthRoot: FC<TAuthRoot> = observer((props) => {
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug: workspaceSlug, error_code } = router.query;
const searchParams = useSearchParams();
// query params
const emailParam = searchParams.get("email");
const invitation_id = searchParams.get("invitation_id");
const workspaceSlug = searchParams.get("slug");
const error_code = searchParams.get("error_code");
// props
const { authMode: currentAuthMode } = props;
// states
@ -130,7 +135,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
setErrorInfo(undefined);
setEmail("");
setAuthStep(EAuthSteps.EMAIL);
router.push(currentAuthMode === EAuthModes.SIGN_IN ? `/` : "/sign-up", undefined, { shallow: true });
router.push(currentAuthMode === EAuthModes.SIGN_IN ? `/` : "/sign-up");
};
// generating the unique code

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { Trash2 } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks

View file

@ -1,5 +1,5 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { useSearchParams } from "next/navigation";
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
@ -13,8 +13,8 @@ export type GithubOAuthButtonProps = {
};
export const GithubOAuthButton: FC<GithubOAuthButtonProps> = (props) => {
const { query } = useRouter();
const { next_path } = query;
const searchParams = useSearchParams();
const next_path = searchParams.get("next_path");
const { text } = props;
// hooks
const { resolvedTheme } = useTheme();

View file

@ -1,5 +1,5 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { useSearchParams } from "next/navigation";
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
@ -12,8 +12,8 @@ export type GoogleOAuthButtonProps = {
};
export const GoogleOAuthButton: FC<GoogleOAuthButtonProps> = (props) => {
const { query } = useRouter();
const { next_path } = query;
const searchParams = useSearchParams();
const next_path = searchParams.get("next_path");
const { text } = props;
// hooks
const { resolvedTheme } = useTheme();

View file

@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { useForm } from "react-hook-form";
import useSWR from "swr";
import { IAnalyticsParams } from "@plane/types";
@ -30,8 +30,7 @@ const analyticsService = new AnalyticsService();
export const CustomAnalytics: React.FC<Props> = observer((props) => {
const { additionalParams, fullScreen } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId } = useParams();
const { control, watch, setValue } = useForm({ defaultValues });

View file

@ -1,4 +1,4 @@
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { mutate } from "swr";
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
@ -22,8 +22,7 @@ type Props = {
export const CustomAnalyticsMainContent: React.FC<Props> = (props) => {
const { analytics, error, fullScreen, params } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const yAxisKey = params.y_axis === "issue_count" ? "count" : "estimate";
const barGraphData = convertResponseToBarGraphData(analytics?.distribution, params);

View file

@ -1,4 +1,4 @@
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
// ui
@ -12,8 +12,7 @@ type Props = {
};
export const SelectSegment: React.FC<Props> = ({ value, onChange, params, analyticsOptions }) => {
const router = useRouter();
const { cycleId, moduleId } = router.query;
const { cycleId, moduleId } = useParams();
return (
<CustomSelect

View file

@ -1,4 +1,4 @@
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
// ui
@ -14,8 +14,7 @@ type Props = {
export const SelectXAxis: React.FC<Props> = (props) => {
const { value, onChange, params, analyticsOptions } = props;
const router = useRouter();
const { cycleId, moduleId } = router.query;
const { cycleId, moduleId } = useParams();
return (
<CustomSelect

View file

@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// components
import { Logo } from "@/components/common";
// constants
@ -10,8 +10,7 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
import { useCycle, useMember, useModule, useProject } from "@/hooks/store";
export const CustomAnalyticsSidebarHeader = observer(() => {
const router = useRouter();
const { projectId, cycleId, moduleId } = router.query;
const { projectId, cycleId, moduleId } = useParams();
const { getProjectById } = useProject();
const { getCycleById } = useCycle();

View file

@ -1,6 +1,6 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { mutate } from "swr";
// icons
import { CalendarDays, Download, RefreshCw } from "lucide-react";
@ -31,8 +31,7 @@ const analyticsService = new AnalyticsService();
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
const { analytics, params, isProjectLevel = false } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { workspaceSlug, projectId, cycleId, moduleId } = useParams();
// store hooks
const { data: currentUser } = useUser();
const { workspaceProjectIds, getProjectById } = useProject();

View file

@ -1,4 +1,4 @@
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWR from "swr";
@ -21,8 +21,7 @@ const analyticsService = new AnalyticsService();
export const ScopeAndDemand: React.FC<Props> = (props) => {
const { fullScreen = true } = props;
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { workspaceSlug, projectId, cycleId, moduleId } = useParams();
const isProjectLevel = projectId ? true : false;

View file

@ -1,5 +1,5 @@
import { useState, FC } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { mutate } from "swr";
// types
import { IApiToken } from "@plane/types";
@ -24,9 +24,8 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
const { isOpen, onClose, tokenId } = props;
// states
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
const handleClose = () => {
onClose();

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { mutate } from "swr";
// types
import { IApiToken } from "@plane/types";
@ -30,8 +30,7 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
const [neverExpires, setNeverExpires] = useState<boolean>(false);
const [generatedToken, setGeneratedToken] = useState<IApiToken | null | undefined>(null);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const handleClose = () => {
onClose();

View file

@ -1,7 +1,7 @@
import { FC } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams, usePathname } from "next/navigation";
// constants
import { ARCHIVES_TAB_LIST } from "@/constants/archives";
// hooks
@ -9,9 +9,9 @@ import { useProject } from "@/hooks/store";
export const ArchiveTabsList: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const activeTab = router.pathname.split("/").pop();
const { workspaceSlug, projectId } = useParams();
const pathname = usePathname();
const activeTab = pathname.split("/").pop();
// store hooks
const { getProjectById } = useProject();

View file

@ -2,7 +2,7 @@ import React from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useSearchParams } from "next/navigation";
// hooks
import { useUser } from "@/hooks/store";
// layouts
@ -19,8 +19,8 @@ type Props = {
export const NotAuthorizedView: React.FC<Props> = observer((props) => {
const { actionButton, type } = props;
// router
const { query } = useRouter();
const { next_path } = query;
const searchParams = useSearchParams();
const next_path = searchParams.get("next_path");
// hooks
const { data: currentUser } = useUser();

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// hooks
import { ClipboardList } from "lucide-react";
import { Button } from "@plane/ui";
@ -19,8 +19,7 @@ export const JoinProject: React.FC = () => {
} = useUser();
const { fetchProjects } = useProject();
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId } = useParams();
const handleJoin = () => {
if (!workspaceSlug || !projectId) return;

View file

@ -1,5 +1,5 @@
import React from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// react-hook-form
import { Controller, useForm } from "react-hook-form";
// headless ui
@ -19,8 +19,7 @@ type Props = {
};
export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen, handleClose, handleChange }) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId } = useParams();
const {
formState: { errors, isSubmitting },

View file

@ -1,6 +1,8 @@
"use client";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2, Users } from "lucide-react";
import { TIssue } from "@plane/types";
// hooks
@ -24,8 +26,7 @@ type Props = {
export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
const { closePalette, issueDetails, pages, setPages, setPlaceholder, setSearchTerm } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId, issueId } = useParams();
// hooks
const {
issues: { updateIssue },
@ -60,7 +61,7 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
};
const copyIssueUrlToClipboard = () => {
if (!router.query.issueId) return;
if (!issueId) return;
const url = new URL(window.location.href);
copyTextToClipboard(url.href)

View file

@ -1,6 +1,8 @@
"use client";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { Check } from "lucide-react";
import { TIssue } from "@plane/types";
// mobx store
@ -17,9 +19,8 @@ type Props = {
export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// router params
const { workspaceSlug, projectId } = useParams();
// store
const {
issues: { updateIssue },

View file

@ -1,6 +1,8 @@
"use client"; ``
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { Check } from "lucide-react";
import { TIssue, TIssuePriorities } from "@plane/types";
// mobx store
@ -18,10 +20,8 @@ type Props = {
export const ChangeIssuePriority: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// router params
const { workspaceSlug, projectId } = useParams();
const {
issues: { updateIssue },
} = useIssues(EIssuesStoreType.PROJECT);

View file

@ -1,6 +1,8 @@
"use client";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// hooks
import { Check } from "lucide-react";
import { TIssue } from "@plane/types";
@ -18,9 +20,8 @@ type Props = {
export const ChangeIssueState: React.FC<Props> = observer((props) => {
const { closePalette, issue } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// router params
const { workspaceSlug, projectId } = useParams();
// store hooks
const {
issues: { updateIssue },

View file

@ -1,5 +1,7 @@
"use client";
import { Command } from "cmdk";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { IWorkspaceSearchResults } from "@plane/types";
// helpers
import { commandGroups } from "@/components/command-palette";

View file

@ -1,3 +1,5 @@
"use client";
import React, { FC, useEffect, useState } from "react";
import { Command } from "cmdk";
import { observer } from "mobx-react";

View file

@ -1,7 +1,9 @@
"use client";
import { Command } from "cmdk";
// hooks
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams, useRouter } from "next/navigation";
// constants
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "@/constants/workspace";
import { useUser } from "@/hooks/store";
@ -14,7 +16,8 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) =
const { closePalette } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
// mobx store
const {
membership: { currentWorkspaceRole },

View file

@ -1,7 +1,9 @@
"use client";
import React, { useEffect, useState } from "react";
import { Command } from "cmdk";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, useRouter } from "next/navigation";
import useSWR from "swr";
import { FolderPlus, Search, Settings } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
@ -67,7 +69,8 @@ export const CommandModal: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// router params
const { workspaceSlug, projectId, issueId } = useParams();
const page = pages[pages.length - 1];

View file

@ -1,6 +1,8 @@
"use client";
import React, { useCallback, useEffect, FC, useMemo } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, usePathname, useRouter } from "next/navigation";
import useSWR from "swr";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
@ -32,7 +34,10 @@ const issueService = new IssueService();
export const CommandPalette: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query;
// router params
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = useParams();
// pathname
const pathname = usePathname();
// store hooks
const { toggleSidebar } = useAppTheme();
const { setTrackElement } = useEventTracker();
@ -260,7 +265,7 @@ export const CommandPalette: FC = observer(() => {
return () => document.removeEventListener("keydown", handleKeyDown);
}, [handleKeyDown]);
const isDraftIssue = router?.asPath?.includes("draft-issues") || false;
const isDraftIssue = pathname?.includes("draft-issues") || false;
if (!currentUser) return null;

View file

@ -1,3 +1,5 @@
"use client";
import { FC, useState, Fragment } from "react";
import { Search, X } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";

View file

@ -1,6 +1,6 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// store hooks
// icons
import {
@ -29,8 +29,8 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// types
export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
const { isMobile } = usePlatformOS();
return (
@ -61,8 +61,8 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
};
const UserLink = ({ activity }: { activity: IIssueActivity }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
return (
<a
@ -725,8 +725,8 @@ type ActivityMessageProps = {
};
export const ActivityMessage = ({ activity, showIssue = false }: ActivityMessageProps) => {
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
return (
<>

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef, useCallback } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { useDropzone } from "react-dropzone";
import { Control, Controller } from "react-hook-form";
import useSWR from "swr";
@ -58,9 +58,8 @@ export const ImagePickerPopover: React.FC<Props> = observer((props) => {
});
// refs
const ref = useRef<HTMLDivElement>(null);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// router params
const { workspaceSlug } = useParams();
// store hooks
const { config } = useInstance();
const { currentWorkspace } = useWorkspace();

View file

@ -1,5 +1,5 @@
import React, { FC } from "react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
// ui
import { ControlLink, Tooltip } from "@plane/ui";
// helpers

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { SubmitHandler, useForm } from "react-hook-form";
import useSWR from "swr";
import { Search } from "lucide-react";
@ -36,9 +36,8 @@ const issueService = new IssueService();
export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
const { isOpen, onClose } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// router params
const { workspaceSlug, projectId } = useParams();
// hooks
const { getProjectById } = useProject();
const {

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef, Fragment, Ref } from "react";
import { Placement } from "@popperjs/core";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { Controller, useForm } from "react-hook-form"; // services
import { usePopper } from "react-popper";
// ui
@ -42,8 +42,7 @@ export const GptAssistantPopover: React.FC<Props> = (props) => {
const editorRef = useRef<any>(null);
const responseRef = useRef<any>(null);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
// popper
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "auto",

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, usePathname } from "next/navigation";
import { useDropzone } from "react-dropzone";
import { UserCircle2 } from "lucide-react";
import { Transition, Dialog } from "@headlessui/react";
@ -31,8 +31,8 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const pathname = usePathname();
// store hooks
const { config } = useInstance();
const { currentWorkspace } = useWorkspace();
@ -55,7 +55,7 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
};
const handleSubmit = async () => {
if (!image || (!workspaceSlug && router.pathname !== "/onboarding")) return;
if (!image || (!workspaceSlug && pathname !== "/onboarding")) return;
setIsImageUploading(true);

View file

@ -1,24 +1,18 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react";
import { Menu } from "lucide-react";
import { useAppTheme } from "@/hooks/store";
type Props = {
onClick?: () => void;
};
export const SidebarHamburgerToggle: FC<Props> = observer((props) => {
const { onClick } = props;
export const SidebarHamburgerToggle: FC = observer(() => {
// store hooks
const { toggleSidebar } = useAppTheme();
return (
<div
className="group flex h-7 w-7 flex-shrink-0 cursor-pointer items-center justify-center rounded bg-custom-background-80 transition-all hover:bg-custom-background-90 md:hidden"
onClick={() => {
if (onClick) onClick();
else toggleSidebar();
}}
onClick={() => toggleSidebar()}
>
<Menu size={14} className="text-custom-text-200 transition-all group-hover:text-custom-text-100" />
</div>

View file

@ -1,7 +1,7 @@
import { useRef } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { Users } from "lucide-react";
// ui
import { Avatar, AvatarGroup, setPromiseToast } from "@plane/ui";
@ -24,8 +24,7 @@ export const UpcomingCycleListItem: React.FC<Props> = observer((props) => {
// refs
const parentRef = useRef(null);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId } = useParams();
// store hooks
const { captureEvent } = useEventTracker();
const { addCycleToFavorites, getCycleById, removeCycleFromFavorites } = useCycle();

View file

@ -1,6 +1,6 @@
import { FC, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// icons
import { ListFilter, Search, X } from "lucide-react";
// types
@ -18,8 +18,7 @@ import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
export const ArchivedCyclesHeader: FC = observer(() => {
// router
const router = useRouter();
const { projectId } = router.query;
const { projectId } = useParams();
// refs
const inputRef = useRef<HTMLInputElement>(null);
// hooks

View file

@ -1,5 +1,5 @@
import { useState, Fragment } from "react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui";

View file

@ -1,6 +1,6 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWR from "swr";
// types
import { TCycleFilters } from "@plane/types";
@ -17,8 +17,7 @@ import { useCycle, useCycleFilter } from "@/hooks/store";
export const ArchivedCycleLayoutRoot: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { workspaceSlug, projectId } = useParams();
// hooks
const { fetchArchivedCycles, currentProjectArchivedCycleIds, loader } = useCycle();
// cycle filters hook

View file

@ -1,7 +1,7 @@
import { FC, MouseEvent, useRef } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { CalendarCheck2, CalendarClock, Info, MoveRight } from "lucide-react";
// types
import type { TCycleGroups } from "@plane/types";
@ -16,6 +16,7 @@ import { CYCLE_FAVORITED, CYCLE_UNFAVORITED } from "@/constants/event-tracker";
import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { findHowManyDaysLeft, getDate, renderFormattedDate } from "@/helpers/date-time.helper";
import { generateQueryParams } from "@/helpers/router.helper";
// hooks
import { useEventTracker, useCycle, useUser, useMember } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
@ -32,6 +33,8 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
const parentRef = useRef(null);
// router
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
// store
const { captureEvent } = useEventTracker();
const {
@ -130,21 +133,14 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
};
const openCycleOverview = (e: MouseEvent<HTMLButtonElement>) => {
const { query } = router;
e.preventDefault();
e.stopPropagation();
if (query.peekCycle) {
delete query.peekCycle;
router.push({
pathname: router.pathname,
query: { ...query },
});
const query = generateQueryParams(searchParams, ['peekCycle']);
if (searchParams.has('peekCycle')) {
router.push(`${pathname}?${query}`);
} else {
router.push({
pathname: router.pathname,
query: { ...query, peekCycle: cycleId },
});
router.push(`${pathname}?${query}&peekCycle=${cycleId}`);
}
};

View file

@ -1,204 +0,0 @@
import { useCallback, useState } from "react";
import router from "next/router";
// icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { CustomMenu } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store";
export const CycleMobileHeader = () => {
const [analyticsModal, setAnalyticsModal] = useState(false);
const { getCycleById } = useCycle();
const layouts = [
{ key: "list", title: "List", icon: List },
{ key: "kanban", title: "Kanban", icon: Kanban },
{ key: "calendar", title: "Calendar", icon: Calendar },
];
const { workspaceSlug, projectId, cycleId } = router.query;
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
// store hooks
const { currentProjectDetails } = useProject();
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.CYCLE);
const activeLayout = issueFilters?.displayFilters?.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
cycleId.toString()
);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId || !cycleId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ [key]: newValues },
cycleId.toString()
);
},
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
cycleId.toString()
);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId || !cycleId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
cycleId.toString()
);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
cycleDetails={cycleDetails ?? undefined}
/>
<div className="flex justify-evenly py-2 border-b border-custom-border-200 md:hidden bg-custom-background-100">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm"
placement="bottom-start"
customButton={<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
{layouts.map((layout, index) => (
<CustomMenu.MenuItem
key={ISSUE_LAYOUTS[index].key}
onClick={() => {
handleLayoutChange(ISSUE_LAYOUTS[index].key);
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Filters"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Filters
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
</div>
<div className="flex flex-grow justify-center border-l border-custom-border-200 items-center text-custom-text-200 text-sm">
<FiltersDropdown
title="Display"
placement="bottom-end"
menuButton={
<span className="flex items-center text-custom-text-200 text-sm">
Display
<ChevronDown className="text-custom-text-200 h-4 w-4 ml-2" />
</span>
}
>
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["cycle"]}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
</div>
<span
onClick={() => setAnalyticsModal(true)}
className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200"
>
Analytics
</span>
</div>
</>
);
};

View file

@ -1,7 +1,8 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
// hooks
import { generateQueryParams } from "@/helpers/router.helper";
import { useCycle } from "@/hooks/store";
// components
import { CycleDetailsSidebar } from "./sidebar";
@ -15,18 +16,17 @@ type Props = {
export const CyclePeekOverview: React.FC<Props> = observer(({ projectId, workspaceSlug, isArchived = false }) => {
// router
const router = useRouter();
const { peekCycle } = router.query;
const pathname = usePathname();
const searchParams = useSearchParams();
const peekCycle = searchParams.get("peekCycle");
// refs
const ref = React.useRef(null);
// store hooks
const { fetchCycleDetails, fetchArchivedCycleDetails } = useCycle();
const handleClose = () => {
delete router.query.peekCycle;
router.push({
pathname: router.pathname,
query: { ...router.query },
});
const query = generateQueryParams(searchParams, ['peekCycle']);
router.push(`${pathname}?${query}`);
};
useEffect(() => {

View file

@ -1,52 +0,0 @@
import { observer } from "mobx-react";
// ui
import { List } from "lucide-react";
import { CustomMenu } from "@plane/ui";
// icon
// constants
import { CYCLE_VIEW_LAYOUTS } from "@/constants/cycle";
// hooks
import { useCycleFilter, useProject } from "@/hooks/store";
const CyclesListMobileHeader = observer(() => {
const { currentProjectDetails } = useProject();
// hooks
const { updateDisplayFilters } = useCycleFilter();
return (
<div className="flex justify-center sm:hidden">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-custom-text-200 text-sm py-2 border-b border-custom-border-200 bg-custom-sidebar-background-100"
// placement="bottom-start"
customButton={
<span className="flex items-center gap-2">
<List className="h-4 w-4" />
<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>
</span>
}
customButtonClassName="flex flex-grow justify-center items-center text-custom-text-200 text-sm"
closeOnSelect
>
{CYCLE_VIEW_LAYOUTS.map((layout) => {
if (layout.key == "gantt") return;
return (
<CustomMenu.MenuItem
key={layout.key}
onClick={() => {
updateDisplayFilters(currentProjectDetails!.id, {
layout: layout.key,
});
}}
className="flex items-center gap-2"
>
<layout.icon className="w-3 h-3" />
<div className="text-custom-text-300">{layout.title}</div>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</div>
);
});
export default CyclesListMobileHeader;

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, useRouter, useSearchParams } from "next/navigation";
// types
import { ICycle } from "@plane/types";
// ui
@ -29,7 +29,9 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
const { deleteCycle } = useCycle();
// router
const router = useRouter();
const { cycleId, peekCycle } = router.query;
const { cycleId } = useParams();
const searchParams = useSearchParams();
const peekCycle = searchParams.get("peekCycle");
const formSubmit = async () => {
if (!cycle) return;

View file

@ -1,6 +1,6 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
// hooks
// ui
import { Tooltip, ContrastIcon } from "@plane/ui";

View file

@ -1,6 +1,6 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { ICycle } from "@plane/types";
// hooks
import { CycleGanttBlock } from "@/components/cycles";
@ -19,8 +19,7 @@ type Props = {
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
const { cycleIds } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
// store hooks
const { getCycleById, updateCycleDetails } = useCycle();

View file

@ -1,6 +1,6 @@
import { FC, MouseEvent, useRef } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
// icons
import { Check, Info } from "lucide-react";
// types
@ -10,6 +10,8 @@ import { CircularProgressIndicator } from "@plane/ui";
// components
import { ListItem } from "@/components/core/list";
import { CycleListItemAction } from "@/components/cycles/list";
// helpers
import { generateQueryParams } from "@/helpers/router.helper";
// hooks
import { useCycle } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
@ -31,6 +33,8 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
const parentRef = useRef(null);
// router
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
// hooks
const { isMobile } = usePlatformOS();
// store hooks
@ -59,21 +63,14 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
// handlers
const openCycleOverview = (e: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
const { query } = router;
e.preventDefault();
e.stopPropagation();
if (query.peekCycle) {
delete query.peekCycle;
router.push({
pathname: router.pathname,
query: { ...query },
});
const query = generateQueryParams(searchParams, ["peekCycle"]);
if (searchParams.has("peekCycle")) {
router.push(`${pathname}?${query}`);
} else {
router.push({
pathname: router.pathname,
query: { ...query, peekCycle: cycleId },
});
router.push(`${pathname}?${query}&peekCycle=${cycleId}`);
}
};

View file

@ -1,6 +1,6 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
// icons
import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react";
// ui

View file

@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from "react";
import isEmpty from "lodash/isEmpty";
import isEqual from "lodash/isEqual";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
// icons
import {
@ -60,7 +60,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId, peekCycle } = router.query;
const { workspaceSlug, projectId } = useParams();
const searchParams = useSearchParams();
const peekCycle = searchParams.get("peekCycle");
// store hooks
const { setTrackElement, captureCycleEvent } = useEventTracker();
const {

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { AlertCircle, Search, X } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
@ -28,8 +28,7 @@ export const TransferIssuesModal: React.FC<Props> = observer((props) => {
issues: { transferIssuesFromCycle },
} = useIssues(EIssuesStoreType.CYCLE);
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { workspaceSlug, projectId, cycleId } = useParams();
const transferIssue = async (payload: any) => {
if (!workspaceSlug || !projectId || !cycleId) return;

View file

@ -1,7 +1,7 @@
import React from "react";
import isEmpty from "lodash/isEmpty";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWR from "swr";
@ -24,8 +24,7 @@ const cycleService = new CycleService();
export const TransferIssues: React.FC<Props> = (props) => {
const { handleClick, disabled = false } = props;
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query;
const { workspaceSlug, projectId, cycleId } = useParams();
const { data: cycleDetails } = useSWR(
cycleId ? CYCLE_DETAILS(cycleId as string) : null,

View file

@ -1,7 +1,7 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { TIssuesByPriorityWidgetFilters, TIssuesByPriorityWidgetResponse } from "@plane/types";
// hooks
// components

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { TIssuesByStateGroupsWidgetFilters, TIssuesByStateGroupsWidgetResponse, TStateGroups } from "@plane/types";
// hooks
import {

View file

@ -0,0 +1,291 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
// types
import { IEstimate, IEstimateFormData } from "@plane/types";
// ui
import { Button, Input, TextArea, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { EModalPosition, EModalWidth, ModalCore } from "@/components/core";
// helpers
import { checkDuplicates } from "@/helpers/array.helper";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = {
isOpen: boolean;
handleClose: () => void;
data?: IEstimate;
};
const defaultValues = {
name: "",
description: "",
value1: "",
value2: "",
value3: "",
value4: "",
value5: "",
value6: "",
};
type FormValues = typeof defaultValues;
export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
const { handleClose, data, isOpen } = props;
// router
const { workspaceSlug, projectId } = useParams();
// store hooks
const { createEstimate, updateEstimate } = useEstimate();
// form info
const {
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<FormValues>({
defaultValues,
});
const onClose = () => {
handleClose();
reset();
};
const handleCreateEstimate = async (payload: IEstimateFormData) => {
if (!workspaceSlug || !projectId) return;
await createEstimate(workspaceSlug.toString(), projectId.toString(), payload)
.then(() => {
onClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message:
errorString ?? err.status === 400
? "Estimate with that name already exists. Please try again with another name."
: "Estimate could not be created. Please try again.",
});
});
};
const handleUpdateEstimate = async (payload: IEstimateFormData) => {
if (!workspaceSlug || !projectId || !data) return;
await updateEstimate(workspaceSlug.toString(), projectId.toString(), data.id, payload)
.then(() => {
onClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be updated. Please try again.",
});
});
};
const onSubmit = async (formData: FormValues) => {
if (!formData.name || formData.name === "") {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate title cannot be empty.",
});
return;
}
if (
formData.value1 === "" ||
formData.value2 === "" ||
formData.value3 === "" ||
formData.value4 === "" ||
formData.value5 === "" ||
formData.value6 === ""
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot be empty.",
});
return;
}
if (
formData.value1.length > 20 ||
formData.value2.length > 20 ||
formData.value3.length > 20 ||
formData.value4.length > 20 ||
formData.value5.length > 20 ||
formData.value6.length > 20
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate point cannot have more than 20 characters.",
});
return;
}
if (
checkDuplicates([
formData.value1,
formData.value2,
formData.value3,
formData.value4,
formData.value5,
formData.value6,
])
) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Estimate points cannot have duplicate values.",
});
return;
}
const payload: IEstimateFormData = {
estimate: {
name: formData.name,
description: formData.description,
},
estimate_points: [],
};
for (let i = 0; i < 6; i++) {
const point = {
key: i,
value: formData[`value${i + 1}` as keyof FormValues],
};
if (data)
payload.estimate_points.push({
id: data.points[i].id,
...point,
});
else payload.estimate_points.push({ ...point });
}
if (data) await handleUpdateEstimate(payload);
else await handleCreateEstimate(payload);
};
useEffect(() => {
if (data)
reset({
...defaultValues,
...data,
value1: data.points[0]?.value,
value2: data.points[1]?.value,
value3: data.points[2]?.value,
value4: data.points[3]?.value,
value5: data.points[4]?.value,
value6: data.points[5]?.value,
});
else reset({ ...defaultValues });
}, [data, reset]);
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-5 p-5">
<div className="text-xl font-medium text-custom-text-200">{data ? "Update" : "Create"} Estimate</div>
<div className="space-y-3">
<div>
<Controller
control={control}
name="name"
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="name"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
className="w-full text-base"
/>
)}
/>
</div>
<div>
<Controller
name="description"
control={control}
render={({ field: { value, onChange } }) => (
<TextArea
id="description"
name="description"
value={value}
placeholder="Description"
onChange={onChange}
className="w-full text-base resize-none min-h-24"
hasError={Boolean(errors?.description)}
/>
)}
/>
</div>
</div>
{/* list of all the points */}
{/* since they are all the same, we can use a loop to render them */}
<div className="grid grid-cols-3 gap-3">
{Array(6)
.fill(0)
.map((_, i) => (
<div className="flex items-center" key={i}>
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">{i + 1}</span>
<span className="rounded-r-lg bg-custom-background-100">
<Controller
control={control}
name={`value${i + 1}` as keyof FormValues}
rules={{
maxLength: {
value: 20,
message: "Estimate point must at most be of 20 characters",
},
}}
render={({ field: { value, onChange, ref } }) => (
<Input
ref={ref}
type="text"
value={value}
onChange={onChange}
id={`value${i + 1}`}
name={`value${i + 1}`}
placeholder={`Point ${i + 1}`}
className="w-full rounded-l-none"
hasError={Boolean(errors[`value${i + 1}` as keyof FormValues])}
/>
)}
/>
</span>
</span>
</div>
))}
</div>
</div>
<div className="px-5 py-4 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data ? (isSubmitting ? "Updating" : "Update Estimate") : isSubmitting ? "Creating" : "Create Estimate"}
</Button>
</div>
</form>
</ModalCore>
);
});

View file

@ -0,0 +1,78 @@
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// types
import { IEstimate } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { AlertModalCore } from "@/components/core";
// hooks
import { useEstimate } from "@/hooks/store";
type Props = {
isOpen: boolean;
data: IEstimate | null;
handleClose: () => void;
};
export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
const { isOpen, handleClose, data } = props;
// states
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
// router
const { workspaceSlug, projectId } = useParams();
// store hooks
const { deleteEstimate } = useEstimate();
const handleEstimateDelete = async () => {
if (!workspaceSlug || !projectId) return;
setIsDeleteLoading(true);
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const estimateId = data?.id!;
await deleteEstimate(workspaceSlug.toString(), projectId.toString(), estimateId)
.then(() => {
handleClose();
})
.catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be deleted. Please try again",
});
})
.finally(() => setIsDeleteLoading(false));
};
useEffect(() => {
setIsDeleteLoading(false);
}, [isOpen]);
const onClose = () => {
setIsDeleteLoading(false);
handleClose();
};
return (
<AlertModalCore
handleClose={onClose}
handleSubmit={handleEstimateDelete}
isSubmitting={isDeleteLoading}
isOpen={isOpen}
title="Delete Estimate"
content={
<>
Are you sure you want to delete estimate-{" "}
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>
{""}? All of the data related to the estiamte will be permanently removed. This action cannot be undone.
</>
}
/>
);
});

View file

@ -0,0 +1,120 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { IEstimate } from "@plane/types";
// store hooks
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
import { EmptyState } from "@/components/empty-state";
import { CreateUpdateEstimateModal, DeleteEstimateModal, EstimateListItem } from "@/components/estimates";
import { EmptyStateType } from "@/constants/empty-state";
import { orderArrayBy } from "@/helpers/array.helper";
import { useEstimate, useProject } from "@/hooks/store";
// components
// ui
// types
// helpers
// constants
export const EstimatesList: React.FC = observer(() => {
// states
const [estimateFormOpen, setEstimateFormOpen] = useState(false);
const [estimateToDelete, setEstimateToDelete] = useState<string | null>(null);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
// router
const { workspaceSlug, projectId } = useParams();
// store hooks
const { updateProject, currentProjectDetails } = useProject();
const { projectEstimates, getProjectEstimateById } = useEstimate();
const editEstimate = (estimate: IEstimate) => {
setEstimateFormOpen(true);
// Order the points array by key before updating the estimate to update state
setEstimateToUpdate({
...estimate,
points: orderArrayBy(estimate.points, "key"),
});
};
const disableEstimates = () => {
if (!workspaceSlug || !projectId) return;
updateProject(workspaceSlug.toString(), projectId.toString(), { estimate: null }).catch((err) => {
const error = err?.error;
const errorString = Array.isArray(error) ? error[0] : error;
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: errorString ?? "Estimate could not be disabled. Please try again",
});
});
};
return (
<>
<CreateUpdateEstimateModal
isOpen={estimateFormOpen}
data={estimateToUpdate}
handleClose={() => {
setEstimateFormOpen(false);
setEstimateToUpdate(undefined);
}}
/>
<DeleteEstimateModal
isOpen={!!estimateToDelete}
handleClose={() => setEstimateToDelete(null)}
data={getProjectEstimateById(estimateToDelete!)}
/>
<section className="flex items-center justify-between border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Estimates</h3>
<div className="col-span-12 space-y-5 sm:col-span-7">
<div className="flex items-center gap-2">
<Button
variant="primary"
onClick={() => {
setEstimateFormOpen(true);
setEstimateToUpdate(undefined);
}}
size="sm"
>
Add Estimate
</Button>
{currentProjectDetails?.estimate && (
<Button variant="neutral-primary" onClick={disableEstimates} size="sm">
Disable Estimates
</Button>
)}
</div>
</div>
</section>
{projectEstimates ? (
projectEstimates.length > 0 ? (
<section className="h-full overflow-y-auto bg-custom-background-100">
{projectEstimates.map((estimate) => (
<EstimateListItem
key={estimate.id}
estimate={estimate}
editEstimate={(estimate) => editEstimate(estimate)}
deleteEstimate={(estimateId) => setEstimateToDelete(estimateId)}
/>
))}
</section>
) : (
<div className="h-full w-full py-8">
<EmptyState type={EmptyStateType.PROJECT_SETTINGS_ESTIMATE} />
</div>
)
) : (
<Loader className="mt-5 space-y-5">
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
</Loader>
)}
</>
);
});

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useParams, useRouter } from "next/navigation";
import { Dialog, Transition } from "@headlessui/react";
import { IUser, IImporterService } from "@plane/types";
// hooks
@ -30,7 +30,7 @@ export const Exporter: React.FC<Props> = observer((props) => {
const [isSelectOpen, setIsSelectOpen] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
// store hooks
const { workspaceProjectIds, getProjectById } = useProject();

View file

@ -2,7 +2,7 @@ import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useRouter, useParams, useSearchParams } from "next/navigation";
import useSWR, { mutate } from "swr";
// icons
import { MoveLeft, MoveRight, RefreshCw } from "lucide-react";
@ -30,7 +30,9 @@ const IntegrationGuide = observer(() => {
const [cursor, setCursor] = useState<string | undefined>(`10:0:0`);
// router
const router = useRouter();
const { workspaceSlug, provider } = router.query;
const { workspaceSlug } = useParams();
const searchParams = useSearchParams();
const provider = searchParams.get("provider");
// store hooks
const { data: currentUser } = useUser();

View file

@ -1,305 +0,0 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
// icons
import { ArrowRight, PanelRight } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper";
// hooks
import {
useEventTracker,
useCycle,
useLabel,
useMember,
useProject,
useProjectState,
useUser,
useIssues,
useCommandPalette,
} from "@/hooks/store";
import useLocalStorage from "@/hooks/use-local-storage";
import { usePlatformOS } from "@/hooks/use-platform-os";
const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getCycleById } = useCycle();
// derived values
const cycle = getCycleById(cycleId);
//
if (!cycle) return null;
return (
<CustomMenu.MenuItem key={cycle.id}>
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`} className="flex items-center gap-1.5">
<ContrastIcon className="h-3 w-3" />
{truncateText(cycle.name, 40)}
</Link>
</CustomMenu.MenuItem>
);
};
export const CycleIssuesHeader: React.FC = observer(() => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string;
projectId: string;
cycleId: string;
};
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
issues: { issuesCount },
} = useIssues(EIssuesStoreType.CYCLE);
const { currentProjectCycleIds, getCycleById } = useCycle();
const { toggleCreateIssueModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const { isMobile } = usePlatformOS();
const activeLayout = issueFilters?.displayFilters?.layout;
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`);
};
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, cycleId);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }, cycleId);
},
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, cycleId);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, cycleId);
},
[workspaceSlug, projectId, cycleId, updateFilters]
);
// derived values
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
const isCompletedCycle = cycleDetails?.status?.toLocaleLowerCase() === "completed";
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
cycleDetails={cycleDetails ?? undefined}
/>
<div className="relative z-[15] w-full items-center gap-x-2 gap-y-4">
<div className="flex justify-between bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<span>
<span className="hidden md:block">
<BreadcrumbLink
label={currentProjectDetails?.name ?? "Project"}
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
</span>
<Link
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
className="block pl-2 text-custom-text-300 md:hidden"
>
...
</Link>
</span>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label="Cycles"
href={`/${workspaceSlug}/projects/${projectId}/cycles`}
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu
label={
<>
<ContrastIcon className="h-3 w-3" />
<div className="flex w-auto max-w-[70px] items-center gap-2 truncate sm:max-w-[200px]">
<p className="truncate">{cycleDetails?.name && cycleDetails.name}</p>
{issuesCount && issuesCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issuesCount} ${
issuesCount > 1 ? "issues" : "issue"
} in this cycle`}
position="bottom"
>
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
{issuesCount}
</span>
</Tooltip>
) : null}
</div>
</>
}
className="ml-1.5 flex-shrink-0 truncate"
placement="bottom-start"
>
{currentProjectCycleIds?.map((cycleId) => <CycleDropdownOption key={cycleId} cycleId={cycleId} />)}
</CustomMenu>
}
/>
</Breadcrumbs>
</div>
<div className="hidden items-center gap-2 md:flex ">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["cycle"]}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
{canUserCreateIssue && (
<>
<Button onClick={() => setAnalyticsModal(true)} variant="neutral-primary" size="sm">
Analytics
</Button>
{!isCompletedCycle && (
<Button
onClick={() => {
setTrackElement("Cycle issues page");
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
}}
size="sm"
>
Add Issue
</Button>
)}
</>
)}
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80"
onClick={toggleSidebar}
>
<ArrowRight className={`h-4 w-4 duration-300 ${isSidebarCollapsed ? "-rotate-180" : ""}`} />
</button>
</div>
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 md:hidden"
onClick={toggleSidebar}
>
<PanelRight className={cn("h-4 w-4", !isSidebarCollapsed ? "text-[#3E63DD]" : "text-custom-text-200")} />
</button>
</div>
</div>
</>
);
});

View file

@ -1,74 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// ui
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { CyclesViewHeader } from "@/components/cycles";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
export const CyclesHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const { toggleCreateCycleModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const canUserCreateCycle =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label={currentProjectDetails?.name ?? "Project"}
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Cycles" icon={<ContrastIcon className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</div>
{canUserCreateCycle && currentProjectDetails && (
<div className="flex items-center gap-3">
<CyclesViewHeader projectId={currentProjectDetails.id} />
<Button
variant="primary"
size="sm"
onClick={() => {
setTrackElement("Cycles page");
toggleCreateCycleModal(true);
}}
>
<div className="hidden sm:block">Add</div> Cycle
</Button>
</div>
)}
</div>
);
});

View file

@ -1,145 +0,0 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
// ui
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues";
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useLabel, useMember, useUser, useIssues } from "@/hooks/store";
export const GlobalIssuesHeader: React.FC = observer(() => {
// states
const [createViewModal, setCreateViewModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
// store hooks
const {
issuesFilter: { filters, updateFilters },
} = useIssues(EIssuesStoreType.GLOBAL);
const {
membership: { currentWorkspaceRole },
} = useUser();
const { workspaceLabels } = useLabel();
const {
workspace: { workspaceMemberIds },
} = useMember();
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !globalViewId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.FILTERS,
{ [key]: newValues },
globalViewId.toString()
);
},
[workspaceSlug, issueFilters, updateFilters, globalViewId]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !globalViewId) return;
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
globalViewId.toString()
);
},
[workspaceSlug, updateFilters, globalViewId]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !globalViewId) return;
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.DISPLAY_PROPERTIES,
property,
globalViewId.toString()
);
},
[workspaceSlug, updateFilters, globalViewId]
);
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
<div className="relative z-[15] flex h-[3.75rem] w-full items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="relative flex gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label={`All Issues`} icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
</div>
<div className="flex items-center gap-2">
<>
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
labels={workspaceLabels ?? undefined}
memberIds={workspaceMemberIds ?? undefined}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_LAYOUT.my_issues.spreadsheet}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
</FiltersDropdown>
</>
{isAuthorizedUser && (
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
Add View
</Button>
)}
</div>
</div>
</>
);
});

View file

@ -1,24 +0,0 @@
export * from "./cycle-issues";
export * from "./global-issues";
export * from "./module-issues";
export * from "./project-inbox";
export * from "./project-issues";
export * from "./project-view-issues";
export * from "./project-views";
export * from "./workspace-analytics";
export * from "./workspace-dashboard";
export * from "./projects";
export * from "./profile-preferences";
export * from "./profile-settings";
export * from "./cycles";
export * from "./modules-list";
export * from "./project-settings";
export * from "./workspace-settings";
export * from "./page-details";
export * from "./pages";
export * from "./project-draft-issues";
export * from "./project-archived-issue-details";
export * from "./project-archives";
export * from "./project-issue-details";
export * from "./user-profile";
export * from "./workspace-active-cycles";

View file

@ -1,312 +0,0 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
// icons
import { ArrowRight, PanelRight } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper";
// hooks
import {
useEventTracker,
useLabel,
useMember,
useModule,
useProject,
useProjectState,
useUser,
useIssues,
useCommandPalette,
} from "@/hooks/store";
import { useIssuesActions } from "@/hooks/use-issues-actions";
import useLocalStorage from "@/hooks/use-local-storage";
import { usePlatformOS } from "@/hooks/use-platform-os";
const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { getModuleById } = useModule();
// derived values
const moduleDetail = getModuleById(moduleId);
if (!moduleDetail) return null;
return (
<CustomMenu.MenuItem key={moduleDetail.id}>
<Link
href={`/${workspaceSlug}/projects/${projectId}/modules/${moduleDetail.id}`}
className="flex items-center gap-1.5"
>
<DiceIcon className="h-3 w-3" />
{truncateText(moduleDetail.name, 40)}
</Link>
</CustomMenu.MenuItem>
);
};
export const ModuleIssuesHeader: React.FC = observer(() => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query;
// hooks
const { isMobile } = usePlatformOS();
// store hooks
const {
issuesFilter: { issueFilters },
issues: { issuesCount },
} = useIssues(EIssuesStoreType.MODULE);
const { updateFilters } = useIssuesActions(EIssuesStoreType.MODULE);
const { projectModuleIds, getModuleById } = useModule();
const { toggleCreateIssueModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectLabels } = useLabel();
const { projectStates } = useProjectState();
const {
project: { projectMemberIds },
} = useMember();
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`);
};
const activeLayout = issueFilters?.displayFilters?.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[projectId, moduleId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(projectId.toString(), EIssueFilterType.FILTERS, { [key]: newValues });
},
[projectId, moduleId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[projectId, moduleId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[projectId, moduleId, updateFilters]
);
// derived values
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
moduleDetails={moduleDetails ?? undefined}
/>
<div className="relative z-[15] items-center gap-x-2 gap-y-4">
<div className="flex justify-between bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<span>
<span className="hidden md:block">
<BreadcrumbLink
label={currentProjectDetails?.name ?? "Project"}
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
</span>
<Link
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
className="block pl-2 text-custom-text-300 md:hidden"
>
...
</Link>
</span>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/modules`}
label="Modules"
icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu
label={
<>
<DiceIcon className="h-3 w-3" />
<div className="flex w-auto max-w-[70px] items-center gap-2 truncate sm:max-w-[200px]">
<p className="truncate">{moduleDetails?.name && moduleDetails.name}</p>
{issuesCount && issuesCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issuesCount} ${
issuesCount > 1 ? "issues" : "issue"
} in this module`}
position="bottom"
>
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
{issuesCount}
</span>
</Tooltip>
) : null}
</div>
</>
}
className="ml-1.5 flex-shrink-0"
placement="bottom-start"
>
{projectModuleIds?.map((moduleId) => <ModuleDropdownOption key={moduleId} moduleId={moduleId} />)}
</CustomMenu>
}
/>
</Breadcrumbs>
</div>
<div className="flex items-center gap-2">
<div className="hidden gap-2 md:flex">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
ignoreGroupedFilters={["module"]}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
</div>
{canUserCreateIssue && (
<>
<Button
className="hidden md:block"
onClick={() => setAnalyticsModal(true)}
variant="neutral-primary"
size="sm"
>
Analytics
</Button>
<Button
className="hidden sm:flex"
onClick={() => {
setTrackElement("Module issues page");
toggleCreateIssueModal(true, EIssuesStoreType.MODULE);
}}
size="sm"
>
Add Issue
</Button>
</>
)}
<button
type="button"
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80"
onClick={toggleSidebar}
>
<ArrowRight
className={`hidden h-4 w-4 duration-300 md:block ${isSidebarCollapsed ? "-rotate-180" : ""}`}
/>
<PanelRight
className={cn(
"block h-4 w-4 md:hidden",
!isSidebarCollapsed ? "text-[#3E63DD]" : "text-custom-text-200"
)}
/>
</button>
</div>
</div>
</div>
</>
);
});

View file

@ -1,74 +0,0 @@
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// ui
import { Breadcrumbs, Button, DiceIcon } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { ModuleViewHeader } from "@/components/modules";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
export const ModulesListHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const { toggleCreateModuleModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
// auth
const canUserCreateModule =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Modules" icon={<DiceIcon className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-2">
<ModuleViewHeader />
{canUserCreateModule && (
<Button
variant="primary"
size="sm"
onClick={() => {
setTrackElement("Modules page");
toggleCreateModuleModal(true);
}}
>
<div className="hidden sm:block">Add</div> Module
</Button>
)}
</div>
</div>
);
});

View file

@ -1,76 +0,0 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { FileText } from "lucide-react";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// helpers
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
import { EPageAccess } from "@/constants/page";
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store";
export const PagesHeader = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, type: pageType } = router.query;
// store hooks
const { toggleCreatePageModal } = useCommandPalette();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { setTrackElement } = useEventTracker();
const canUserCreatePage =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Pages" icon={<FileText className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</div>
{canUserCreatePage && (
<div className="flex items-center gap-2">
<Button
variant="primary"
size="sm"
onClick={() => {
setTrackElement("Project pages page");
toggleCreatePageModal({
isOpen: true,
pageAccess: pageType === "private" ? EPageAccess.PRIVATE : EPageAccess.PUBLIC,
});
}}
>
Add Page
</Button>
</div>
)}
</div>
);
});

View file

@ -1,15 +0,0 @@
// components
import { Breadcrumbs } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
export const ProfilePreferencesHeader = () => (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label="My Profile Preferences" />} />
</Breadcrumbs>
</div>
</div>
</div>
);

View file

@ -1,35 +0,0 @@
import { FC } from "react";
// ui
import { Settings } from "lucide-react";
import { Breadcrumbs } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
interface IProfileSettingHeader {
title: string;
}
export const ProfileSettingsHeader: FC<IProfileSettingHeader> = (props) => {
const { title } = props;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href="/profile"
label="My Profile"
icon={<Settings className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label={title} />} />
</Breadcrumbs>
</div>
</div>
</div>
);
};

View file

@ -1,98 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import useSWR from "swr";
// hooks
import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui";
import { BreadcrumbLink, Logo } from "@/components/common";
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
import { useProject } from "@/hooks/store";
// components
// ui
// types
import { IssueArchiveService } from "@/services/issue";
// constants
// services
// helpers
// components
const issueArchiveService = new IssueArchiveService();
export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, archivedIssueId } = router.query;
// store hooks
const { currentProjectDetails } = useProject();
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
workspaceSlug && projectId && archivedIssueId
? () =>
issueArchiveService.retrieveArchivedIssue(
workspaceSlug as string,
projectId as string,
archivedIssueId as string
)
: null
);
return (
<div className="relative z-10 flex h-14 w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
label="Archives"
icon={<ArchiveIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
label="Issues"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label={
currentProjectDetails && issueDetails
? `${currentProjectDetails.identifier}-${issueDetails.sequence_id}`
: ""
}
/>
}
/>
</Breadcrumbs>
</div>
</div>
</div>
);
});

View file

@ -1,95 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// ui
import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
import { PROJECT_ARCHIVES_BREADCRUMB_LIST } from "@/constants/archives";
import { EIssuesStoreType } from "@/constants/issue";
// hooks
import { useIssues, useProject } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
export const ProjectArchivesHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const activeTab = router.pathname.split("/").pop();
// store hooks
const {
issuesFilter: { issueFilters },
} = useIssues(EIssuesStoreType.ARCHIVED);
const { currentProjectDetails } = useProject();
// hooks
const { isMobile } = usePlatformOS();
const issueCount = currentProjectDetails
? !issueFilters?.displayFilters?.sub_issue && currentProjectDetails.archived_sub_issues
? currentProjectDetails.archived_issues - currentProjectDetails.archived_sub_issues
: currentProjectDetails.archived_issues
: undefined;
const activeTabBreadcrumbDetail =
PROJECT_ARCHIVES_BREADCRUMB_LIST[activeTab as keyof typeof PROJECT_ARCHIVES_BREADCRUMB_LIST];
return (
<div className="relative z-10 flex h-14 w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-2.5">
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/archives/issues`}
label="Archives"
icon={<ArchiveIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
{activeTabBreadcrumbDetail && (
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label={activeTabBreadcrumbDetail.label}
icon={<activeTabBreadcrumbDetail.icon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
)}
</Breadcrumbs>
{activeTab === "issues" && issueCount && issueCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's archived`}
position="bottom"
>
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
{issueCount}
</span>
</Tooltip>
) : null}
</div>
</div>
</div>
);
});

View file

@ -1,170 +0,0 @@
import { FC, useCallback } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
export const ProjectDraftIssueHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.DRAFT);
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const { isMobile } = usePlatformOS();
const activeLayout = issueFilters?.displayFilters?.layout;
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, projectId, issueFilters, updateFilters]
);
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, projectId, updateFilters]
);
const issueCount = currentProjectDetails
? !issueFilters?.displayFilters?.sub_issue && currentProjectDetails.draft_sub_issues
? currentProjectDetails.draft_issues - currentProjectDetails.draft_sub_issues
: currentProjectDetails.draft_issues
: undefined;
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-2.5">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Draft Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
{issueCount && issueCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's draft`}
position="bottom"
>
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
{issueCount}
</span>
</Tooltip>
) : null}
</div>
<div className="ml-auto flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
</div>
</div>
</div>
);
});

View file

@ -1,79 +0,0 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { RefreshCcw } from "lucide-react";
// ui
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { InboxIssueCreateEditModalRoot } from "@/components/inbox";
// hooks
import { useProject, useProjectInbox } from "@/hooks/store";
export const ProjectInboxHeader: FC = observer(() => {
// states
const [createIssueModal, setCreateIssueModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const { currentProjectDetails } = useProject();
const { loader } = useProjectInbox();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-4">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Inbox" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
{loader === "pagination-loading" && (
<div className="flex items-center gap-1.5 text-custom-text-300">
<RefreshCcw className="h-3.5 w-3.5 animate-spin" />
<p className="text-sm">Syncing...</p>
</div>
)}
</div>
</div>
{currentProjectDetails?.inbox_view && workspaceSlug && projectId && (
<div className="flex items-center gap-2">
<InboxIssueCreateEditModalRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
modalState={createIssueModal}
handleModalClose={() => setCreateIssueModal(false)}
issue={undefined}
/>
<Button variant="primary" size="sm" onClick={() => setCreateIssueModal(true)}>
Add Issue
</Button>
</div>
)}
</div>
);
});

View file

@ -1,85 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// hooks
import { PanelRight } from "lucide-react";
import { Breadcrumbs, LayersIcon } from "@plane/ui";
import { BreadcrumbLink, Logo } from "@/components/common";
import { cn } from "@/helpers/common.helper";
import { useAppTheme, useIssueDetail, useProject } from "@/hooks/store";
// ui
// helpers
// services
// constants
// components
export const ProjectIssueDetailsHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
// store hooks
const { currentProjectDetails } = useProject();
const { issueDetailSidebarCollapsed, toggleIssueDetailSidebar } = useAppTheme();
const {
issue: { getIssueById },
} = useIssueDetail();
// derived values
const issueDetails = issueId ? getIssueById(issueId.toString()) : undefined;
const isSidebarCollapsed = issueDetailSidebarCollapsed;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${projectId}/issues`}
label="Issues"
icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label={
currentProjectDetails && issueDetails
? `${currentProjectDetails.identifier}-${issueDetails.sequence_id}`
: ""
}
/>
}
/>
</Breadcrumbs>
</div>
</div>
<button className="block md:hidden" onClick={() => toggleIssueDetailSidebar()}>
<PanelRight
className={cn("h-4 w-4 ", !isSidebarCollapsed ? "text-custom-primary-100" : " text-custom-text-200")}
/>
</button>
</div>
);
});

View file

@ -1,236 +0,0 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// icons
import { Briefcase, Circle, ExternalLink } from "lucide-react";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
// components
import { ProjectAnalyticsModal } from "@/components/analytics";
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// constants
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// helpers
import { SPACE_BASE_URL } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import {
useEventTracker,
useLabel,
useProject,
useProjectState,
useUser,
useMember,
useCommandPalette,
} from "@/hooks/store";
import { useIssues } from "@/hooks/store/use-issues";
import { usePlatformOS } from "@/hooks/use-platform-os";
export const ProjectIssuesHeader: React.FC = observer(() => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store hooks
const {
project: { projectMemberIds },
} = useMember();
const {
issuesFilter: { issueFilters, updateFilters },
issues: { issuesCount },
} = useIssues(EIssuesStoreType.PROJECT);
const { toggleCreateIssueModal } = useCommandPalette();
const { setTrackElement } = useEventTracker();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const { isMobile } = usePlatformOS();
const activeLayout = issueFilters?.displayFilters?.layout;
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues });
},
[workspaceSlug, projectId, issueFilters, updateFilters]
);
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[workspaceSlug, projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[workspaceSlug, projectId, updateFilters]
);
const publishedURL = `${SPACE_BASE_URL}/issues/${currentProjectDetails?.anchor}`;
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<>
<ProjectAnalyticsModal
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
projectDetails={currentProjectDetails ?? undefined}
/>
<div className="relative z-[15] flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-2.5">
<Breadcrumbs onBack={() => router.back()}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails ? (
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
<Briefcase className="h-4 w-4" />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Issues" icon={<LayersIcon className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
{issuesCount && issuesCount > 0 ? (
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "issues" : "issue"} in this project`}
position="bottom"
>
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
{issuesCount}
</span>
</Tooltip>
) : null}
</div>
{currentProjectDetails?.anchor && (
<a
href={publishedURL}
className="group flex items-center gap-1.5 rounded bg-custom-primary-100/10 px-2.5 py-1 text-xs font-medium text-custom-primary-100"
target="_blank"
rel="noopener noreferrer"
>
<Circle className="h-1.5 w-1.5 fill-custom-primary-100" strokeWidth={2} />
Public
<ExternalLink className="hidden h-3 w-3 group-hover:block" strokeWidth={2} />
</a>
)}
</div>
<div className="items-center gap-2 hidden md:flex">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied}>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
</div>
{canUserCreateIssue && (
<>
<Button
className="hidden md:block"
onClick={() => setAnalyticsModal(true)}
variant="neutral-primary"
size="sm"
>
Analytics
</Button>
<Button
onClick={() => {
setTrackElement("Project issues page");
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
}}
size="sm"
>
<div className="hidden sm:block">Add</div> Issue
</Button>
</>
)}
</div>
</>
);
});

View file

@ -1,82 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// ui
import { Settings } from "lucide-react";
import { Breadcrumbs, CustomMenu } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
// constants
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "@/constants/project";
// hooks
import { useProject, useUser } from "@/hooks/store";
export const ProjectSettingHeader: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store hooks
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
if (currentProjectRole && currentProjectRole <= EUserProjectRoles.VIEWER) return null;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<div className="z-50">
<Breadcrumbs onBack={router.back}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid place-items-center flex-shrink-0 h-4 w-4">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<div className="hidden sm:hidden md:block lg:block">
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Settings" icon={<Settings className="h-4 w-4 text-custom-text-300" />} />
}
/>
</div>
</Breadcrumbs>
</div>
</div>
<CustomMenu
className="flex-shrink-0 block sm:block md:hidden lg:hidden"
maxHeight="lg"
customButton={
<span className="text-xs px-1.5 py-1 border rounded-md text-custom-text-200 border-custom-border-300">
Settings
</span>
}
placement="bottom-start"
closeOnSelect
>
{PROJECT_SETTINGS_LINKS.map((item) => (
<CustomMenu.MenuItem
key={item.key}
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
>
{item.label}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div>
</div>
);
});

View file

@ -1,258 +0,0 @@
import { useCallback } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
// types
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
// ui
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
// constants
import { EIssuesStoreType, EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EUserProjectRoles } from "@/constants/project";
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
import { truncateText } from "@/helpers/string.helper";
// hooks
import {
useCommandPalette,
useEventTracker,
useIssues,
useLabel,
useMember,
useProject,
useProjectState,
useProjectView,
useUser,
} from "@/hooks/store";
export const ProjectViewIssuesHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query;
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
const { setTrackElement } = useEventTracker();
const { toggleCreateIssueModal } = useCommandPalette();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const { projectViewIds, getViewById } = useProjectView();
const { projectStates } = useProjectState();
const { projectLabels } = useLabel();
const {
project: { projectMemberIds },
} = useMember();
const activeLayout = issueFilters?.displayFilters?.layout;
const handleLayoutChange = useCallback(
(layout: TIssueLayouts) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId || !viewId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.FILTERS,
{ [key]: newValues },
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, issueFilters, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
viewId.toString()
);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const viewDetails = viewId ? getViewById(viewId.toString()) : null;
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0;
return (
<div className="relative z-[15] flex h-[3.75rem] w-full items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/views`}
label="Views"
icon={<PhotoFilterIcon className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="component"
component={
<CustomMenu
label={
<>
{viewDetails?.logo_props?.in_use ? (
<Logo logo={viewDetails.logo_props} size={12} type="lucide" />
) : (
<PhotoFilterIcon height={12} width={12} />
)}
{viewDetails?.name && truncateText(viewDetails.name, 40)}
</>
}
className="ml-1.5"
placement="bottom-start"
>
{projectViewIds?.map((viewId) => {
const view = getViewById(viewId);
if (!view) return;
return (
<CustomMenu.MenuItem key={viewId}>
<Link
href={`/${workspaceSlug}/projects/${projectId}/views/${viewId}`}
className="flex items-center gap-1.5"
>
{view?.logo_props?.in_use ? (
<Logo logo={view.logo_props} size={12} type="lucide" />
) : (
<PhotoFilterIcon height={12} width={12} />
)}
{truncateText(view.name, 40)}
</Link>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
}
/>
</Breadcrumbs>
</div>
<div className="flex items-center gap-2">
<LayoutSelection
layouts={["list", "kanban", "calendar", "spreadsheet", "gantt_chart"]}
onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout}
/>
<FiltersDropdown
title="Filters"
placement="bottom-end"
disabled={!canUserCreateIssue}
isFiltersApplied={isFiltersApplied}
>
<FilterSelection
filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectLabels}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
<FiltersDropdown title="Display" placement="bottom-end">
<DisplayFiltersSelection
layoutDisplayFiltersOptions={
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
displayFilters={issueFilters?.displayFilters ?? {}}
handleDisplayFiltersUpdate={handleDisplayFilters}
displayProperties={issueFilters?.displayProperties ?? {}}
handleDisplayPropertiesUpdate={handleDisplayProperties}
cycleViewDisabled={!currentProjectDetails?.cycle_view}
moduleViewDisabled={!currentProjectDetails?.module_view}
/>
</FiltersDropdown>
{canUserCreateIssue && (
<Button
onClick={() => {
setTrackElement("PROJECT_VIEW_PAGE_HEADER");
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
}}
size="sm"
>
Add Issue
</Button>
)}
</div>
</div>
);
});

View file

@ -1,71 +0,0 @@
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// ui
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
// components
import { BreadcrumbLink, Logo } from "@/components/common";
import { ViewListHeader } from "@/components/views";
// constants
import { EUserProjectRoles } from "@/constants/project";
// hooks
import { useCommandPalette, useProject, useUser } from "@/hooks/store";
export const ProjectViewsHeader: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// store hooks
const { toggleCreateViewModal } = useCommandPalette();
const {
membership: { currentProjectRole },
} = useUser();
const { currentProjectDetails } = useProject();
const canUserCreateIssue =
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
return (
<>
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
label={currentProjectDetails?.name ?? "Project"}
icon={
currentProjectDetails && (
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
<Logo logo={currentProjectDetails?.logo_props} size={16} />
</span>
)
}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Views" icon={<PhotoFilterIcon className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
</div>
</div>
{canUserCreateIssue && (
<div className="flex flex-shrink-0 items-center gap-2">
<ViewListHeader />
<div>
<Button variant="primary" size="sm" onClick={() => toggleCreateViewModal(true)}>
Add View
</Button>
</div>
</div>
)}
</div>
</>
);
});

View file

@ -1,183 +0,0 @@
import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
import { Search, Briefcase, X, ListFilter } from "lucide-react";
// types
import { TProjectFilters } from "@plane/types";
// ui
import { Breadcrumbs, Button } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { FiltersDropdown } from "@/components/issues";
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project";
// constants
import { EUserWorkspaceRoles } from "@/constants/workspace";
// helpers
import { cn } from "@/helpers/common.helper";
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useAppRouter, useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store";
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
export const ProjectsHeader = observer(() => {
// states
const [isSearchOpen, setIsSearchOpen] = useState(false);
// refs
const inputRef = useRef<HTMLInputElement>(null);
// store hooks
const { toggleCreateProjectModal } = useCommandPalette();
const { workspaceSlug } = useAppRouter();
const { setTrackElement } = useEventTracker();
const {
membership: { currentWorkspaceRole },
} = useUser();
const {
currentWorkspaceDisplayFilters: displayFilters,
currentWorkspaceFilters: filters,
updateFilters,
updateDisplayFilters,
searchQuery,
updateSearchQuery,
} = useProjectFilter();
const {
workspace: { workspaceMemberIds },
} = useMember();
// outside click detector hook
useOutsideClickDetector(inputRef, () => {
if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false);
});
// auth
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const handleFilters = useCallback(
(key: keyof TProjectFilters, value: string | string[]) => {
if (!workspaceSlug) return;
let newValues = filters?.[key] ?? [];
if (Array.isArray(value)) {
if (key === "created_at" && newValues.find((v) => v.includes("custom"))) newValues = [];
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else {
if (key === "created_at") newValues = [value];
else newValues.push(value);
}
}
updateFilters(workspaceSlug, { [key]: newValues });
},
[filters, updateFilters, workspaceSlug]
);
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
if (searchQuery && searchQuery.trim() !== "") updateSearchQuery("");
else setIsSearchOpen(false);
}
};
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Projects" icon={<Briefcase className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</div>
<div className="w-full flex items-center justify-end gap-3">
<div className="flex items-center">
{!isSearchOpen && (
<button
type="button"
className="-mr-1 p-2 hover:bg-custom-background-80 rounded text-custom-text-400 grid place-items-center"
onClick={() => {
setIsSearchOpen(true);
inputRef.current?.focus();
}}
>
<Search className="h-3.5 w-3.5" />
</button>
)}
<div
className={cn(
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-custom-background-100 text-custom-text-400 w-0 transition-[width] ease-linear overflow-hidden opacity-0",
{
"w-30 md:w-64 px-2.5 py-1.5 border-custom-border-200 opacity-100": isSearchOpen,
}
)}
>
<Search className="h-3.5 w-3.5" />
<input
ref={inputRef}
className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none"
placeholder="Search"
value={searchQuery}
onChange={(e) => updateSearchQuery(e.target.value)}
onKeyDown={handleInputKeyDown}
/>
{isSearchOpen && (
<button
type="button"
className="grid place-items-center"
onClick={() => {
updateSearchQuery("");
setIsSearchOpen(false);
}}
>
<X className="h-3 w-3" />
</button>
)}
</div>
</div>
<div className="hidden md:flex gap-3">
<ProjectOrderByDropdown
value={displayFilters?.order_by}
onChange={(val) => {
if (!workspaceSlug || val === displayFilters?.order_by) return;
updateDisplayFilters(workspaceSlug, {
order_by: val,
});
}}
/>
<FiltersDropdown
icon={<ListFilter className="h-3 w-3" />}
title="Filters"
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
<ProjectFiltersSelection
displayFilters={displayFilters ?? {}}
filters={filters ?? {}}
handleFiltersUpdate={handleFilters}
handleDisplayFiltersUpdate={(val) => {
if (!workspaceSlug) return;
updateDisplayFilters(workspaceSlug, val);
}}
memberIds={workspaceMemberIds ?? undefined}
/>
</FiltersDropdown>
</div>
{isAuthorizedUser && (
<Button
size="sm"
onClick={() => {
setTrackElement("Projects page");
toggleCreateProjectModal(true);
}}
className="items-center gap-1"
>
<span className="hidden sm:inline-block">Add</span> Project
</Button>
)}
</div>
</div>
);
});

View file

@ -1,91 +0,0 @@
// ui
import { FC } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { ChevronDown, PanelRight } from "lucide-react";
import { Breadcrumbs, CustomMenu } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
// components
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
import { cn } from "@/helpers/common.helper";
import { useAppTheme, useUser } from "@/hooks/store";
type TUserProfileHeader = {
type?: string | undefined;
};
export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
const { type = undefined } = props;
// router
const router = useRouter();
const { workspaceSlug, userId } = router.query;
// store hooks
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
const {
membership: { currentWorkspaceRole },
} = useUser();
// derived values
const AUTHORIZED_ROLES = [20, 15, 10];
if (!currentWorkspaceRole) return null;
const isAuthorized = AUTHORIZED_ROLES.includes(currentWorkspaceRole);
const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB;
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex w-full justify-between">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink href="/profile" label="Activity Overview" />}
/>
</Breadcrumbs>
<div className="flex gap-4 md:hidden">
<CustomMenu
maxHeight={"md"}
className="flex flex-grow justify-center text-sm text-custom-text-200"
placement="bottom-start"
customButton={
<div className="flex items-center gap-2 rounded-md border border-custom-border-400 px-2 py-1.5">
<span className="flex flex-grow justify-center text-sm text-custom-text-200">{type}</span>
<ChevronDown className="h-4 w-4 text-custom-text-400" />
</div>
}
customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
closeOnSelect
>
<></>
{tabsList.map((tab) => (
<CustomMenu.MenuItem className="flex items-center gap-2" key={tab.route}>
<Link
key={tab.route}
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
className="w-full text-custom-text-300"
>
{tab.label}
</Link>
</CustomMenu.MenuItem>
))}
</CustomMenu>
<button
className="block transition-all md:hidden"
onClick={() => {
toggleProfileSidebar();
}}
>
<PanelRight
className={cn(
"block h-4 w-4 md:hidden",
!profileSidebarCollapsed ? "text-[#3E63DD]" : "text-custom-text-200"
)}
/>
</button>
</div>
</div>
</div>
</div>
);
});

View file

@ -1,27 +0,0 @@
import { observer } from "mobx-react";
// ui
import { Crown } from "lucide-react";
import { Breadcrumbs, ContrastIcon } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
// icons
export const WorkspaceActiveCycleHeader = observer(() => (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label="Active Cycles"
icon={<ContrastIcon className="h-4 w-4 text-custom-text-300 rotate-180" />}
/>
}
/>
</Breadcrumbs>
<Crown className="h-3.5 w-3.5 text-amber-400" />
</div>
</div>
</div>
));

View file

@ -1,68 +0,0 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { BarChart2, PanelRight } from "lucide-react";
// ui
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { cn } from "@/helpers/common.helper";
import { useAppTheme } from "@/hooks/store";
export const WorkspaceAnalyticsHeader = observer(() => {
const router = useRouter();
const { analytics_tab } = router.query;
// store hooks
const { workspaceAnalyticsSidebarCollapsed, toggleWorkspaceAnalyticsSidebar } = useAppTheme();
useEffect(() => {
const handleToggleWorkspaceAnalyticsSidebar = () => {
if (window && window.innerWidth < 768) {
toggleWorkspaceAnalyticsSidebar(true);
}
if (window && workspaceAnalyticsSidebarCollapsed && window.innerWidth >= 768) {
toggleWorkspaceAnalyticsSidebar(false);
}
};
window.addEventListener("resize", handleToggleWorkspaceAnalyticsSidebar);
handleToggleWorkspaceAnalyticsSidebar();
return () => window.removeEventListener("resize", handleToggleWorkspaceAnalyticsSidebar);
}, [toggleWorkspaceAnalyticsSidebar, workspaceAnalyticsSidebarCollapsed]);
return (
<>
<div
className={`relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div className="flex w-full items-center justify-between">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink label="Analytics" icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
{analytics_tab === "custom" && (
<button
className="block md:hidden"
onClick={() => {
toggleWorkspaceAnalyticsSidebar();
}}
>
<PanelRight
className={cn(
"block h-4 w-4 md:hidden",
!workspaceAnalyticsSidebarCollapsed ? "text-custom-primary-100" : "text-custom-text-200"
)}
/>
</button>
)}
</div>
</div>
</div>
</>
);
});

View file

@ -1,71 +0,0 @@
import Image from "next/image";
import { useTheme } from "next-themes";
import { Home, Zap } from "lucide-react";
// images
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
// hooks
// components
import { Breadcrumbs } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common";
// constants
import { CHANGELOG_REDIRECTED, GITHUB_REDIRECTED } from "@/constants/event-tracker";
import { useEventTracker } from "@/hooks/store";
export const WorkspaceDashboardHeader = () => {
// hooks
const { captureEvent } = useEventTracker();
const { resolvedTheme } = useTheme();
return (
<>
<div className="relative z-[15] flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
/>
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-3 px-3">
<a
onClick={() =>
captureEvent(CHANGELOG_REDIRECTED, {
element: "navbar",
})
}
href="https://plane.so/changelog"
target="_blank"
rel="noopener noreferrer"
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
>
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
<span className="hidden text-xs font-medium sm:hidden md:block">{"What's new?"}</span>
</a>
<a
onClick={() =>
captureEvent(GITHUB_REDIRECTED, {
element: "navbar",
})
}
className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5"
href="https://github.com/makeplane/plane"
target="_blank"
rel="noopener noreferrer"
>
<Image
src={resolvedTheme === "dark" ? githubWhiteImage : githubBlackImage}
height={16}
width={16}
alt="GitHub Logo"
/>
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
</a>
</div>
</div>
</>
);
};

View file

@ -1,35 +0,0 @@
import { FC } from "react";
import { observer } from "mobx-react";;
import { Settings } from "lucide-react";
// ui
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
// hooks
import { useWorkspace } from "@/hooks/store";
export const WorkspaceSettingHeader: FC = observer(() => {
const { currentWorkspace } = useWorkspace();
return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${currentWorkspace?.slug}/settings`}
label={currentWorkspace?.name ?? "Workspace"}
icon={<Settings className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem type="text" link={<BreadcrumbLink label="Settings" />} />
</Breadcrumbs>
</div>
</div>
</div>
);
});

View file

@ -1,6 +1,6 @@
import { FC, useCallback, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import {
CircleCheck,
CircleX,

View file

@ -1,6 +1,6 @@
import React from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import {
CircleCheck,
CircleX,

View file

@ -1,6 +1,6 @@
import React from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { CalendarCheck2, CopyPlus, Signal, Tag, Users } from "lucide-react";
import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types";
import { ControlLink, DoubleCircleIcon, Tooltip } from "@plane/ui";

View file

@ -1,6 +1,6 @@
import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { usePathname } from "next/navigation";
import { TIssue } from "@plane/types";
import { Loader, TOAST_TYPE, setToast } from "@plane/ui";
// components
@ -29,7 +29,7 @@ type Props = {
};
export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
const router = useRouter();
const pathname = usePathname();
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
// hooks
const { data: currentUser } = useUser();
@ -87,7 +87,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
path: pathname,
});
} catch (error) {
setToast({
@ -102,7 +102,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
changed_property: Object.keys(data).join(","),
change_details: Object.values(data).join(","),
},
path: router.asPath,
path: pathname,
});
}
},

View file

@ -1,6 +1,6 @@
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import useSWR from "swr";
import { InboxIssueActionsHeader, InboxIssueMainContent } from "@/components/inbox";
import { EUserProjectRoles } from "@/constants/project";

View file

@ -1,6 +1,6 @@
import { FC, FormEvent, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { usePathname, useRouter } from "next/navigation";
// editor
import { EditorRefApi } from "@plane/rich-text-editor";
// types
@ -41,6 +41,7 @@ export const defaultIssueData: Partial<TIssue> = {
export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props) => {
const { workspaceSlug, projectId, handleModalClose } = props;
const router = useRouter();
const pathname = usePathname();
// refs
const descriptionEditorRef = useRef<EditorRefApi>(null);
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
@ -117,7 +118,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
state: "SUCCESS",
element: "Inbox page",
},
path: router.pathname,
path: pathname,
});
setToast({
type: TOAST_TYPE.SUCCESS,
@ -134,7 +135,7 @@ export const InboxIssueCreateRoot: FC<TInboxIssueCreateRoot> = observer((props)
state: "FAILED",
element: "Inbox page",
},
path: router.pathname,
path: pathname,
});
setToast({
type: TOAST_TYPE.ERROR,

View file

@ -1,6 +1,6 @@
import { FC, useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { usePathname } from "next/navigation";
// editor
import { EditorRefApi } from "@plane/rich-text-editor";
// types
@ -31,7 +31,7 @@ type TInboxIssueEditRoot = {
export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
const { workspaceSlug, projectId, issueId, issue, handleModalClose, onSubmit } = props;
const router = useRouter();
const pathname = usePathname();
// refs
const descriptionEditorRef = useRef<EditorRefApi>(null);
const submitBtnRef = useRef<HTMLButtonElement | null>(null);
@ -96,7 +96,7 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
state: "SUCCESS",
element: "Inbox page",
},
path: router.pathname,
path: pathname,
});
setToast({
type: TOAST_TYPE.SUCCESS,
@ -115,7 +115,7 @@ export const InboxIssueEditRoot: FC<TInboxIssueEditRoot> = observer((props) => {
state: "FAILED",
element: "Inbox page",
},
path: router.pathname,
path: pathname,
});
setToast({
type: TOAST_TYPE.ERROR,

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWR from "swr";
import { Search } from "lucide-react";
import { Combobox, Dialog, Transition } from "@headlessui/react";
@ -30,8 +30,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const [query, setQuery] = useState("");
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { workspaceSlug, projectId, issueId } = useParams();
// hooks
const { getProjectStates } = useProjectState();

View file

@ -1,7 +1,7 @@
import { FC, MouseEvent } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useSearchParams } from "next/navigation";
import { Tooltip, PriorityIcon } from "@plane/ui";
// components
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
@ -24,8 +24,8 @@ type InboxIssueListItemProps = {
export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props) => {
const { workspaceSlug, projectId, inboxIssueId, projectIdentifier, setIsMobileSidebar } = props;
// router
const router = useRouter();
const { inboxIssueId: selectedInboxIssueId } = router.query;
const searchParams = useSearchParams();
const selectedInboxIssueId = searchParams.get("inboxIssueId");
// store
const { currentTab, getIssueInboxByIssueId } = useProjectInbox();
const { projectLabels } = useLabel();

View file

@ -1,6 +1,6 @@
import { FC, useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { useRouter } from "next/navigation";
import { TInboxIssueCurrentTab } from "@plane/types";
import { Loader } from "@plane/ui";
// components

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { mutate } from "swr";
@ -31,8 +31,7 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data }
const [deleteLoading, setDeleteLoading] = useState(false);
const [confirmDeleteImport, setConfirmDeleteImport] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const handleDeletion = () => {
if (!workspaceSlug || !data) return;

View file

@ -1,6 +1,6 @@
import { FC, useEffect } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// react-hook-form
import { UseFormSetValue } from "react-hook-form";
@ -25,8 +25,7 @@ type Props = {
const githubIntegrationService = new GithubIntegrationService();
export const GithubRepoDetails: FC<Props> = ({ selectedRepo, handleStepChange, setUsers, setValue }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const { data: repoInfo } = useSWR(
workspaceSlug && selectedRepo ? GITHUB_REPOSITORY_INFO(workspaceSlug as string, selectedRepo.name) : null,

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useForm } from "react-hook-form";
import useSWR, { mutate } from "swr";
@ -92,7 +92,9 @@ export const GithubImporterRoot: React.FC = () => {
const [users, setUsers] = useState<IUserDetails[]>([]);
const router = useRouter();
const { workspaceSlug, provider } = router.query;
const { workspaceSlug } = useParams();
const searchParams = useSearchParams();
const provider = searchParams.get("provider");
const { handleSubmit, control, setValue, watch } = useForm<TFormValues>({
defaultValues: defaultFormValues,

View file

@ -1,5 +1,5 @@
import React from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWRInfinite from "swr/infinite";
import { IWorkspaceIntegration } from "@plane/types";
// services
@ -23,8 +23,7 @@ const projectService = new ProjectService();
export const SelectRepository: React.FC<Props> = (props) => {
const { integration, value, label, onChange, characterLimit = 25 } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const getKey = (pageIndex: number) => {
if (!workspaceSlug || !integration) return;

View file

@ -1,4 +1,4 @@
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import useSWR from "swr";
import { IGithubRepoCollaborator } from "@plane/types";
// services
@ -36,8 +36,7 @@ const importOptions = [
const workspaceService = new WorkspaceService();
export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users, setUsers }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const { data: members } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug.toString()) : null,

View file

@ -2,7 +2,7 @@ import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams, useSearchParams } from "next/navigation";
import useSWR, { mutate } from "swr";
// icons
import { RefreshCw } from "lucide-react";
@ -32,8 +32,9 @@ const IntegrationGuide = observer(() => {
const [deleteImportModal, setDeleteImportModal] = useState(false);
const [importToDelete, setImportToDelete] = useState<IImporterService | null>(null);
// router
const router = useRouter();
const { workspaceSlug, provider } = router.query;
const { workspaceSlug } = useParams();
const searchParams = useSearchParams();
const provider = searchParams.get("provider");
// store hooks
const { data: currentUser } = useUser();

View file

@ -1,5 +1,5 @@
import { FC } from "react";
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
import { useFormContext, useFieldArray, Controller } from "react-hook-form";
import useSWR from "swr";
import { IJiraImporterForm } from "@plane/types";
@ -14,8 +14,7 @@ import { WorkspaceService } from "@/services/workspace.service";
const workspaceService = new WorkspaceService();
export const JiraImportUsers: FC = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
// form info
const {
control,

View file

@ -1,7 +1,7 @@
import React, { useEffect } from "react";
// next
import { useRouter } from "next/router";
import { useParams } from "next/navigation";
// swr
import { useFormContext, Controller } from "react-hook-form";
@ -39,8 +39,7 @@ export const JiraProjectDetail: React.FC<Props> = (props) => {
formState: { errors },
} = useFormContext<IJiraImporterForm>();
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const params: IJiraMetadata = {
api_token: watch("metadata.api_token"),

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { useParams, useRouter } from "next/navigation";
import { FormProvider, useForm } from "react-hook-form";
import { mutate } from "swr";
// icons
@ -63,7 +63,7 @@ export const JiraImporterRoot: React.FC = () => {
const [disableTopBarAfter, setDisableTopBarAfter] = useState<TJiraIntegrationSteps | null>(null);
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug } = useParams();
const methods = useForm<IJiraImporterForm>({
defaultValues: jiraFormDefaultValues,

Some files were not shown because too many files have changed in this diff Show more