mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'preview' of github.com:makeplane/plane into auth-fixes
This commit is contained in:
commit
f9d2858209
32
.github/workflows/build-test-pull-request.yml
vendored
32
.github/workflows/build-test-pull-request.yml
vendored
@ -14,10 +14,10 @@ jobs:
|
||||
space_changed: ${{ steps.changed-files.outputs.space_any_changed }}
|
||||
web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
uses: tj-actions/changed-files@v44
|
||||
with:
|
||||
files_yaml: |
|
||||
apiserver:
|
||||
@ -49,9 +49,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.get-changed-files.outputs.apiserver_changed == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x" # Specify the Python version you need
|
||||
- name: Install Pylint
|
||||
@ -66,9 +66,9 @@ jobs:
|
||||
if: needs.get-changed-files.outputs.admin_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@ -79,9 +79,9 @@ jobs:
|
||||
if: needs.get-changed-files.outputs.space_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@ -92,9 +92,9 @@ jobs:
|
||||
if: needs.get-changed-files.outputs.web_changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@ -104,9 +104,9 @@ jobs:
|
||||
needs: lint-admin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@ -116,9 +116,9 @@ jobs:
|
||||
needs: lint-space
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@ -128,9 +128,9 @@ jobs:
|
||||
needs: lint-web
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
|
@ -1,8 +1,6 @@
|
||||
import Image from "next/image";
|
||||
// components
|
||||
import IssueNavbar from "@/components/issues/navbar";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
// services
|
||||
import ProjectService from "@/services/project.service";
|
||||
// assets
|
||||
@ -17,7 +15,7 @@ export default async function ProjectLayout({ children, params }: { children: Re
|
||||
return (
|
||||
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
||||
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
||||
<IssueNavbar projectSettings={projectSettings} />
|
||||
<IssueNavbar projectSettings={projectSettings} workspaceSlug={workspace_slug} projectId={project_id} />
|
||||
</div>
|
||||
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
||||
<a
|
||||
|
3
space/app/[workspace_slug]/[project_id]/not-found.tsx
Normal file
3
space/app/[workspace_slug]/[project_id]/not-found.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function ProjectSettingsNotFound() {
|
||||
return <>Project Settings not found</>;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
import { ProjectDetailsView } from "@/components/views";
|
||||
|
||||
export default async function WorkspaceProjectPage({ params }: { params: any }) {
|
||||
const { workspace_slug, project_id, ...rest } = params;
|
||||
const { workspace_slug, project_id, peekId } = params;
|
||||
|
||||
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} params={rest} />;
|
||||
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} peekId={peekId} />;
|
||||
}
|
||||
|
@ -1,5 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg";
|
||||
import InstanceFailureImage from "@/public/instance/instance-failure.svg";
|
||||
|
||||
export default function InstanceError() {
|
||||
return <div>Instance Error</div>;
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||
|
||||
const handleRetry = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||
<Image src={instanceImage} alt="Plane Logo" />
|
||||
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
||||
<p className="font-medium text-base text-center">
|
||||
We were unable to fetch the details of the instance. <br />
|
||||
Fret not, it might just be a connectivity issue.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="md" onClick={handleRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,8 @@
|
||||
import { Metadata } from "next";
|
||||
// styles
|
||||
import "@/styles/globals.css";
|
||||
// components
|
||||
import { InstanceNotReady } from "@/components/instance";
|
||||
// lib
|
||||
import { AppProvider } from "@/lib/app-providers";
|
||||
// services
|
||||
import { InstanceService } from "@/services/instance.service";
|
||||
|
||||
const instanceService = new InstanceService();
|
||||
// helpers
|
||||
import { ASSET_PREFIX } from "@/helpers/common.helper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Plane Deploy | Make your Plane boards public with one-click",
|
||||
@ -26,24 +20,16 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const instanceDetails = await instanceService.getInstanceInfo();
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* <link rel="apple-touch-icon" sizes="180x180" href={`${prefix}favicon/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${prefix}favicon/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${prefix}favicon/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${prefix}site.webmanifest.json`} />
|
||||
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} /> */}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}favicon/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}favicon/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}favicon/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${ASSET_PREFIX}site.webmanifest.json`} />
|
||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body>
|
||||
{!instanceDetails?.instance?.is_setup_done ? (
|
||||
<InstanceNotReady />
|
||||
) : (
|
||||
<AppProvider initialState={{ instance: instanceDetails.instance }}>{children}</AppProvider>
|
||||
)}
|
||||
</body>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
39
space/app/not-found.tsx
Normal file
39
space/app/not-found.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg";
|
||||
import InstanceFailureImage from "@/public/instance/instance-failure.svg";
|
||||
|
||||
export default function InstanceNotFound() {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||
|
||||
const handleRetry = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
||||
<Image src={instanceImage} alt="Plane Logo" />
|
||||
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
||||
<p className="font-medium text-base text-center">
|
||||
We were unable to fetch the details of the instance. <br />
|
||||
Fret not, it might just be a connectivity issue.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="md" onClick={handleRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,31 +1,43 @@
|
||||
// components
|
||||
import { UserLoggedIn } from "@/components/accounts";
|
||||
import { InstanceNotReady, InstanceFailureView } from "@/components/instance";
|
||||
import { AuthView } from "@/components/views";
|
||||
// helpers
|
||||
// import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
// import { useInstance, useUser } from "@/hooks/store";
|
||||
// wrapper
|
||||
// import { AuthWrapper } from "@/lib/wrappers";
|
||||
// lib
|
||||
import { AppProvider } from "@/lib/app-providers";
|
||||
// services
|
||||
import { InstanceService } from "@/services/instance.service";
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
const userServices = new UserService();
|
||||
const instanceService = new InstanceService();
|
||||
|
||||
export default async function HomePage() {
|
||||
const instanceDetails = await instanceService.getInstanceInfo().catch(() => undefined);
|
||||
const user = await userServices
|
||||
.currentUser()
|
||||
.then((user) => ({ ...user, isAuthenticated: true }))
|
||||
.catch(() => ({ isAuthenticated: false }));
|
||||
|
||||
// const { data } = useInstance();
|
||||
if (!instanceDetails) {
|
||||
return <InstanceFailureView />;
|
||||
}
|
||||
|
||||
// console.log("data", data);
|
||||
console.log("user", user);
|
||||
if (!instanceDetails?.instance?.is_setup_done) {
|
||||
<InstanceNotReady />;
|
||||
}
|
||||
|
||||
if (user.isAuthenticated) {
|
||||
return <UserLoggedIn />;
|
||||
}
|
||||
|
||||
// return <>Login View</>;
|
||||
return <AuthView />;
|
||||
return (
|
||||
<AppProvider initialState={{ instance: instanceDetails.instance }}>
|
||||
<AuthView />
|
||||
</AppProvider>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
// icons
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Eye, EyeOff, XCircle } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
@ -47,8 +47,7 @@ export const PasswordForm: React.FC<Props> = (props) => {
|
||||
// hooks
|
||||
const { data: instance, config: instanceConfig } = useInstance();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { next_path } = router.query;
|
||||
const { next_path } = useParams<any>();
|
||||
// derived values
|
||||
const isSmtpConfigured = instanceConfig?.is_smtp_configured;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { CircleCheck, XCircle } from "lucide-react";
|
||||
// ui
|
||||
@ -45,8 +45,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
|
||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { next_path } = router.query;
|
||||
const { next_path } = useParams<any>();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// timer
|
||||
|
@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
import { FC } from "react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
@ -7,15 +8,18 @@ import InstanceFailureDarkImage from "public/instance/instance-failure-dark.svg"
|
||||
import InstanceFailureImage from "public/instance/instance-failure.svg";
|
||||
|
||||
type InstanceFailureViewProps = {
|
||||
mutate: () => void;
|
||||
// mutate: () => void;
|
||||
};
|
||||
|
||||
export const InstanceFailureView: FC<InstanceFailureViewProps> = (props) => {
|
||||
const { mutate } = props;
|
||||
export const InstanceFailureView: FC<InstanceFailureViewProps> = () => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||
|
||||
const handleRetry = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
||||
@ -28,7 +32,7 @@ export const InstanceFailureView: FC<InstanceFailureViewProps> = (props) => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="md" onClick={mutate}>
|
||||
<Button size="md" onClick={handleRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@ import { FC } from "react";
|
||||
import Image from "next/image";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// helpers
|
||||
// helper
|
||||
import { ADMIN_BASE_URL, ADMIN_BASE_PATH } from "@/helpers/common.helper";
|
||||
// images
|
||||
import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png";
|
||||
|
@ -1,53 +1,48 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// components
|
||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
|
||||
// components
|
||||
// hooks
|
||||
import { useIssueDetails, useProject } from "@/hooks/store";
|
||||
// interfaces
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { IIssue } from "types/issue";
|
||||
import { IIssue } from "@/types/issue";
|
||||
|
||||
export const IssueKanBanBlock = observer(({ issue }: { issue: IIssue }) => {
|
||||
const { project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore();
|
||||
type IssueKanBanBlockProps = {
|
||||
issue: IIssue;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
params: any;
|
||||
};
|
||||
|
||||
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, params, issue } = props;
|
||||
const { board, priorities, states, labels } = params;
|
||||
// store
|
||||
const { project } = useProject();
|
||||
const { setPeekId } = useIssueDetails();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspace_slug, project_slug, board, priorities, states, labels } = router.query as {
|
||||
workspace_slug: string;
|
||||
project_slug: string;
|
||||
board: string;
|
||||
priorities: string;
|
||||
states: string;
|
||||
labels: string;
|
||||
};
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const handleBlockClick = () => {
|
||||
issueDetailStore.setPeekId(issue.id);
|
||||
setPeekId(issue.id);
|
||||
const params: any = { board: board, peekId: issue.id };
|
||||
if (states && states.length > 0) params.states = states;
|
||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||
if (labels && labels.length > 0) params.labels = labels;
|
||||
router.push(
|
||||
{
|
||||
pathname: `/${workspace_slug}/${project_slug}`,
|
||||
query: { ...params },
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5 space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs">
|
||||
{/* id */}
|
||||
<div className="break-words text-xs text-custom-text-300">
|
||||
{projectStore?.project?.identifier}-{issue?.sequence_id}
|
||||
{project?.identifier}-{issue?.sequence_id}
|
||||
</div>
|
||||
|
||||
{/* name */}
|
||||
|
@ -1,18 +1,16 @@
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// interfaces
|
||||
// constants
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { IIssueState } from "types/issue";
|
||||
import { useIssue } from "@/hooks/store";
|
||||
// interfaces
|
||||
import { IIssueState } from "@/types/issue";
|
||||
|
||||
export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const { getCountOfIssuesByState } = useIssue();
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
@ -23,9 +21,7 @@ export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) =>
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
||||
</div>
|
||||
<div className="mr-1 truncate font-semibold capitalize text-custom-text-200">{state?.name}</div>
|
||||
<span className="flex-shrink-0 rounded-full text-custom-text-300">
|
||||
{store.issue.getCountOfIssuesByState(state.id)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 rounded-full text-custom-text-300">{getCountOfIssuesByState(state.id)}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,36 +1,47 @@
|
||||
"use client";
|
||||
|
||||
// mobx react lite
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueKanBanBlock } from "@/components/issues/board-views/kanban/block";
|
||||
import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header";
|
||||
// ui
|
||||
import { Icon } from "@/components/ui";
|
||||
// interfaces
|
||||
// mobx hook
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { IIssueState, IIssue } from "types/issue";
|
||||
import { useIssue } from "@/hooks/store";
|
||||
// interfaces
|
||||
import { IIssueState, IIssue } from "@/types/issue";
|
||||
|
||||
export const IssueKanbanView = observer(() => {
|
||||
const store: RootStore = useMobxStore();
|
||||
type IssueKanbanViewProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// store hooks
|
||||
const { states, getFilteredIssuesByState } = useIssue();
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full gap-3 overflow-hidden overflow-x-auto">
|
||||
{store?.issue?.states &&
|
||||
store?.issue?.states.length > 0 &&
|
||||
store?.issue?.states.map((_state: IIssueState) => (
|
||||
{states &&
|
||||
states.length > 0 &&
|
||||
states.map((_state: IIssueState) => (
|
||||
<div key={_state.id} className="relative flex h-full w-[340px] flex-shrink-0 flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<IssueKanBanHeader state={_state} />
|
||||
</div>
|
||||
<div className="hide-vertical-scrollbar h-full w-full overflow-hidden overflow-y-auto">
|
||||
{store.issue.getFilteredIssuesByState(_state.id) &&
|
||||
store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||
<div className="space-y-3 px-2 pb-2">
|
||||
{store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||
<IssueKanBanBlock key={_issue.id} issue={_issue} />
|
||||
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||
<IssueKanBanBlock
|
||||
key={_issue.id}
|
||||
issue={_issue}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
params={{}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
@ -1,47 +1,40 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
// components
|
||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
||||
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
|
||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { useIssueDetails, useProject } from "@/hooks/store";
|
||||
// interfaces
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { IIssue } from "types/issue";
|
||||
import { IIssue } from "@/types/issue";
|
||||
// store
|
||||
|
||||
export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
|
||||
const { issue } = props;
|
||||
type IssueListBlockProps = {
|
||||
issue: IIssue;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issue } = props;
|
||||
const { board, states, priorities, labels } = useParams<any>();
|
||||
const searchParams = useSearchParams();
|
||||
// store
|
||||
const { project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore();
|
||||
const { project } = useProject();
|
||||
const { setPeekId } = useIssueDetails();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspace_slug, project_slug, board, priorities, states, labels } = router.query as {
|
||||
workspace_slug: string;
|
||||
project_slug: string;
|
||||
board: string;
|
||||
priorities: string;
|
||||
states: string;
|
||||
labels: string;
|
||||
};
|
||||
|
||||
const handleBlockClick = () => {
|
||||
issueDetailStore.setPeekId(issue.id);
|
||||
setPeekId(issue.id);
|
||||
const params: any = { board: board, peekId: issue.id };
|
||||
if (states && states.length > 0) params.states = states;
|
||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||
if (labels && labels.length > 0) params.labels = labels;
|
||||
router.push(
|
||||
{
|
||||
pathname: `/${workspace_slug}/${project_slug}`,
|
||||
query: { ...params },
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
// router.push(`/${workspace_slug?.toString()}/${project_slug}?board=${board?.toString()}&peekId=${issue.id}`);
|
||||
};
|
||||
|
||||
@ -50,7 +43,7 @@ export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
|
||||
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
|
||||
{/* id */}
|
||||
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
|
||||
{projectStore?.project?.identifier}-{issue?.sequence_id}
|
||||
{project?.identifier}-{issue?.sequence_id}
|
||||
</div>
|
||||
{/* name */}
|
||||
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium">
|
||||
|
@ -1,18 +1,15 @@
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// interfaces
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { IIssueState } from "types/issue";
|
||||
import { useIssue } from "@/hooks/store";
|
||||
// types
|
||||
import { IIssueState } from "@/types/issue";
|
||||
|
||||
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
const store: RootStore = useMobxStore();
|
||||
|
||||
const { getCountOfIssuesByState } = useIssue();
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
||||
if (stateGroup === null) return <></>;
|
||||
@ -23,7 +20,7 @@ export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
||||
</div>
|
||||
<div className="mr-1 font-medium capitalize">{state?.name}</div>
|
||||
<div className="text-sm font-medium text-custom-text-200">{store.issue.getCountOfIssuesByState(state.id)}</div>
|
||||
<div className="text-sm font-medium text-custom-text-200">{getCountOfIssuesByState(state.id)}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
||||
@ -7,7 +8,14 @@ import { useIssue } from "@/hooks/store";
|
||||
// types
|
||||
import { IIssueState, IIssue } from "@/types/issue";
|
||||
|
||||
export const IssueListView = observer(() => {
|
||||
type IssueListViewProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const IssueListView: FC<IssueListViewProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// store hooks
|
||||
const { states, getFilteredIssuesByState } = useIssue();
|
||||
|
||||
return (
|
||||
@ -20,7 +28,7 @@ export const IssueListView = observer(() => {
|
||||
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
|
||||
<div className="divide-y divide-custom-border-200">
|
||||
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
||||
<IssueListBlock key={_issue.id} issue={_issue} />
|
||||
<IssueListBlock key={_issue.id} issue={_issue} workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
@ -1,12 +1,10 @@
|
||||
// components
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// helpers
|
||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
||||
import { IIssueLabel, IIssueState } from "types/issue";
|
||||
// types
|
||||
import { IIssueLabel, IIssueState, IIssueFilterOptions } from "@/types/issue";
|
||||
// components
|
||||
import { AppliedPriorityFilters } from "./priority";
|
||||
import { AppliedStateFilters } from "./state";
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
appliedFilters: IIssueFilterOptions;
|
||||
|
@ -1,29 +1,29 @@
|
||||
import { FC, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useIssue, useIssueFilter, useProject } from "@/hooks/store";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "@/types/issue";
|
||||
// components
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
|
||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { FiltersDropdown } from "./helpers/dropdown";
|
||||
import { FilterSelection } from "./selection";
|
||||
// types
|
||||
// helpers
|
||||
// store
|
||||
|
||||
export const IssueFiltersDropdown: FC = observer(() => {
|
||||
type IssueFiltersDropdownProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const { workspace_slug: workspaceSlug, project_slug: projectId } = router.query as {
|
||||
workspace_slug: string;
|
||||
project_slug: string;
|
||||
};
|
||||
|
||||
const {
|
||||
project: { activeBoard },
|
||||
issue: { states, labels },
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
}: RootStore = useMobxStore();
|
||||
// store hooks
|
||||
const { activeLayout } = useProject();
|
||||
const { states, labels } = useIssue();
|
||||
const { issueFilters, updateFilters } = useIssueFilter();
|
||||
|
||||
const updateRouteParams = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string[]) => {
|
||||
@ -31,14 +31,14 @@ export const IssueFiltersDropdown: FC = observer(() => {
|
||||
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||
|
||||
let params: any = { board: activeBoard || "list" };
|
||||
let params: any = { board: activeLayout || "list" };
|
||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||
|
||||
router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true });
|
||||
console.log("params", params);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
},
|
||||
[workspaceSlug, projectId, activeBoard, issueFilters, router]
|
||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
||||
);
|
||||
|
||||
const handleFilters = useCallback(
|
||||
@ -66,8 +66,8 @@ export const IssueFiltersDropdown: FC = observer(() => {
|
||||
<FiltersDropdown title="Filters" placement="bottom-end">
|
||||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFilters={handleFilters}
|
||||
layoutDisplayFiltersOptions={activeBoard ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeBoard] : undefined}
|
||||
handleFilters={handleFilters as any}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
states={states ?? undefined}
|
||||
labels={labels ?? undefined}
|
||||
/>
|
||||
|
@ -1,13 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, X } from "lucide-react";
|
||||
// components
|
||||
// types
|
||||
|
||||
// filter helpers
|
||||
import { ILayoutDisplayFiltersOptions } from "@/store/issues/helpers";
|
||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
||||
import { IIssueState, IIssueLabel } from "types/issue";
|
||||
import { IIssueState, IIssueLabel, IIssueFilterOptions } from "@/types/issue";
|
||||
import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters";
|
||||
// components
|
||||
import { FilterPriority, FilterState } from "./";
|
||||
|
||||
type Props = {
|
||||
|
@ -3,47 +3,50 @@
|
||||
import { useEffect, FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
// components
|
||||
import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation";
|
||||
import { Briefcase } from "lucide-react";
|
||||
import { Avatar, Button } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectLogo } from "@/components/common";
|
||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||
// hooks
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
// store
|
||||
import { useProject, useUser, useIssueFilter } from "@/hooks/store";
|
||||
// types
|
||||
import { TIssueBoardKeys } from "@/types/issue";
|
||||
// components
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
import { NavbarTheme } from "./theme";
|
||||
|
||||
type IssueNavbarProps = {
|
||||
projectSettings: any;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||
const { projectSettings } = props;
|
||||
const { projectSettings, workspaceSlug, projectId } = props;
|
||||
const { project_details, views } = projectSettings;
|
||||
|
||||
console.log("projectSettings", projectSettings);
|
||||
const { board, labels, states, priorities, peekId } = useParams<any>();
|
||||
const searchParams = useSearchParams();
|
||||
const pathName = usePathname();
|
||||
// hooks
|
||||
const router = useRouter();
|
||||
// store
|
||||
const { settings, activeLayout, hydrate } = useProject();
|
||||
hydrate(projectSettings);
|
||||
const { settings, activeLayout, hydrate, setActiveLayout } = useProject();
|
||||
const { data: user } = useUser();
|
||||
console.log("user", user);
|
||||
const { updateFilters } = useIssueFilter();
|
||||
hydrate(projectSettings);
|
||||
|
||||
// return <>layout</>;
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && projectId && settings) {
|
||||
const viewsAcceptable: string[] = [];
|
||||
let currentBoard: TIssueBoardKeys | null = null;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (workspace_slug && project_slug && settings) {
|
||||
// const viewsAcceptable: string[] = [];
|
||||
// let currentBoard: TIssueBoardKeys | null = null;
|
||||
|
||||
// if (settings?.views?.list) viewsAcceptable.push("list");
|
||||
// if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
||||
// if (settings?.views?.calendar) viewsAcceptable.push("calendar");
|
||||
// if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
||||
// if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||
if (settings?.views?.list) viewsAcceptable.push("list");
|
||||
if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
||||
if (settings?.views?.calendar) viewsAcceptable.push("calendar");
|
||||
if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
||||
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||
|
||||
// if (board) {
|
||||
// if (viewsAcceptable.includes(board.toString())) {
|
||||
@ -59,42 +62,40 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (currentBoard) {
|
||||
// if (projectStore?.layout === null || projectStore?.activeBoard !== currentBoard) {
|
||||
// let params: any = { board: currentBoard };
|
||||
// if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
|
||||
// if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
|
||||
// if (states && states.length > 0) params = { ...params, states: states };
|
||||
// if (labels && labels.length > 0) params = { ...params, labels: labels };
|
||||
if (currentBoard) {
|
||||
if (activeLayout === null || activeLayout !== currentBoard) {
|
||||
let params: any = { board: currentBoard };
|
||||
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
|
||||
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
|
||||
if (states && states.length > 0) params = { ...params, states: states };
|
||||
if (labels && labels.length > 0) params = { ...params, labels: labels };
|
||||
console.log("params", params);
|
||||
let storeParams: any = {};
|
||||
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
|
||||
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
|
||||
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
|
||||
|
||||
// let storeParams: any = {};
|
||||
// if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
|
||||
// if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
|
||||
// if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
|
||||
|
||||
// if (storeParams) updateFilters(project_slug, storeParams);
|
||||
|
||||
// projectStore.setActiveBoard(currentBoard);
|
||||
// router.push({
|
||||
// pathname: `/${workspace_slug}/${project_slug}`,
|
||||
// query: { ...params },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }, [
|
||||
// board,
|
||||
// workspace_slug,
|
||||
// project_slug,
|
||||
// router,
|
||||
// projectStore,
|
||||
// projectStore?.deploySettings,
|
||||
// updateFilters,
|
||||
// labels,
|
||||
// states,
|
||||
// priorities,
|
||||
// peekId,
|
||||
// ]);
|
||||
if (storeParams) updateFilters(projectId, storeParams);
|
||||
setActiveLayout(currentBoard);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
board,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
router,
|
||||
updateFilters,
|
||||
labels,
|
||||
states,
|
||||
priorities,
|
||||
peekId,
|
||||
settings,
|
||||
activeLayout,
|
||||
setActiveLayout,
|
||||
searchParams,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="relative flex w-full items-center gap-4 px-5">
|
||||
@ -121,7 +122,7 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||
|
||||
{/* issue filters */}
|
||||
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
||||
<IssueFiltersDropdown />
|
||||
<IssueFiltersDropdown workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
|
||||
{/* theming */}
|
||||
@ -136,7 +137,7 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/?next_path=${router.asPath}`}>
|
||||
<Link href={`/?next_path=${pathName}`}>
|
||||
<Button variant="outline-primary">Sign in</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useRef } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
// components
|
||||
import { EditorRefApi } from "@plane/lite-text-editor";
|
||||
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
|
||||
// hooks
|
||||
import { useMobxStore, useUser } from "@/hooks/store";
|
||||
import { useIssueDetails, useProject, useUser } from "@/hooks/store";
|
||||
import useToast from "@/hooks/use-toast";
|
||||
// types
|
||||
import { Comment } from "@/types/issue";
|
||||
@ -17,22 +16,21 @@ const defaultValues: Partial<Comment> = {
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const AddComment: React.FC<Props> = observer(() => {
|
||||
export const AddComment: React.FC<Props> = observer((props) => {
|
||||
// const { disabled = false } = props;
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// refs
|
||||
const editorRef = useRef<EditorRefApi>(null);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspace_slug, project_slug } = router.query;
|
||||
// store hooks
|
||||
const { project } = useMobxStore();
|
||||
const { issueDetails: issueDetailStore } = useMobxStore();
|
||||
const { workspace } = useProject();
|
||||
const { peekId: issueId, addIssueComment } = useIssueDetails();
|
||||
const { data: currentUser } = useUser();
|
||||
// derived values
|
||||
const workspaceId = project.workspace?.id;
|
||||
const issueId = issueDetailStore.peekId;
|
||||
const workspaceId = workspace?.id;
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
@ -45,10 +43,9 @@ export const AddComment: React.FC<Props> = observer(() => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const onSubmit = async (formData: Comment) => {
|
||||
if (!workspace_slug || !project_slug || !issueId || isSubmitting || !formData.comment_html) return;
|
||||
if (!workspaceSlug || !projectId || !issueId || isSubmitting || !formData.comment_html) return;
|
||||
|
||||
await issueDetailStore
|
||||
.addIssueComment(workspace_slug.toString(), project_slug.toString(), issueId, formData)
|
||||
await addIssueComment(workspaceSlug, projectId, issueId, formData)
|
||||
.then(() => {
|
||||
reset(defaultValues);
|
||||
editorRef.current?.clearEditor();
|
||||
@ -75,7 +72,7 @@ export const AddComment: React.FC<Props> = observer(() => {
|
||||
if (currentUser) handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
workspaceId={workspaceId as string}
|
||||
workspaceSlug={workspace_slug as string}
|
||||
workspaceSlug={workspaceSlug}
|
||||
ref={editorRef}
|
||||
initialValue={
|
||||
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
|
||||
|
@ -10,9 +10,7 @@ import { CommentReactions } from "@/components/issues/peek-overview";
|
||||
// helpers
|
||||
import { timeAgo } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
import { useMobxStore, useUser } from "@/hooks/store";
|
||||
// store
|
||||
import { RootStore } from "@/store/root.store";
|
||||
import { useIssueDetails, useProject, useUser } from "@/hooks/store";
|
||||
// types
|
||||
import { Comment } from "@/types/issue";
|
||||
|
||||
@ -23,12 +21,13 @@ type Props = {
|
||||
|
||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
const { comment, workspaceSlug } = props;
|
||||
const { project }: RootStore = useMobxStore();
|
||||
const workspaceId = project.workspace?.id;
|
||||
|
||||
// store
|
||||
const { issueDetails: issueDetailStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { workspace } = useProject();
|
||||
const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails();
|
||||
const { data: currentUser } = useUser();
|
||||
// derived values
|
||||
const workspaceId = workspace?.id;
|
||||
|
||||
// states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
// refs
|
||||
@ -44,15 +43,14 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!workspaceSlug || !issueDetailStore.peekId) return;
|
||||
issueDetailStore.deleteIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id);
|
||||
if (!workspaceSlug || !peekId) return;
|
||||
deleteIssueComment(workspaceSlug, comment.project, peekId, comment.id);
|
||||
};
|
||||
|
||||
const handleCommentUpdate = async (formData: Comment) => {
|
||||
if (!workspaceSlug || !issueDetailStore.peekId) return;
|
||||
issueDetailStore.updateIssueComment(workspaceSlug, comment.project, issueDetailStore.peekId, comment.id, formData);
|
||||
if (!workspaceSlug || !peekId) return;
|
||||
updateIssueComment(workspaceSlug, comment.project, peekId, comment.id, formData);
|
||||
setIsEditing(false);
|
||||
|
||||
editorRef.current?.setEditorValue(formData.comment_html);
|
||||
showEditorRef.current?.setEditorValue(formData.comment_html);
|
||||
};
|
||||
@ -135,7 +133,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
</form>
|
||||
<div className={`${isEditing ? "hidden" : ""}`}>
|
||||
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} />
|
||||
<CommentReactions commentId={comment.id} projectId={comment.project} />
|
||||
<CommentReactions commentId={comment.id} projectId={comment.project} workspaceSlug={workspaceSlug} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,58 +1,38 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// ui
|
||||
import { ReactionSelector } from "@/components/ui";
|
||||
// helpers
|
||||
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useMobxStore, useUser } from "@/hooks/store";
|
||||
import { useIssueDetails, useUser } from "@/hooks/store";
|
||||
|
||||
type Props = {
|
||||
commentId: string;
|
||||
projectId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CommentReactions: React.FC<Props> = observer((props) => {
|
||||
const { commentId, projectId } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspace_slug } = router.query;
|
||||
const { commentId, projectId, workspaceSlug } = props;
|
||||
// hooks
|
||||
const { issueDetails: issueDetailsStore } = useMobxStore();
|
||||
const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails();
|
||||
const { data: user } = useUser();
|
||||
|
||||
const peekId = issueDetailsStore.peekId;
|
||||
const commentReactions = peekId
|
||||
? issueDetailsStore.details[peekId].comments.find((c) => c.id === commentId)?.comment_reactions
|
||||
: [];
|
||||
const commentReactions = peekId ? details[peekId].comments.find((c) => c.id === commentId)?.comment_reactions : [];
|
||||
const groupedReactions = peekId ? groupReactions(commentReactions ?? [], "reaction") : {};
|
||||
|
||||
const userReactions = commentReactions?.filter((r) => r.actor_detail.id === user?.id);
|
||||
|
||||
const handleAddReaction = (reactionHex: string) => {
|
||||
if (!workspace_slug || !projectId || !peekId) return;
|
||||
|
||||
issueDetailsStore.addCommentReaction(
|
||||
workspace_slug.toString(),
|
||||
projectId.toString(),
|
||||
peekId,
|
||||
commentId,
|
||||
reactionHex
|
||||
);
|
||||
if (!workspaceSlug || !projectId || !peekId) return;
|
||||
addCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex);
|
||||
};
|
||||
|
||||
const handleRemoveReaction = (reactionHex: string) => {
|
||||
if (!workspace_slug || !projectId || !peekId) return;
|
||||
|
||||
issueDetailsStore.removeCommentReaction(
|
||||
workspace_slug.toString(),
|
||||
projectId.toString(),
|
||||
peekId,
|
||||
commentId,
|
||||
reactionHex
|
||||
);
|
||||
if (!workspaceSlug || !projectId || !peekId) return;
|
||||
removeCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex);
|
||||
};
|
||||
|
||||
const handleReactionClick = (reactionHex: string) => {
|
||||
|
@ -14,10 +14,11 @@ type Props = {
|
||||
handleClose: () => void;
|
||||
issueDetails: IIssue | undefined;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
||||
const { handleClose, issueDetails } = props;
|
||||
const { handleClose, issueDetails, workspaceSlug, projectId } = props;
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full grid-cols-10 divide-x divide-custom-border-200 overflow-hidden">
|
||||
@ -35,7 +36,11 @@ export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
||||
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
||||
{/* issue activity/comments */}
|
||||
<div className="w-full pb-5">
|
||||
<PeekOverviewIssueActivity issueDetails={issueDetails} />
|
||||
<PeekOverviewIssueActivity
|
||||
issueDetails={issueDetails}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -2,19 +2,17 @@ import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { MoveRight } from "lucide-react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
// ui
|
||||
import { Icon } from "@/components/ui";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useIssueDetails } from "@/hooks/store";
|
||||
import useToast from "@/hooks/use-toast";
|
||||
// store
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { IPeekMode } from "@/store/issue-detail.store";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
// lib
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IIssue } from "types/issue";
|
||||
import { IIssue } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
handleClose: () => void;
|
||||
@ -42,7 +40,7 @@ const peekModes: {
|
||||
export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
||||
const { handleClose } = props;
|
||||
|
||||
const { issueDetails: issueDetailStore }: RootStore = useMobxStore();
|
||||
const { peekMode, setPeekMode } = useIssueDetails();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -62,21 +60,19 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
{issueDetailStore.peekMode === "side" && (
|
||||
{peekMode === "side" && (
|
||||
<button type="button" onClick={handleClose}>
|
||||
<MoveRight className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
</button>
|
||||
)}
|
||||
<Listbox
|
||||
as="div"
|
||||
value={issueDetailStore.peekMode}
|
||||
onChange={(val) => issueDetailStore.setPeekMode(val)}
|
||||
value={peekMode}
|
||||
onChange={(val) => setPeekMode(val)}
|
||||
className="relative flex-shrink-0 text-left"
|
||||
>
|
||||
<Listbox.Button
|
||||
className={`grid place-items-center ${issueDetailStore.peekMode === "full" ? "rotate-45" : ""}`}
|
||||
>
|
||||
<Icon iconName={peekModes.find((m) => m.key === issueDetailStore.peekMode)?.icon ?? ""} />
|
||||
<Listbox.Button className={`grid place-items-center ${peekMode === "full" ? "rotate-45" : ""}`}>
|
||||
<Icon iconName={peekModes.find((m) => m.key === peekMode)?.icon ?? ""} />
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
@ -121,7 +117,7 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
||||
</Transition>
|
||||
</Listbox>
|
||||
</div>
|
||||
{(issueDetailStore.peekMode === "side" || issueDetailStore.peekMode === "modal") && (
|
||||
{(peekMode === "side" || peekMode === "modal") && (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<button type="button" onClick={handleCopyLink} className="-rotate-45 focus:outline-none" tabIndex={1}>
|
||||
<Icon iconName="link" />
|
||||
|
@ -1,44 +1,48 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
|
||||
import { Icon } from "@/components/ui";
|
||||
// hooks
|
||||
import { useMobxStore, useUser } from "@/hooks/store";
|
||||
import { useIssueDetails, useProject, useUser } from "@/hooks/store";
|
||||
// types
|
||||
import { IIssue } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
issueDetails: IIssue;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const PeekOverviewIssueActivity: React.FC<Props> = observer(() => {
|
||||
export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspace_slug } = router.query;
|
||||
const pathname = usePathname();
|
||||
// store
|
||||
const { issueDetails: issueDetailStore, project: projectStore } = useMobxStore();
|
||||
const { canComment } = useProject();
|
||||
const { details, peekId } = useIssueDetails();
|
||||
const { data: currentUser } = useUser();
|
||||
const comments = issueDetailStore.details[issueDetailStore.peekId || ""]?.comments || [];
|
||||
|
||||
const comments = details[peekId || ""]?.comments || [];
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<h4 className="font-medium">Activity</h4>
|
||||
{workspace_slug && (
|
||||
{workspaceSlug && (
|
||||
<div className="mt-4">
|
||||
<div className="space-y-4">
|
||||
{comments.map((comment: any) => (
|
||||
<CommentCard key={comment.id} comment={comment} workspaceSlug={workspace_slug?.toString()} />
|
||||
<CommentCard key={comment.id} comment={comment} workspaceSlug={workspaceSlug?.toString()} />
|
||||
))}
|
||||
</div>
|
||||
{currentUser ? (
|
||||
<>
|
||||
{projectStore.deploySettings?.comments && (
|
||||
{canComment && (
|
||||
<div className="mt-4">
|
||||
<AddComment disabled={!currentUser} />
|
||||
<AddComment disabled={!currentUser} workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@ -48,7 +52,7 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer(() => {
|
||||
<Icon iconName="lock" className="!text-sm" />
|
||||
Sign in to add your comment
|
||||
</p>
|
||||
<Link href={`/?next_path=${router.asPath}`}>
|
||||
<Link href={`/?next_path=${pathname}`}>
|
||||
<Button variant="primary">Sign in</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { useParams } from "next/navigation";
|
||||
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
|
||||
import { useProject } from "@/hooks/store";
|
||||
|
||||
type IssueReactionsProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
// type IssueReactionsProps = {
|
||||
// workspaceSlug: string;
|
||||
// projectId: string;
|
||||
// };
|
||||
|
||||
export const IssueReactions: React.FC = () => {
|
||||
const { workspace_slug: workspaceSlug, project_id: projectId } = useParams<any>();
|
||||
|
||||
export const IssueReactions: React.FC<IssueReactionsProps> = (props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
const { canVote, canReact } = useProject();
|
||||
|
||||
return (
|
||||
|
@ -68,7 +68,12 @@ export const IssuePeekOverview: React.FC = observer((props: any) => {
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<Dialog.Panel className="fixed right-0 top-0 z-20 h-full w-1/2 bg-custom-background-100 shadow-custom-shadow-sm">
|
||||
<SidePeekView handleClose={handleClose} issueDetails={issueDetails} />
|
||||
<SidePeekView
|
||||
handleClose={handleClose}
|
||||
issueDetails={issueDetails}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</Dialog>
|
||||
@ -102,13 +107,19 @@ export const IssuePeekOverview: React.FC = observer((props: any) => {
|
||||
}`}
|
||||
>
|
||||
{issueDetailStore.peekMode === "modal" && (
|
||||
<SidePeekView handleClose={handleClose} issueDetails={issueDetails} />
|
||||
<SidePeekView
|
||||
handleClose={handleClose}
|
||||
issueDetails={issueDetails}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
{issueDetailStore.peekMode === "full" && (
|
||||
<FullScreenPeekView
|
||||
workspaceSlug={workspaceSlug}
|
||||
handleClose={handleClose}
|
||||
issueDetails={issueDetails}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -7,16 +7,18 @@ import {
|
||||
PeekOverviewIssueDetails,
|
||||
PeekOverviewIssueProperties,
|
||||
} from "@/components/issues/peek-overview";
|
||||
|
||||
import { IIssue } from "types/issue";
|
||||
// types
|
||||
import { IIssue } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
handleClose: () => void;
|
||||
issueDetails: IIssue | undefined;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const SidePeekView: React.FC<Props> = observer((props) => {
|
||||
const { handleClose, issueDetails } = props;
|
||||
const { handleClose, issueDetails, workspaceSlug, projectId } = props;
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
@ -37,7 +39,11 @@ export const SidePeekView: React.FC<Props> = observer((props) => {
|
||||
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
||||
{/* issue activity/comments */}
|
||||
<div className="w-full pb-5">
|
||||
<PeekOverviewIssueActivity issueDetails={issueDetails} />
|
||||
<PeekOverviewIssueActivity
|
||||
issueDetails={issueDetails}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { FC, useEffect } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Image from "next/image";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
||||
@ -20,12 +21,13 @@ import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||
type ProjectDetailsViewProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
params: any;
|
||||
peekId: string;
|
||||
};
|
||||
|
||||
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, params } = props;
|
||||
const { states, labels, priorities, peekId } = params;
|
||||
const { workspaceSlug, projectId, peekId } = props;
|
||||
// router
|
||||
const params = useParams();
|
||||
// store hooks
|
||||
const { fetchPublicIssues } = useIssue();
|
||||
const { activeLayout } = useProject();
|
||||
@ -34,7 +36,7 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
||||
workspaceSlug && projectId ? "PROJECT_PUBLIC_ISSUES" : null,
|
||||
workspaceSlug && projectId ? () => fetchPublicIssues(workspaceSlug, projectId, params) : null
|
||||
);
|
||||
|
||||
// store hooks
|
||||
const issueStore = useIssue();
|
||||
const issueDetailStore = useIssueDetails();
|
||||
const { data: currentUser, fetchCurrentUser } = useUser();
|
||||
@ -79,12 +81,12 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
||||
|
||||
{activeLayout === "list" && (
|
||||
<div className="relative h-full w-full overflow-y-auto">
|
||||
<IssueListView />
|
||||
<IssueListView workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
)}
|
||||
{activeLayout === "kanban" && (
|
||||
<div className="relative mx-auto h-full w-full p-5">
|
||||
<IssueKanbanView />
|
||||
<IssueKanbanView workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
)}
|
||||
{activeLayout === "calendar" && <IssueCalendarView />}
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { useMobxStore } from "@/hooks/store";
|
||||
import { RootStore } from "@/store/root.store";
|
||||
|
||||
const useEditorSuggestions = () => {
|
||||
const { mentionsStore }: RootStore = useMobxStore();
|
||||
|
||||
return {
|
||||
// mentionSuggestions: mentionsStore.mentionSuggestions,
|
||||
mentionHighlights: mentionsStore.mentionHighlights,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEditorSuggestions;
|
@ -1,2 +1 @@
|
||||
export * from "./instance-wrapper";
|
||||
export * from "./auth-wrapper";
|
||||
|
@ -1,39 +0,0 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { InstanceNotReady, InstanceFailureView } from "@/components/instance";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
type TInstanceWrapper = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
||||
const { children } = props;
|
||||
// hooks
|
||||
const { isLoading, instance, fetchInstanceInfo } = useInstance();
|
||||
|
||||
const { isLoading: isSWRLoading, mutate } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnReconnect: false,
|
||||
errorRetryCount: 0,
|
||||
});
|
||||
|
||||
if (isSWRLoading || isLoading)
|
||||
return (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!instance) return <InstanceFailureView mutate={mutate} />;
|
||||
|
||||
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />;
|
||||
|
||||
return <>{children}</>;
|
||||
});
|
@ -17,6 +17,7 @@
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
],
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user