fix: space app dir fixes

This commit is contained in:
sriram veeraghanta 2024-05-13 23:22:26 +05:30
parent 9eac78ae83
commit 55a173d3e3
67 changed files with 613 additions and 798 deletions

View File

@ -0,0 +1,5 @@
"use client";
export default function ProjectError() {
return <>Project Error</>;
}

View File

@ -0,0 +1,38 @@
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
import planeLogo from "public/plane-logo.svg";
const projectService = new ProjectService();
export default async function ProjectLayout({ children, params }: { children: React.ReactNode; params: any }) {
const { workspace_slug, project_id } = params;
const projectSettings = await projectService.getProjectSettings(workspace_slug, project_id);
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} />
</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<a
href="https://plane.so"
className="fixed bottom-2.5 right-5 !z-[999999] flex items-center gap-1 rounded border border-custom-border-200 bg-custom-background-100 px-2 py-1 shadow-custom-shadow-2xs"
target="_blank"
rel="noreferrer noopener"
>
<div className="relative grid h-6 w-6 place-items-center">
<Image src={planeLogo} alt="Plane logo" className="h-6 w-6" height="24" width="24" />
</div>
<div className="text-xs">
Powered by <span className="font-semibold">Plane Deploy</span>
</div>
</a>
</div>
);
}

View File

@ -0,0 +1,8 @@
// components
import { ProjectDetailsView } from "@/components/views";
export default async function WorkspaceProjectPage({ params }: { params: any }) {
const { workspace_slug, project_id, ...rest } = params;
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} params={rest} />;
}

View File

@ -1,30 +0,0 @@
import { observer } from "mobx-react-lite";
import Image from "next/image";
// components
import IssueNavbar from "@/components/issues/navbar";
// assets
import planeLogo from "public/plane-logo.svg";
const ProjectLayout = ({ children }: { children: React.ReactNode }) => (
<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 />
</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<a
href="https://plane.so"
className="fixed bottom-2.5 right-5 !z-[999999] flex items-center gap-1 rounded border border-custom-border-200 bg-custom-background-100 px-2 py-1 shadow-custom-shadow-2xs"
target="_blank"
rel="noreferrer noopener"
>
<div className="relative grid h-6 w-6 place-items-center">
<Image src={planeLogo} alt="Plane logo" className="h-6 w-6" height="24" width="24" />
</div>
<div className="text-xs">
Powered by <span className="font-semibold">Plane Deploy</span>
</div>
</a>
</div>
);
export default observer(ProjectLayout);

View File

@ -1,44 +0,0 @@
import Head from "next/head";
import { useRouter } from "next/router";
import useSWR from "swr";
// components
import { ProjectDetailsView } from "@/components/views";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useMobxStore } from "@/hooks/store";
// layouts
import ProjectLayout from "@/layouts/project-layout";
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
const WorkspaceProjectPage = (props: any) => {
const SITE_TITLE = props?.project_settings?.project_details?.name || "Plane | Deploy";
const router = useRouter();
const { workspace_slug, project_slug, states, labels, priorities } = router.query;
const { project: projectStore, issue: issueStore } = useMobxStore();
useSWR("REVALIDATE_ALL", () => {
if (workspace_slug && project_slug) {
projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString());
const params = {
state: states || null,
labels: labels || null,
priority: priorities || null,
};
issueStore.fetchPublicIssues(workspace_slug.toString(), project_slug.toString(), params);
}
});
return (
<AuthWrapper pageType={EPageTypes.AUTHENTICATED}>
<ProjectLayout>
<ProjectDetailsView />
</ProjectLayout>
</AuthWrapper>
);
};
export default WorkspaceProjectPage;

View File

@ -1,20 +1,31 @@
"use client";
// components // components
import { UserLoggedIn } from "@/components/accounts";
import { AuthView } from "@/components/views"; import { AuthView } from "@/components/views";
// helpers // helpers
import { EPageTypes } from "@/helpers/authentication.helper"; // import { EPageTypes } from "@/helpers/authentication.helper";
import { useInstance } from "@/hooks/store"; // import { useInstance, useUser } from "@/hooks/store";
// wrapper // wrapper
import { AuthWrapper } from "@/lib/wrappers"; // import { AuthWrapper } from "@/lib/wrappers";
// services
import { UserService } from "@/services/user.service";
export default function HomePage() { const userServices = new UserService();
const { data } = useInstance();
console.log("data", data); export default async function HomePage() {
const user = await userServices
.currentUser()
.then((user) => ({ ...user, isAuthenticated: true }))
.catch(() => ({ isAuthenticated: false }));
return ( // const { data } = useInstance();
<AuthWrapper pageType={EPageTypes.INIT}>
<AuthView /> // console.log("data", data);
</AuthWrapper> console.log("user", user);
);
if (user.isAuthenticated) {
return <UserLoggedIn />;
}
// return <>Login View</>;
return <AuthView />;
} }

View File

@ -1,3 +1,5 @@
"use client";
import React from "react"; import React from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// icons // icons

View File

@ -1,3 +1,4 @@
"use client";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
import { X } from "lucide-react"; import { X } from "lucide-react";

View File

@ -1,7 +1,9 @@
"use client";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
// icons // icons
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
import { Eye, EyeOff, XCircle } from "lucide-react"; import { Eye, EyeOff, XCircle } from "lucide-react";
// ui // ui
import { Button, Input, Spinner } from "@plane/ui"; import { Button, Input, Spinner } from "@plane/ui";
@ -12,7 +14,7 @@ import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper"; import { getPasswordStrength } from "@/helpers/password.helper";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
import { AuthService } from "@/services/authentication.service"; import { AuthService } from "@/services/auth.service";
type Props = { type Props = {
email: string; email: string;
@ -43,12 +45,12 @@ export const PasswordForm: React.FC<Props> = (props) => {
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
// hooks // hooks
const { instance } = useInstance(); const { data: instance, config: instanceConfig } = useInstance();
// router // router
const router = useRouter(); const router = useRouter();
const { next_path } = router.query; const { next_path } = router.query;
// derived values // derived values
const isSmtpConfigured = instance?.config?.is_smtp_configured; const isSmtpConfigured = instanceConfig?.is_smtp_configured;
const handleFormChange = (key: keyof TPasswordFormValues, value: string) => const handleFormChange = (key: keyof TPasswordFormValues, value: string) =>
setPasswordFormData((prev) => ({ ...prev, [key]: value })); setPasswordFormData((prev) => ({ ...prev, [key]: value }));

View File

@ -1,3 +1,5 @@
"use client";
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
@ -7,7 +9,7 @@ import { EmailForm, UniqueCodeForm, PasswordForm, OAuthOptions, TermsAndConditio
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
import useToast from "@/hooks/use-toast"; import useToast from "@/hooks/use-toast";
// services // services
import { AuthService } from "@/services/authentication.service"; import { AuthService } from "@/services/auth.service";
export enum EAuthSteps { export enum EAuthSteps {
EMAIL = "EMAIL", EMAIL = "EMAIL",
@ -60,9 +62,9 @@ export const AuthRoot = observer(() => {
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL); const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
// hooks // hooks
const { instance } = useInstance(); const { config: instanceConfig } = useInstance();
// derived values // derived values
const isSmtpConfigured = instance?.config?.is_smtp_configured; const isSmtpConfigured = instanceConfig?.is_smtp_configured;
const { header, subHeader } = getHeaderSubHeader(authMode); const { header, subHeader } = getHeaderSubHeader(authMode);
@ -112,8 +114,8 @@ export const AuthRoot = observer(() => {
); );
}; };
const isOAuthEnabled = const isOAuthEnabled = instanceConfig && (instanceConfig?.is_google_enabled || instanceConfig?.is_github_enabled);
instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled);
return ( return (
<div className="relative flex flex-col space-y-6"> <div className="relative flex flex-col space-y-6">
<div className="space-y-1 text-center"> <div className="space-y-1 text-center">
@ -149,7 +151,7 @@ export const AuthRoot = observer(() => {
)} )}
</> </>
)} )}
{isOAuthEnabled && <OAuthOptions />} {isOAuthEnabled !== undefined && <OAuthOptions />}
<TermsAndConditions mode={authMode} /> <TermsAndConditions mode={authMode} />
</div> </div>
); );

View File

@ -1,5 +1,7 @@
"use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
// icons // icons
import { CircleCheck, XCircle } from "lucide-react"; import { CircleCheck, XCircle } from "lucide-react";
// ui // ui
@ -10,7 +12,7 @@ import { API_BASE_URL } from "@/helpers/common.helper";
import useTimer from "@/hooks/use-timer"; import useTimer from "@/hooks/use-timer";
import useToast from "@/hooks/use-toast"; import useToast from "@/hooks/use-toast";
// services // services
import { AuthService } from "@/services/authentication.service"; import { AuthService } from "@/services/auth.service";
// types // types
import { IEmailCheckData } from "@/types/auth"; import { IEmailCheckData } from "@/types/auth";
import { EAuthModes } from "./root"; import { EAuthModes } from "./root";

View File

@ -1,3 +1,5 @@
"use client";
import { FC } from "react"; import { FC } from "react";
import Image from "next/image"; import Image from "next/image";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";

View File

@ -1,3 +1,5 @@
"use client";
import { FC } from "react"; import { FC } from "react";
import Image from "next/image"; import Image from "next/image";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";

View File

@ -1,3 +1,5 @@
"use client";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { GithubOAuthButton, GoogleOAuthButton } from "@/components/accounts"; import { GithubOAuthButton, GoogleOAuthButton } from "@/components/accounts";
@ -6,7 +8,7 @@ import { useInstance } from "@/hooks/store";
export const OAuthOptions: React.FC = observer(() => { export const OAuthOptions: React.FC = observer(() => {
// hooks // hooks
const { instance } = useInstance(); const { config: instanceConfig } = useInstance();
return ( return (
<> <>
@ -16,12 +18,12 @@ export const OAuthOptions: React.FC = observer(() => {
<hr className="w-full border-onboarding-border-100" /> <hr className="w-full border-onboarding-border-100" />
</div> </div>
<div className={`mx-auto mt-7 grid gap-4 overflow-hidden sm:w-96`}> <div className={`mx-auto mt-7 grid gap-4 overflow-hidden sm:w-96`}>
{instance?.config?.is_google_enabled && ( {instanceConfig?.is_google_enabled && (
<div className="flex h-[42px] items-center !overflow-hidden"> <div className="flex h-[42px] items-center !overflow-hidden">
<GoogleOAuthButton text="SignIn with Google" /> <GoogleOAuthButton text="SignIn with Google" />
</div> </div>
)} )}
{instance?.config?.is_github_enabled && <GithubOAuthButton text="SignIn with Github" />} {instanceConfig?.is_github_enabled && <GithubOAuthButton text="SignIn with Github" />}
</div> </div>
</> </>
); );

View File

@ -1,3 +1,5 @@
"use client";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
@ -8,7 +10,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { UserImageUploadModal } from "@/components/accounts"; import { UserImageUploadModal } from "@/components/accounts";
// hooks // hooks
import { useMobxStore } from "@/hooks/store"; import { useUser } from "@/hooks/store";
// services // services
import fileService from "@/services/file.service"; import fileService from "@/services/file.service";
@ -35,9 +37,7 @@ export const OnBoardingForm: React.FC<Props> = observer((props) => {
const [isRemoving, setIsRemoving] = useState(false); const [isRemoving, setIsRemoving] = useState(false);
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
// store hooks // store hooks
const { const { updateCurrentUser } = useUser();
user: { updateCurrentUser },
} = useMobxStore();
// form info // form info
const { const {
getValues, getValues,

View File

@ -1,3 +1,5 @@
"use client";
// icons // icons
import { CircleCheck } from "lucide-react"; import { CircleCheck } from "lucide-react";
// helpers // helpers

View File

@ -1,3 +1,5 @@
"use client";
import React, { FC } from "react"; import React, { FC } from "react";
import Link from "next/link"; import Link from "next/link";
import { EAuthModes } from "./auth-forms"; import { EAuthModes } from "./auth-forms";

View File

@ -1,3 +1,4 @@
"use client";
import React, { useState } from "react"; import React, { useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
@ -27,7 +28,7 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
const [image, setImage] = useState<File | null>(null); const [image, setImage] = useState<File | null>(null);
const [isImageUploading, setIsImageUploading] = useState(false); const [isImageUploading, setIsImageUploading] = useState(false);
// store hooks // store hooks
const { instance } = useInstance(); const { config: instanceConfig } = useInstance();
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]); const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
@ -36,7 +37,7 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
accept: { accept: {
"image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"], "image/*": [".png", ".jpg", ".jpeg", ".svg", ".webp"],
}, },
maxSize: instance?.config?.file_size_limit ?? MAX_FILE_SIZE, maxSize: (instanceConfig?.file_size_limit as number) ?? MAX_FILE_SIZE,
multiple: false, multiple: false,
}); });

View File

@ -1,3 +1,5 @@
"use client";
import Image from "next/image"; import Image from "next/image";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser } from "@/hooks/store";

View File

@ -2,7 +2,7 @@
// mobx react lite // mobx react lite
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority"; import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
import { IssueBlockState } from "@/components/issues/board-views/block-state"; import { IssueBlockState } from "@/components/issues/board-views/block-state";

View File

@ -1,6 +1,6 @@
import { FC } from "react"; import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
// components // components
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date"; import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels"; import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";

View File

@ -2,27 +2,24 @@ import { observer } from "mobx-react-lite";
// components // components
import { IssueListBlock } from "@/components/issues/board-views/list/block"; import { IssueListBlock } from "@/components/issues/board-views/list/block";
import { IssueListHeader } from "@/components/issues/board-views/list/header"; import { IssueListHeader } from "@/components/issues/board-views/list/header";
// interfaces
// mobx hook // mobx hook
import { useMobxStore } from "@/hooks/store"; import { useIssue } from "@/hooks/store";
// store // types
import { RootStore } from "@/store/root.store"; import { IIssueState, IIssue } from "@/types/issue";
import { IIssueState, IIssue } from "types/issue";
export const IssueListView = observer(() => { export const IssueListView = observer(() => {
const { issue: issueStore }: RootStore = useMobxStore(); const { states, getFilteredIssuesByState } = useIssue();
return ( return (
<> <>
{issueStore?.states && {states &&
issueStore?.states.length > 0 && states.length > 0 &&
issueStore?.states.map((_state: IIssueState) => ( states.map((_state: IIssueState) => (
<div key={_state.id} className="relative w-full"> <div key={_state.id} className="relative w-full">
<IssueListHeader state={_state} /> <IssueListHeader state={_state} />
{issueStore.getFilteredIssuesByState(_state.id) && {getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
issueStore.getFilteredIssuesByState(_state.id).length > 0 ? (
<div className="divide-y divide-custom-border-200"> <div className="divide-y divide-custom-border-200">
{issueStore.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => ( {getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
<IssueListBlock key={_issue.id} issue={_issue} /> <IssueListBlock key={_issue.id} issue={_issue} />
))} ))}
</div> </div>

View File

@ -1,33 +1,31 @@
"use client";
import { FC, useCallback } from "react"; import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
// components // hooks
import { useIssue, useProject, useIssueFilter } from "@/hooks/store";
// store // store
import { useMobxStore } from "@/hooks/store"; import { IIssueFilterOptions } from "@/types/issue";
import { IIssueFilterOptions } from "@/store/issues/types"; // components
import { RootStore } from "@/store/root.store";
import { AppliedFiltersList } from "./filters-list"; import { AppliedFiltersList } from "./filters-list";
export const IssueAppliedFilters: FC = observer(() => { // TODO: fix component types
export const IssueAppliedFilters: FC = observer((props: any) => {
const router = useRouter(); const router = useRouter();
const { workspace_slug: workspaceSlug, project_slug: projectId } = router.query as { const { workspaceSlug, projectId } = props;
workspace_slug: string; const { states, labels } = useIssue();
project_slug: string; const { activeLayout } = useProject();
}; const { issueFilters, updateFilters } = useIssueFilter();
const {
issuesFilter: { issueFilters, updateFilters },
issue: { states, labels },
project: { activeBoard },
}: RootStore = useMobxStore();
const userFilters = issueFilters?.filters || {}; const userFilters = issueFilters?.filters || {};
const appliedFilters: IIssueFilterOptions = {}; const appliedFilters: any = {};
Object.entries(userFilters).forEach(([key, value]) => { Object.entries(userFilters).forEach(([key, value]) => {
if (!value) return; if (!value) return;
if (Array.isArray(value) && value.length === 0) return; if (Array.isArray(value) && value.length === 0) return;
appliedFilters[key as keyof IIssueFilterOptions] = value; appliedFilters[key] = value;
}); });
const updateRouteParams = useCallback( const updateRouteParams = useCallback(
@ -36,16 +34,17 @@ export const IssueAppliedFilters: FC = observer(() => {
const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? []; const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? [];
const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? []; const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? [];
let params: any = { board: activeBoard || "list" }; let params: any = { board: activeLayout || "list" };
if (!clearFields) { if (!clearFields) {
if (priority.length > 0) params = { ...params, priorities: priority.join(",") }; if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
if (state.length > 0) params = { ...params, states: state.join(",") }; if (state.length > 0) params = { ...params, states: state.join(",") };
if (labels.length > 0) params = { ...params, labels: labels.join(",") }; if (labels.length > 0) params = { ...params, labels: labels.join(",") };
} }
console.log("params", params);
router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true }); // TODO: fix this redirection
// router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true });
}, },
[workspaceSlug, projectId, activeBoard, issueFilters, router] [workspaceSlug, projectId, activeLayout, issueFilters, router]
); );
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => { const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
@ -80,7 +79,7 @@ export const IssueAppliedFilters: FC = observer(() => {
<div className="border-b border-custom-border-200 p-5 py-3"> <div className="border-b border-custom-border-200 p-5 py-3">
<AppliedFiltersList <AppliedFiltersList
appliedFilters={appliedFilters || {}} appliedFilters={appliedFilters || {}}
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter as any}
handleRemoveAllFilters={handleRemoveAllFilters} handleRemoveAllFilters={handleRemoveAllFilters}
labels={labels ?? []} labels={labels ?? []}
states={states ?? []} states={states ?? []}

View File

@ -1,6 +1,6 @@
import { FC, useCallback } from "react"; import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
// components // components
import { useMobxStore } from "@/hooks/store"; import { useMobxStore } from "@/hooks/store";
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";

View File

@ -1,115 +1,108 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect, FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
// components // components
import { Briefcase } from "lucide-react"; import { Briefcase } from "lucide-react";
import { Avatar, Button } from "@plane/ui"; import { Avatar, Button } from "@plane/ui";
import { ProjectLogo } from "@/components/common"; import { ProjectLogo } from "@/components/common";
import { IssueFiltersDropdown } from "@/components/issues/filters"; import { IssueFiltersDropdown } from "@/components/issues/filters";
// hooks // hooks
import { useMobxStore, useUser } from "@/hooks/store"; import { useProject, useUser } from "@/hooks/store";
// store // store
import { RootStore } from "@/store/root.store";
import { TIssueBoardKeys } from "@/types/issue";
import { NavbarIssueBoardView } from "./issue-board-view"; import { NavbarIssueBoardView } from "./issue-board-view";
import { NavbarTheme } from "./theme"; import { NavbarTheme } from "./theme";
const IssueNavbar = observer(() => { type IssueNavbarProps = {
const { projectSettings: any;
project: projectStore, };
issuesFilter: { updateFilters },
}: RootStore = useMobxStore(); const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
const { data: user } = useUser(); const { projectSettings } = props;
// router const { project_details, views } = projectSettings;
console.log("projectSettings", projectSettings);
// hooks
const router = useRouter(); const router = useRouter();
const { workspace_slug, project_slug, board, peekId, states, priorities, labels } = router.query as { // store
workspace_slug: string; const { settings, activeLayout, hydrate } = useProject();
project_slug: string; hydrate(projectSettings);
peekId: string; const { data: user } = useUser();
board: string; console.log("user", user);
states: string;
priorities: string;
labels: string;
};
useEffect(() => { // return <>layout</>;
if (workspace_slug && project_slug) {
projectStore.fetchProjectSettings(workspace_slug.toString(), project_slug.toString());
}
}, [projectStore, workspace_slug, project_slug]);
useEffect(() => { // useEffect(() => {
if (workspace_slug && project_slug && projectStore?.deploySettings) { // if (workspace_slug && project_slug && settings) {
const viewsAcceptable: string[] = []; // const viewsAcceptable: string[] = [];
let currentBoard: TIssueBoardKeys | null = null; // let currentBoard: TIssueBoardKeys | null = null;
if (projectStore?.deploySettings?.views?.list) viewsAcceptable.push("list"); // if (settings?.views?.list) viewsAcceptable.push("list");
if (projectStore?.deploySettings?.views?.kanban) viewsAcceptable.push("kanban"); // if (settings?.views?.kanban) viewsAcceptable.push("kanban");
if (projectStore?.deploySettings?.views?.calendar) viewsAcceptable.push("calendar"); // if (settings?.views?.calendar) viewsAcceptable.push("calendar");
if (projectStore?.deploySettings?.views?.gantt) viewsAcceptable.push("gantt"); // if (settings?.views?.gantt) viewsAcceptable.push("gantt");
if (projectStore?.deploySettings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet"); // if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
if (board) { // if (board) {
if (viewsAcceptable.includes(board.toString())) { // if (viewsAcceptable.includes(board.toString())) {
currentBoard = board.toString() as TIssueBoardKeys; // currentBoard = board.toString() as TIssueBoardKeys;
} else { // } else {
if (viewsAcceptable && viewsAcceptable.length > 0) { // if (viewsAcceptable && viewsAcceptable.length > 0) {
currentBoard = viewsAcceptable[0] as TIssueBoardKeys; // currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
} // }
} // }
} else { // } else {
if (viewsAcceptable && viewsAcceptable.length > 0) { // if (viewsAcceptable && viewsAcceptable.length > 0) {
currentBoard = viewsAcceptable[0] as TIssueBoardKeys; // currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
} // }
} // }
if (currentBoard) { // if (currentBoard) {
if (projectStore?.activeBoard === null || projectStore?.activeBoard !== currentBoard) { // if (projectStore?.layout === null || projectStore?.activeBoard !== currentBoard) {
let params: any = { board: currentBoard }; // let params: any = { board: currentBoard };
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId }; // if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities }; // if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
if (states && states.length > 0) params = { ...params, states: states }; // if (states && states.length > 0) params = { ...params, states: states };
if (labels && labels.length > 0) params = { ...params, labels: labels }; // if (labels && labels.length > 0) params = { ...params, labels: labels };
let storeParams: any = {}; // let storeParams: any = {};
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") }; // if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") }; // if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") }; // if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
if (storeParams) updateFilters(project_slug, storeParams); // if (storeParams) updateFilters(project_slug, storeParams);
projectStore.setActiveBoard(currentBoard); // projectStore.setActiveBoard(currentBoard);
router.push({ // router.push({
pathname: `/${workspace_slug}/${project_slug}`, // pathname: `/${workspace_slug}/${project_slug}`,
query: { ...params }, // query: { ...params },
}); // });
} // }
} // }
} // }
}, [ // }, [
board, // board,
workspace_slug, // workspace_slug,
project_slug, // project_slug,
router, // router,
projectStore, // projectStore,
projectStore?.deploySettings, // projectStore?.deploySettings,
updateFilters, // updateFilters,
labels, // labels,
states, // states,
priorities, // priorities,
peekId, // peekId,
]); // ]);
return ( return (
<div className="relative flex w-full items-center gap-4 px-5"> <div className="relative flex w-full items-center gap-4 px-5">
{/* project detail */} {/* project detail */}
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
{projectStore.project ? ( {project_details ? (
<span className="h-7 w-7 flex-shrink-0 grid place-items-center"> <span className="h-7 w-7 flex-shrink-0 grid place-items-center">
<ProjectLogo logo={projectStore.project.logo_props} className="text-lg" /> <ProjectLogo logo={project_details.logo_props} className="text-lg" />
</span> </span>
) : ( ) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase"> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
@ -117,16 +110,13 @@ const IssueNavbar = observer(() => {
</span> </span>
)} )}
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium"> <div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
{projectStore?.project?.name || `...`} {project_details?.name || `...`}
</div> </div>
</div> </div>
{/* issue search bar */}
<div className="w-full">{/* <NavbarSearch /> */}</div>
{/* issue views */} {/* issue views */}
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out"> <div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
<NavbarIssueBoardView /> <NavbarIssueBoardView layouts={views} />
</div> </div>
{/* issue filters */} {/* issue filters */}
@ -139,7 +129,7 @@ const IssueNavbar = observer(() => {
<NavbarTheme /> <NavbarTheme />
</div> </div>
{user ? ( {user?.id ? (
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2"> <div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
<Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" /> <Avatar name={user?.display_name} src={user?.avatar ?? undefined} shape="square" size="sm" />
<h6 className="text-xs font-medium">{user.display_name}</h6> <h6 className="text-xs font-medium">{user.display_name}</h6>

View File

@ -1,47 +1,49 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// constants // constants
import { issueViews } from "@/constants/data"; import { issueViews } from "@/constants/data";
// hooks
import { useProject } from "@/hooks/store";
// mobx // mobx
import { useMobxStore } from "@/hooks/store"; import { TIssueBoardKeys } from "@/types/issue";
import { RootStore } from "@/store/root.store";
import { TIssueBoardKeys } from "types/issue";
export const NavbarIssueBoardView = observer(() => { type NavbarIssueBoardViewProps = {
const { layouts: Record<TIssueBoardKeys, boolean>;
project: { viewOptions, setActiveBoard, activeBoard }, };
}: RootStore = useMobxStore();
// router export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((props) => {
const router = useRouter(); const { layouts } = props;
const { workspace_slug, project_slug } = router.query as { workspace_slug: string; project_slug: string };
const { activeLayout, setActiveLayout } = useProject();
const handleCurrentBoardView = (boardView: string) => { const handleCurrentBoardView = (boardView: string) => {
setActiveBoard(boardView as TIssueBoardKeys); setActiveLayout(boardView as TIssueBoardKeys);
router.push(`/${workspace_slug}/${project_slug}?board=${boardView}`);
}; };
return ( return (
<> <>
{viewOptions && {layouts &&
Object.keys(viewOptions).map((viewKey: string) => { Object.keys(layouts).map((layoutKey: string) => {
if (viewOptions[viewKey]) { if (layouts[layoutKey as TIssueBoardKeys]) {
return ( return (
<div <div
key={viewKey} key={layoutKey}
className={`flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded-sm ${ className={`flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded-sm ${
viewKey === activeBoard layoutKey === activeLayout
? `bg-custom-background-80 text-custom-text-200` ? `bg-custom-background-80 text-custom-text-200`
: `text-custom-text-300 hover:bg-custom-background-80` : `text-custom-text-300 hover:bg-custom-background-80`
}`} }`}
onClick={() => handleCurrentBoardView(viewKey)} onClick={() => handleCurrentBoardView(layoutKey)}
title={viewKey} title={layoutKey}
> >
<span <span
className={`material-symbols-rounded text-[18px] ${ className={`material-symbols-rounded text-[18px] ${
issueViews[viewKey]?.className ? issueViews[viewKey]?.className : `` issueViews[layoutKey]?.className ? issueViews[layoutKey]?.className : ``
}`} }`}
> >
{issueViews[viewKey]?.icon} {issueViews[layoutKey]?.icon}
</span> </span>
</div> </div>
); );

View File

@ -1,3 +1,5 @@
"use client";
// next theme // next theme
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
@ -16,7 +18,6 @@ export const NavbarTheme = observer(() => {
useEffect(() => { useEffect(() => {
if (!theme) return; if (!theme) return;
setAppTheme(theme); setAppTheme(theme);
}, [theme]); }, [theme]);

View File

@ -1,6 +1,6 @@
import React, { useRef } from "react"; import React, { useRef } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
// components // components
import { EditorRefApi } from "@plane/lite-text-editor"; import { EditorRefApi } from "@plane/lite-text-editor";

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
// ui // ui
import { ReactionSelector } from "@/components/ui"; import { ReactionSelector } from "@/components/ui";

View File

@ -13,7 +13,7 @@ import { IIssue } from "@/types/issue";
type Props = { type Props = {
handleClose: () => void; handleClose: () => void;
issueDetails: IIssue | undefined; issueDetails: IIssue | undefined;
workspace_slug: string; workspaceSlug: string;
}; };
export const FullScreenPeekView: React.FC<Props> = observer((props) => { export const FullScreenPeekView: React.FC<Props> = observer((props) => {

View File

@ -9,7 +9,7 @@ import { Icon } from "@/components/ui";
import { copyTextToClipboard } from "@/helpers/string.helper"; import { copyTextToClipboard } from "@/helpers/string.helper";
// store // store
import { useMobxStore } from "@/hooks/store"; import { useMobxStore } from "@/hooks/store";
import { IPeekMode } from "@/store/issue_details"; import { IPeekMode } from "@/store/issue-detail.store";
import { RootStore } from "@/store/root.store"; import { RootStore } from "@/store/root.store";
// lib // lib
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/navigation";
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
// components // components
import { CommentCard, AddComment } from "@/components/issues/peek-overview"; import { CommentCard, AddComment } from "@/components/issues/peek-overview";

View File

@ -1,20 +1,22 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// lib // lib
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
import { ReactionSelector } from "@/components/ui"; import { ReactionSelector } from "@/components/ui";
// helpers // helpers
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper"; import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
// hooks // hooks
import { useMobxStore, useUser } from "@/hooks/store"; import { useIssueDetails, useUser } from "@/hooks/store";
export const IssueEmojiReactions: React.FC = observer(() => { type IssueEmojiReactionsProps = {
// router workspaceSlug: string;
const router = useRouter(); projectId: string;
const { workspace_slug, project_slug } = router.query; };
export const IssueEmojiReactions: React.FC<IssueEmojiReactionsProps> = observer((props) => {
const { workspaceSlug, projectId } = props;
// store // store
const { issueDetails: issueDetailsStore } = useMobxStore(); const issueDetailsStore = useIssueDetails();
const { data: user, fetchCurrentUser } = useUser(); const { data: user, fetchCurrentUser } = useUser();
const issueId = issueDetailsStore.peekId; const issueId = issueDetailsStore.peekId;
@ -24,20 +26,17 @@ export const IssueEmojiReactions: React.FC = observer(() => {
const userReactions = reactions?.filter((r) => r.actor_detail.id === user?.id); const userReactions = reactions?.filter((r) => r.actor_detail.id === user?.id);
const handleAddReaction = (reactionHex: string) => { const handleAddReaction = (reactionHex: string) => {
if (!workspace_slug || !project_slug || !issueId) return; if (!workspaceSlug || !projectId || !issueId) return;
issueDetailsStore.addIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex);
issueDetailsStore.addIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex);
}; };
const handleRemoveReaction = (reactionHex: string) => { const handleRemoveReaction = (reactionHex: string) => {
if (!workspace_slug || !project_slug || !issueId) return; if (!workspaceSlug || !projectId || !issueId) return;
issueDetailsStore.removeIssueReaction(workspaceSlug.toString(), projectId.toString(), issueId, reactionHex);
issueDetailsStore.removeIssueReaction(workspace_slug.toString(), project_slug.toString(), issueId, reactionHex);
}; };
const handleReactionClick = (reactionHex: string) => { const handleReactionClick = (reactionHex: string) => {
const userReaction = userReactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex); const userReaction = userReactions?.find((r) => r.actor_detail.id === user?.id && r.reaction === reactionHex);
if (userReaction) handleRemoveReaction(reactionHex); if (userReaction) handleRemoveReaction(reactionHex);
else handleAddReaction(reactionHex); else handleAddReaction(reactionHex);
}; };

View File

@ -8,7 +8,7 @@ import { issueGroupFilter, issuePriorityFilter } from "@/constants/data";
import { renderFullDate } from "@/helpers/date-time.helper"; import { renderFullDate } from "@/helpers/date-time.helper";
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper"; import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
// types // types
import { IPeekMode } from "@/store/issue_details"; import { IPeekMode } from "@/store/issue-detail.store";
// constants // constants
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import { IIssue } from "types/issue"; import { IIssue } from "types/issue";

View File

@ -1,12 +1,18 @@
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview"; import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
import { useMobxStore } from "@/hooks/store"; import { useProject } from "@/hooks/store";
export const IssueReactions: React.FC = () => { type IssueReactionsProps = {
const { project: projectStore } = useMobxStore(); workspaceSlug: string;
projectId: string;
};
export const IssueReactions: React.FC<IssueReactionsProps> = (props) => {
const { workspaceSlug, projectId } = props;
const { canVote, canReact } = useProject();
return ( return (
<div className="mt-4 flex items-center gap-3"> <div className="mt-4 flex items-center gap-3">
{projectStore?.deploySettings?.votes && ( {canVote && (
<> <>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<IssueVotes /> <IssueVotes />
@ -14,9 +20,9 @@ export const IssueReactions: React.FC = () => {
<div className="h-8 w-0.5 bg-custom-background-200" /> <div className="h-8 w-0.5 bg-custom-background-200" />
</> </>
)} )}
{projectStore?.deploySettings?.reactions && ( {canReact && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<IssueEmojiReactions /> <IssueEmojiReactions workspaceSlug={workspaceSlug} projectId={projectId} />
</div> </div>
)} )}
</div> </div>

View File

@ -1,18 +1,17 @@
"use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
// hooks // hooks
import { useMobxStore, useUser } from "@/hooks/store"; import { useIssueDetails, useUser } from "@/hooks/store";
export const IssueVotes: React.FC = observer(() => { export const IssueVotes: React.FC = observer((props: any) => {
const { workspaceSlug, projectId } = props;
// states
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter(); const issueDetailsStore = useIssueDetails();
const { workspace_slug, project_slug } = router.query;
const { issueDetails: issueDetailsStore } = useMobxStore();
const { data: user, fetchCurrentUser } = useUser(); const { data: user, fetchCurrentUser } = useUser();
const issueId = issueDetailsStore.peekId; const issueId = issueDetailsStore.peekId;
@ -26,16 +25,16 @@ export const IssueVotes: React.FC = observer(() => {
const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id); const isDownVotedByUser = allDownVotes?.some((vote) => vote.actor === user?.id);
const handleVote = async (e: any, voteValue: 1 | -1) => { const handleVote = async (e: any, voteValue: 1 | -1) => {
if (!workspace_slug || !project_slug || !issueId) return; if (!workspaceSlug || !projectId || !issueId) return;
setIsSubmitting(true); setIsSubmitting(true);
const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue); const actionPerformed = votes?.find((vote) => vote.actor === user?.id && vote.vote === voteValue);
if (actionPerformed) if (actionPerformed)
await issueDetailsStore.removeIssueVote(workspace_slug.toString(), project_slug.toString(), issueId); await issueDetailsStore.removeIssueVote(workspaceSlug.toString(), projectId.toString(), issueId);
else else
await issueDetailsStore.addIssueVote(workspace_slug.toString(), project_slug.toString(), issueId, { await issueDetailsStore.addIssueVote(workspaceSlug.toString(), projectId.toString(), issueId, {
vote: voteValue, vote: voteValue,
}); });

View File

@ -1,42 +1,32 @@
"use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// components // components
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview"; import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
// lib // store
import { useMobxStore } from "@/hooks/store"; import { useIssue, useIssueDetails } from "@/hooks/store";
export const IssuePeekOverview: React.FC = observer(() => { export const IssuePeekOverview: React.FC = observer((props: any) => {
const { workspaceSlug, projectId, peekId, board, priorities, states, labels } = props;
// states // states
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false); const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
const [isModalPeekOpen, setIsModalPeekOpen] = useState(false); const [isModalPeekOpen, setIsModalPeekOpen] = useState(false);
// router
const router = useRouter();
const { workspace_slug, project_slug, peekId, board, priorities, states, labels } = router.query as {
workspace_slug: string;
project_slug: string;
peekId: string;
board: string;
priorities: string;
states: string;
labels: string;
};
// store // store
const { issueDetails: issueDetailStore, issue: issueStore } = useMobxStore(); const issueDetailStore = useIssueDetails();
const issueStore = useIssue();
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined; const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
useEffect(() => { useEffect(() => {
if (workspace_slug && project_slug && peekId && issueStore.issues && issueStore.issues.length > 0) { if (workspaceSlug && projectId && peekId && issueStore.issues && issueStore.issues.length > 0) {
if (!issueDetails) { if (!issueDetails) {
issueDetailStore.fetchIssueDetails(workspace_slug.toString(), project_slug.toString(), peekId.toString()); issueDetailStore.fetchIssueDetails(workspaceSlug.toString(), projectId.toString(), peekId.toString());
} }
} }
}, [workspace_slug, project_slug, issueDetailStore, issueDetails, peekId, issueStore.issues]); }, [workspaceSlug, projectId, issueDetailStore, issueDetails, peekId, issueStore.issues]);
const handleClose = () => { const handleClose = () => {
issueDetailStore.setPeekId(null); issueDetailStore.setPeekId(null);
@ -45,10 +35,8 @@ export const IssuePeekOverview: React.FC = observer(() => {
if (states && states.length > 0) params.states = states; if (states && states.length > 0) params.states = states;
if (priorities && priorities.length > 0) params.priorities = priorities; if (priorities && priorities.length > 0) params.priorities = priorities;
if (labels && labels.length > 0) params.labels = labels; if (labels && labels.length > 0) params.labels = labels;
// TODO: fix this redirection
router.replace({ pathname: `/${workspace_slug?.toString()}/${project_slug}`, query: { ...params } }, undefined, { // router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } });
shallow: true,
});
}; };
useEffect(() => { useEffect(() => {
@ -118,7 +106,7 @@ export const IssuePeekOverview: React.FC = observer(() => {
)} )}
{issueDetailStore.peekMode === "full" && ( {issueDetailStore.peekMode === "full" && (
<FullScreenPeekView <FullScreenPeekView
workspace_slug={workspace_slug} workspaceSlug={workspaceSlug}
handleClose={handleClose} handleClose={handleClose}
issueDetails={issueDetails} issueDetails={issueDetails}
/> />

View File

@ -7,7 +7,7 @@ import { useTheme } from "next-themes";
import useSWR from "swr"; import useSWR from "swr";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
// components // components
import { AuthRoot, UserLoggedIn } from "@/components/accounts"; import { AuthRoot } from "@/components/accounts";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser } from "@/hooks/store";
// images // images
@ -19,12 +19,15 @@ export const AuthView = observer(() => {
// hooks // hooks
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
// store // store
const { fetchCurrentUser, isLoading, isAuthenticated } = useUser(); const { fetchCurrentUser, isLoading } = useUser();
// fetching user information // fetching user information
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), { const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
shouldRetryOnError: false, shouldRetryOnError: false,
revalidateOnFocus: false, revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: true,
errorRetryCount: 1,
}); });
return ( return (
@ -35,30 +38,26 @@ export const AuthView = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{isAuthenticated ? ( <div className="relative w-screen h-screen overflow-hidden">
<UserLoggedIn /> <div className="absolute inset-0 z-0">
) : ( <Image
<div className="relative w-screen h-screen overflow-hidden"> src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
<div className="absolute inset-0 z-0"> className="w-full h-full object-cover"
<Image alt="Plane background pattern"
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern} />
className="w-full h-full object-cover" </div>
alt="Plane background pattern" <div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
/> <div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div> </div>
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col"> <div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all"> <AuthRoot />
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<AuthRoot />
</div>
</div> </div>
</div> </div>
)} </div>
</> </>
)} )}
</> </>

View File

@ -1,7 +1,9 @@
import { useEffect } from "react"; "use client";
import { FC, useEffect } from "react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import useSWR from "swr";
// components // components
import { IssueCalendarView } from "@/components/issues/board-views/calendar"; import { IssueCalendarView } from "@/components/issues/board-views/calendar";
import { IssueGanttView } from "@/components/issues/board-views/gantt"; import { IssueGanttView } from "@/components/issues/board-views/gantt";
@ -11,16 +13,30 @@ import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadshee
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root"; import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
import { IssuePeekOverview } from "@/components/issues/peek-overview"; import { IssuePeekOverview } from "@/components/issues/peek-overview";
// mobx store // mobx store
import { useMobxStore, useUser } from "@/hooks/store"; import { useIssue, useUser, useProject, useIssueDetails } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
// assets // assets
import SomethingWentWrongImage from "public/something-went-wrong.svg"; import SomethingWentWrongImage from "public/something-went-wrong.svg";
export const ProjectDetailsView = observer(() => { type ProjectDetailsViewProps = {
const router = useRouter(); workspaceSlug: string;
const { workspace_slug, project_slug, states, labels, priorities, peekId } = router.query; projectId: string;
params: any;
};
const { issue: issueStore, project: projectStore, issueDetails: issueDetailStore }: RootStore = useMobxStore(); export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
const { workspaceSlug, projectId, params } = props;
const { states, labels, priorities, peekId } = params;
// store hooks
const { fetchPublicIssues } = useIssue();
const { activeLayout } = useProject();
// fetching public issues
useSWR(
workspaceSlug && projectId ? "PROJECT_PUBLIC_ISSUES" : null,
workspaceSlug && projectId ? () => fetchPublicIssues(workspaceSlug, projectId, params) : null
);
const issueStore = useIssue();
const issueDetailStore = useIssueDetails();
const { data: currentUser, fetchCurrentUser } = useUser(); const { data: currentUser, fetchCurrentUser } = useUser();
useEffect(() => { useEffect(() => {
@ -30,25 +46,14 @@ export const ProjectDetailsView = observer(() => {
}, [currentUser, fetchCurrentUser]); }, [currentUser, fetchCurrentUser]);
useEffect(() => { useEffect(() => {
if (workspace_slug && project_slug) { if (peekId && workspaceSlug && projectId) {
const params = {
state: states || null,
labels: labels || null,
priority: priorities || null,
};
issueStore.fetchPublicIssues(workspace_slug?.toString(), project_slug.toString(), params);
}
}, [workspace_slug, project_slug, issueStore, states, labels, priorities]);
useEffect(() => {
if (peekId && workspace_slug && project_slug) {
issueDetailStore.setPeekId(peekId.toString()); issueDetailStore.setPeekId(peekId.toString());
} }
}, [peekId, issueDetailStore, project_slug, workspace_slug]); }, [peekId, issueDetailStore, projectId, workspaceSlug]);
return ( return (
<div className="relative h-full w-full overflow-hidden"> <div className="relative h-full w-full overflow-hidden">
{workspace_slug && <IssuePeekOverview />} {workspaceSlug && <IssuePeekOverview />}
{issueStore?.loader && !issueStore.issues ? ( {issueStore?.loader && !issueStore.issues ? (
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div> <div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
@ -67,24 +72,24 @@ export const ProjectDetailsView = observer(() => {
</div> </div>
</div> </div>
) : ( ) : (
projectStore?.activeBoard && ( activeLayout && (
<div className="relative flex h-full w-full flex-col overflow-hidden"> <div className="relative flex h-full w-full flex-col overflow-hidden">
{/* applied filters */} {/* applied filters */}
<IssueAppliedFilters /> <IssueAppliedFilters />
{projectStore?.activeBoard === "list" && ( {activeLayout === "list" && (
<div className="relative h-full w-full overflow-y-auto"> <div className="relative h-full w-full overflow-y-auto">
<IssueListView /> <IssueListView />
</div> </div>
)} )}
{projectStore?.activeBoard === "kanban" && ( {activeLayout === "kanban" && (
<div className="relative mx-auto h-full w-full p-5"> <div className="relative mx-auto h-full w-full p-5">
<IssueKanbanView /> <IssueKanbanView />
</div> </div>
)} )}
{projectStore?.activeBoard === "calendar" && <IssueCalendarView />} {activeLayout === "calendar" && <IssueCalendarView />}
{projectStore?.activeBoard === "spreadsheet" && <IssueSpreadsheetView />} {activeLayout === "spreadsheet" && <IssueSpreadsheetView />}
{projectStore?.activeBoard === "gantt" && <IssueGanttView />} {activeLayout === "gantt" && <IssueGanttView />}
</div> </div>
) )
)} )}

20
space/constants/issue.ts Normal file
View File

@ -0,0 +1,20 @@
import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters";
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
[pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions };
} = {
issues: {
list: {
filters: ["priority", "state", "labels"],
display_properties: null,
display_filters: null,
extra_options: null,
},
kanban: {
filters: ["priority", "state", "labels"],
display_properties: null,
display_filters: null,
extra_options: null,
},
},
};

View File

@ -1,2 +1,7 @@
export * from "./use-instance"; export * from "./use-instance";
export * from "./user"; export * from "./use-project";
export * from "./use-issue";
export * from "./use-user";
export * from "./use-user-profile";
export * from "./use-issue-details";
export * from "./use-issue-filter";

View File

@ -1,10 +1,11 @@
import { useContext } from "react"; import { useContext } from "react";
// store // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/app-providers";
// store
import { IInstanceStore } from "@/store/instance.store"; import { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => { export const useInstance = (): IInstanceStore => {
const context = useContext(StoreContext); const context = useContext(StoreContext);
if (context === undefined) throw new Error("useInstance must be used within StoreProvider"); if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.instance; return context.instance;
}; };

View File

@ -0,0 +1,11 @@
import { useContext } from "react";
// lib
import { StoreContext } from "@/lib/app-providers";
// store
import { IIssueDetailStore } from "@/store/issue-detail.store";
export const useIssueDetails = (): IIssueDetailStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.issueDetail;
};

View File

@ -0,0 +1,11 @@
import { useContext } from "react";
// lib
import { StoreContext } from "@/lib/app-providers";
// store
import { IIssueFilterStore } from "@/store/issue-filters.store";
export const useIssueFilter = (): IIssueFilterStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.issueFilter;
};

View File

@ -0,0 +1,11 @@
import { useContext } from "react";
// lib
import { StoreContext } from "@/lib/app-providers";
// store
import { IIssueStore } from "@/store/issue.store";
export const useIssue = (): IIssueStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.issue;
};

View File

@ -0,0 +1,11 @@
import { useContext } from "react";
// lib
import { StoreContext } from "@/lib/app-providers";
// store
import { IProjectStore } from "@/store/project.store";
export const useProject = (): IProjectStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.project;
};

View File

@ -1,7 +1,8 @@
import { useContext } from "react"; import { useContext } from "react";
// store // lib
import { StoreContext } from "@/lib/app-providers"; import { StoreContext } from "@/lib/app-providers";
import { IProfileStore } from "@/store/user/profile.store"; // store
import { IProfileStore } from "@/store/profile.store";
export const useUserProfile = (): IProfileStore => { export const useUserProfile = (): IProfileStore => {
const context = useContext(StoreContext); const context = useContext(StoreContext);

View File

@ -1,2 +0,0 @@
export * from "./use-user";
export * from "./use-user-profile";

View File

@ -1,32 +0,0 @@
import Image from "next/image";
// mobx
import { observer } from "mobx-react-lite";
import planeLogo from "public/plane-logo.svg";
// components
import IssueNavbar from "@/components/issues/navbar";
const ProjectLayout = ({ children }: { children: React.ReactNode }) => (
<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 />
</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<a
href="https://plane.so"
className="fixed bottom-2.5 right-5 !z-[999999] flex items-center gap-1 rounded border border-custom-border-200 bg-custom-background-100 px-2 py-1 shadow-custom-shadow-2xs"
target="_blank"
rel="noreferrer noopener"
>
<div className="relative grid h-6 w-6 place-items-center">
<Image src={planeLogo} alt="Plane logo" className="h-6 w-6" height="24" width="24" />
</div>
<div className="text-xs">
Powered by <span className="font-semibold">Plane Deploy</span>
</div>
</a>
</div>
);
export default observer(ProjectLayout);

View File

@ -1,6 +1,6 @@
import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
import APIService from "@/services/api.service"; import APIService from "@/services/api.service";
import { API_BASE_URL } from "@/helpers/common.helper";
class IssueService extends APIService { class IssueService extends APIService {
constructor() { constructor() {

View File

@ -1,6 +1,6 @@
import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
import APIService from "@/services/api.service"; import APIService from "@/services/api.service";
import { API_BASE_URL } from "@/helpers/common.helper";
class ProjectService extends APIService { class ProjectService extends APIService {
constructor() { constructor() {

View File

@ -19,7 +19,7 @@ export interface IInstanceStore {
// issues // issues
isLoading: boolean; isLoading: boolean;
data: IInstance | NonNullable<unknown>; data: IInstance | NonNullable<unknown>;
config: Record<string, unknown>; config: Record<string, any>;
error: TError | undefined; error: TError | undefined;
// action // action
fetchInstanceInfo: () => Promise<void>; fetchInstanceInfo: () => Promise<void>;
@ -28,7 +28,7 @@ export interface IInstanceStore {
export class InstanceStore implements IInstanceStore { export class InstanceStore implements IInstanceStore {
isLoading: boolean = true; isLoading: boolean = true;
data: IInstance | Record<string, unknown> = {}; data: IInstance | Record<string, any> = {};
config: Record<string, unknown> = {}; config: Record<string, unknown> = {};
error: TError | undefined = undefined; error: TError | undefined = undefined;
// services // services

View File

@ -55,7 +55,7 @@ export interface IIssueDetailStore {
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>; removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
} }
class IssueDetailStore implements IIssueDetailStore { export class IssueDetailStore implements IIssueDetailStore {
loader: boolean = false; loader: boolean = false;
error: any = null; error: any = null;
peekId: string | null = null; peekId: string | null = null;
@ -431,5 +431,3 @@ class IssueDetailStore implements IIssueDetailStore {
} }
}; };
} }
export default IssueDetailStore;

View File

@ -1,15 +1,16 @@
import { action, makeObservable, observable, runInAction, computed } from "mobx"; import { action, makeObservable, observable, runInAction, computed } from "mobx";
// types // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// store
import { RootStore } from "@/store/root.store"; import { RootStore } from "@/store/root.store";
import { IIssueFilterOptions, TIssueParams } from "./types"; // types
import { handleIssueQueryParamsByLayout } from "./helpers"; import { TIssueBoardKeys, IIssueFilterOptions, TIssueParams } from "@/types/issue";
import { IssueFilterBaseStore } from "./base-issue-filter.store";
interface IFiltersOptions { interface IFiltersOptions {
filters: IIssueFilterOptions; filters: IIssueFilterOptions;
} }
export interface IIssuesFilterStore { export interface IIssueFilterStore {
// observables // observables
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined; projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined;
// computed // computed
@ -21,15 +22,13 @@ export interface IIssuesFilterStore {
updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise<IFiltersOptions>; updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise<IFiltersOptions>;
} }
export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFilterStore { export class IssueFilterStore implements IIssueFilterStore {
// observables // observables
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined; projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined;
// root store // root store
rootStore; rootStore;
constructor(_rootStore: RootStore) { constructor(_rootStore: RootStore) {
super(_rootStore);
makeObservable(this, { makeObservable(this, {
// observables // observables
projectIssueFilters: observable.ref, projectIssueFilters: observable.ref,
@ -43,35 +42,61 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
this.rootStore = _rootStore; this.rootStore = _rootStore;
} }
// helper methods
computedFilter = (filters: any, filteredParams: any) => {
const computedFilters: any = {};
Object.keys(filters).map((key) => {
if (filters[key] != undefined && filteredParams.includes(key))
computedFilters[key] =
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
});
return computedFilters;
};
// helpers // helpers
issueDisplayFilters = (projectId: string) => { issueDisplayFilters = (projectId: string) => {
if (!projectId) return undefined; if (!projectId) return undefined;
return this.projectIssueFilters?.[projectId] || undefined; return this.projectIssueFilters?.[projectId] || undefined;
}; };
// actions handleIssueQueryParamsByLayout = (layout: TIssueBoardKeys | undefined, viewType: "issues"): TIssueParams[] | null => {
const queryParams: TIssueParams[] = [];
if (!layout) return null;
const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout];
// add filters query params
layoutOptions.filters.forEach((option: any) => {
queryParams.push(option);
});
return queryParams;
};
// actions
updateFilters = async (projectId: string, filters: IIssueFilterOptions) => { updateFilters = async (projectId: string, filters: IIssueFilterOptions) => {
try { try {
let _projectIssueFilters = { ...this.projectIssueFilters }; let issueFilters = { ...this.projectIssueFilters };
if (!_projectIssueFilters) _projectIssueFilters = {}; if (!issueFilters) issueFilters = {};
if (!_projectIssueFilters[projectId]) _projectIssueFilters[projectId] = { filters: {} }; if (!issueFilters[projectId]) issueFilters[projectId] = { filters: {} };
const _filters = { const newFilters = {
filters: { ..._projectIssueFilters[projectId].filters }, filters: { ...issueFilters[projectId].filters },
}; };
_filters.filters = { ..._filters.filters, ...filters }; newFilters.filters = { ...newFilters.filters, ...filters };
_projectIssueFilters[projectId] = { issueFilters[projectId] = {
filters: _filters.filters, filters: newFilters.filters,
}; };
runInAction(() => { runInAction(() => {
this.projectIssueFilters = _projectIssueFilters; this.projectIssueFilters = issueFilters;
}); });
return _filters; return newFilters;
} catch (error) { } catch (error) {
throw error; throw error;
} }
@ -89,7 +114,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
get appliedFilters() { get appliedFilters() {
const userFilters = this.issueFilters; const userFilters = this.issueFilters;
const layout = this.rootStore.project?.activeBoard; const layout = this.rootStore.project?.activeLayout;
if (!userFilters || !layout) return undefined; if (!userFilters || !layout) return undefined;
let filteredRouteParams: any = { let filteredRouteParams: any = {
@ -98,7 +123,7 @@ export class IssuesFilterStore extends IssueFilterBaseStore implements IIssuesFi
labels: userFilters?.filters?.labels || undefined, labels: userFilters?.filters?.labels || undefined,
}; };
const filteredParams = handleIssueQueryParamsByLayout(layout, "issues"); const filteredParams = this.handleIssueQueryParamsByLayout(layout, "issues");
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
return filteredRouteParams; return filteredRouteParams;

View File

@ -1,11 +1,11 @@
import { observable, action, computed, makeObservable, runInAction } from "mobx"; import { observable, action, makeObservable, runInAction } from "mobx";
// services // services
import IssueService from "@/services/issue.service"; import IssueService from "@/services/issue.service";
// types
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
// store // store
import { RootStore } from "./root.store"; import { RootStore } from "./root.store";
// types
// import { IssueDetailType, TIssueBoardKeys } from "types/issue"; // import { IssueDetailType, TIssueBoardKeys } from "types/issue";
import { IIssue, IIssueState, IIssueLabel } from "types/issue";
export interface IIssueStore { export interface IIssueStore {
loader: boolean; loader: boolean;
@ -26,7 +26,7 @@ export interface IIssueStore {
getFilteredIssuesByState: (state: string) => IIssue[]; getFilteredIssuesByState: (state: string) => IIssue[];
} }
class IssueStore implements IIssueStore { export class IssueStore implements IIssueStore {
loader: boolean = false; loader: boolean = false;
error: any | null = null; error: any | null = null;
@ -75,13 +75,13 @@ class IssueStore implements IIssueStore {
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params); const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params);
if (response) { if (response) {
const _states: IIssueState[] = [...response?.states]; const states: IIssueState[] = [...response?.states];
const _labels: IIssueLabel[] = [...response?.labels]; const labels: IIssueLabel[] = [...response?.labels];
const _issues: IIssue[] = [...response?.issues]; const issues: IIssue[] = [...response?.issues];
runInAction(() => { runInAction(() => {
this.states = _states; this.states = states;
this.labels = _labels; this.labels = labels;
this.issues = _issues; this.issues = issues;
this.loader = false; this.loader = false;
}); });
} }
@ -99,5 +99,3 @@ class IssueStore implements IIssueStore {
getFilteredIssuesByState = (state_id: string): IIssue[] | [] => getFilteredIssuesByState = (state_id: string): IIssue[] | [] =>
this.issues?.filter((issue) => issue.state == state_id) || []; this.issues?.filter((issue) => issue.state == state_id) || [];
} }
export default IssueStore;

View File

@ -1,29 +0,0 @@
// types
import { RootStore } from "@/store/root.store";
export interface IIssueFilterBaseStore {
// helper methods
computedFilter(filters: any, filteredParams: any): any;
}
export class IssueFilterBaseStore implements IIssueFilterBaseStore {
// root store
rootStore;
constructor(_rootStore: RootStore) {
// root store
this.rootStore = _rootStore;
}
// helper methods
computedFilter = (filters: any, filteredParams: any) => {
const computedFilters: any = {};
Object.keys(filters).map((key) => {
if (filters[key] != undefined && filteredParams.includes(key))
computedFilters[key] =
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
});
return computedFilters;
};
}

View File

@ -1,52 +0,0 @@
import { TIssueBoardKeys } from "types/issue";
import { IIssueFilterOptions, TIssueParams } from "./types";
export const isNil = (value: any) => {
if (value === undefined || value === null) return true;
return false;
};
export interface ILayoutDisplayFiltersOptions {
filters: (keyof IIssueFilterOptions)[];
display_properties: boolean | null;
display_filters: null;
extra_options: null;
}
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
[pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions };
} = {
issues: {
list: {
filters: ["priority", "state", "labels"],
display_properties: null,
display_filters: null,
extra_options: null,
},
kanban: {
filters: ["priority", "state", "labels"],
display_properties: null,
display_filters: null,
extra_options: null,
},
},
};
export const handleIssueQueryParamsByLayout = (
layout: TIssueBoardKeys | undefined,
viewType: "issues"
): TIssueParams[] | null => {
const queryParams: TIssueParams[] = [];
if (!layout) return null;
const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout];
// add filters query params
layoutOptions.filters.forEach((option) => {
queryParams.push(option);
});
return queryParams;
};

View File

@ -1,36 +0,0 @@
import { IIssue } from "types/issue";
export type TIssueGroupByOptions = "state" | "priority" | "labels" | null;
export type TIssueParams = "priority" | "state" | "labels";
export interface IIssueFilterOptions {
state?: string[] | null;
labels?: string[] | null;
priority?: string[] | null;
}
// issues
export interface IGroupedIssues {
[group_id: string]: string[];
}
export interface ISubGroupedIssues {
[sub_grouped_id: string]: {
[group_id: string]: string[];
};
}
export type TUnGroupedIssues = string[];
export interface IIssueResponse {
[issue_id: string]: IIssue;
}
export type TLoader = "init-loader" | "mutation" | undefined;
export interface ViewFlags {
enableQuickAdd: boolean;
enableIssueCreation: boolean;
enableInlineEditing: boolean;
}

View File

@ -11,11 +11,15 @@ export interface IProjectStore {
error: any | null; error: any | null;
workspace: IWorkspace | null; workspace: IWorkspace | null;
project: IProject | null; project: IProject | null;
deploySettings: IProjectSettings | null; settings: IProjectSettings | null;
viewOptions: any; activeLayout: TIssueBoardKeys;
activeBoard: TIssueBoardKeys | null; layoutOptions: Record<TIssueBoardKeys, boolean>;
canReact: boolean;
canComment: boolean;
canVote: boolean;
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>; fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
setActiveBoard: (value: TIssueBoardKeys) => void; setActiveLayout: (value: TIssueBoardKeys) => void;
hydrate: (projectSettings: any) => void;
} }
export class ProjectStore implements IProjectStore { export class ProjectStore implements IProjectStore {
@ -24,9 +28,18 @@ export class ProjectStore implements IProjectStore {
// data // data
workspace: IWorkspace | null = null; workspace: IWorkspace | null = null;
project: IProject | null = null; project: IProject | null = null;
deploySettings: IProjectSettings | null = null; settings: IProjectSettings | null = null;
viewOptions: any = null; activeLayout: TIssueBoardKeys = "list";
activeBoard: TIssueBoardKeys | null = null; layoutOptions: Record<TIssueBoardKeys, boolean> = {
list: true,
kanban: true,
calendar: false,
gantt: false,
spreadsheet: false,
};
canReact: boolean = false;
canComment: boolean = false;
canVote: boolean = false;
// root store // root store
rootStore; rootStore;
// service // service
@ -38,14 +51,18 @@ export class ProjectStore implements IProjectStore {
loader: observable, loader: observable,
error: observable.ref, error: observable.ref,
// observable // observable
workspace: observable.ref, workspace: observable,
project: observable.ref, project: observable,
deploySettings: observable.ref, settings: observable,
viewOptions: observable.ref, layoutOptions: observable,
activeBoard: observable.ref, activeLayout: observable.ref,
canReact: observable.ref,
canComment: observable.ref,
canVote: observable.ref,
// actions // actions
fetchProjectSettings: action, fetchProjectSettings: action,
setActiveBoard: action, setActiveLayout: action,
hydrate: action,
// computed // computed
}); });
@ -53,6 +70,20 @@ export class ProjectStore implements IProjectStore {
this.projectService = new ProjectService(); this.projectService = new ProjectService();
} }
hydrate = (projectSettings: any) => {
const { workspace_detail, project_details, views, votes, comments, reactions } = projectSettings;
this.workspace = workspace_detail;
this.project = project_details;
this.layoutOptions = views;
this.canComment = comments;
this.canVote = votes;
this.canReact = reactions;
};
setActiveLayout = (boardValue: TIssueBoardKeys) => {
this.activeLayout = boardValue;
};
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => { fetchProjectSettings = async (workspace_slug: string, project_slug: string) => {
try { try {
this.loader = true; this.loader = true;
@ -68,8 +99,8 @@ export class ProjectStore implements IProjectStore {
runInAction(() => { runInAction(() => {
this.project = currentProject; this.project = currentProject;
this.workspace = currentWorkspace; this.workspace = currentWorkspace;
this.viewOptions = currentViewOptions; this.layoutOptions = currentViewOptions;
this.deploySettings = currentDeploySettings; this.settings = currentDeploySettings;
this.loader = false; this.loader = false;
}); });
} }
@ -80,8 +111,4 @@ export class ProjectStore implements IProjectStore {
return error; return error;
} }
}; };
setActiveBoard = (boardValue: TIssueBoardKeys) => {
this.activeBoard = boardValue;
};
} }

View File

@ -1,42 +1,32 @@
import { enableStaticRendering } from "mobx-react-lite"; import { enableStaticRendering } from "mobx-react-lite";
// store imports // store imports
import { IInstanceStore, InstanceStore } from "@/store/instance.store"; import { IInstanceStore, InstanceStore } from "@/store/instance.store";
import { IssueDetailStore, IIssueDetailStore } from "@/store/issue-detail.store";
import { IssueStore, IIssueStore } from "@/store/issue.store";
import { IProjectStore, ProjectStore } from "@/store/project.store";
import { IUserStore, UserStore } from "@/store/user.store"; import { IUserStore, UserStore } from "@/store/user.store";
import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store";
// import { IProjectStore, ProjectStore } from "@/store/project"; import { IMentionsStore, MentionsStore } from "./mentions.store";
// import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
// import IssueStore, { IIssueStore } from "./issue";
// import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
// import { IIssuesFilterStore, IssuesFilterStore } from "./issues/issue-filters.store";
// import { IMentionsStore, MentionsStore } from "./mentions.store";
enableStaticRendering(typeof window === "undefined"); enableStaticRendering(typeof window === "undefined");
export class RootStore { export class RootStore {
instance: IInstanceStore; instance: IInstanceStore;
user: IUserStore; user: IUserStore;
// profile: IProfileStore; project: IProjectStore;
// project: IProjectStore; issue: IIssueStore;
issueDetail: IIssueDetailStore;
// issue: IIssueStore; mentionStore: IMentionsStore;
// issueDetails: IIssueDetailStore; issueFilter: IIssueFilterStore;
// mentionsStore: IMentionsStore;
// issuesFilter: IIssuesFilterStore;
constructor() { constructor() {
// makeObservable(this, {
// instance: observable,
// });
this.instance = new InstanceStore(this); this.instance = new InstanceStore(this);
this.user = new UserStore(this); this.user = new UserStore(this);
// this.profile = new ProfileStore(this); this.project = new ProjectStore(this);
// this.project = new ProjectStore(this); this.issue = new IssueStore(this);
this.issueDetail = new IssueDetailStore(this);
// this.issue = new IssueStore(this); this.mentionStore = new MentionsStore(this);
// this.issueDetails = new IssueDetailStore(this); this.issueFilter = new IssueFilterStore(this);
// this.mentionsStore = new MentionsStore(this);
// this.issuesFilter = new IssuesFilterStore(this);
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -50,11 +40,10 @@ export class RootStore {
localStorage.setItem("theme", "system"); localStorage.setItem("theme", "system");
this.instance = new InstanceStore(this); this.instance = new InstanceStore(this);
this.user = new UserStore(this); this.user = new UserStore(this);
// this.profile = new ProfileStore(this); this.project = new ProjectStore(this);
// this.project = new ProjectStore(this); this.issue = new IssueStore(this);
// this.issue = new IssueStore(this); this.issueDetail = new IssueDetailStore(this);
// this.issueDetails = new IssueDetailStore(this); this.mentionStore = new MentionsStore(this);
// this.mentionsStore = new MentionsStore(this); this.issueFilter = new IssueFilterStore(this);
// this.issuesFilter = new IssuesFilterStore(this);
}; };
} }

View File

@ -3,11 +3,11 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"
// types // types
import { IUser } from "@plane/types"; import { IUser } from "@plane/types";
// services // services
import { AuthService } from "@/services/authentication.service"; import { AuthService } from "@/services/auth.service";
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
// stores // stores
import { RootStore } from "@/store/root.store"; import { RootStore } from "@/store/root.store";
import { ProfileStore, IProfileStore } from "@/store/user/profile.store"; import { ProfileStore, IProfileStore } from "@/store/profile.store";
import { ActorDetail } from "@/types/issue"; import { ActorDetail } from "@/types/issue";
type TUserErrorStatus = { type TUserErrorStatus = {

View File

@ -1,178 +0,0 @@
import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// types
import { IUser } from "@plane/types";
// services
import { AuthService } from "@/services/authentication.service";
import { UserService } from "@/services/user.service";
// stores
import { RootStore } from "@/store/root.store";
import { ProfileStore, IProfileStore } from "@/store/user/profile.store";
import { ActorDetail } from "@/types/issue";
type TUserErrorStatus = {
status: string;
message: string;
};
export interface IUserStore {
// observables
isAuthenticated: boolean;
isLoading: boolean;
error: TUserErrorStatus | undefined;
data: IUser | undefined;
// store observables
userProfile: IProfileStore;
// computed
currentActor: ActorDetail;
// actions
fetchCurrentUser: () => Promise<IUser | undefined>;
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
reset: () => void;
signOut: () => Promise<void>;
}
export class UserStore implements IUserStore {
// observables
isAuthenticated: boolean = false;
isLoading: boolean = false;
error: TUserErrorStatus | undefined = undefined;
data: IUser | undefined = undefined;
// store observables
userProfile: IProfileStore;
// service
userService: UserService;
authService: AuthService;
constructor(private store: RootStore) {
// stores
this.userProfile = new ProfileStore(store);
// service
this.userService = new UserService();
this.authService = new AuthService();
// observables
makeObservable(this, {
// observables
isAuthenticated: observable.ref,
isLoading: observable.ref,
error: observable,
// model observables
data: observable,
userProfile: observable,
// computed
currentActor: computed,
// actions
fetchCurrentUser: action,
updateCurrentUser: action,
reset: action,
signOut: action,
});
}
// computed
get currentActor(): ActorDetail {
return {
id: this.data?.id,
first_name: this.data?.first_name,
last_name: this.data?.last_name,
display_name: this.data?.display_name,
avatar: this.data?.avatar || undefined,
is_bot: false,
};
}
// actions
/**
* @description fetches the current user
* @returns {Promise<IUser>}
*/
fetchCurrentUser = async (): Promise<IUser> => {
try {
runInAction(() => {
this.isLoading = true;
this.error = undefined;
});
const user = await this.userService.currentUser();
if (user && user?.id) {
await this.userProfile.fetchUserProfile();
runInAction(() => {
this.data = user;
this.isLoading = false;
this.isAuthenticated = true;
});
} else
runInAction(() => {
this.data = user;
this.isLoading = false;
this.isAuthenticated = false;
});
return user;
} catch (error) {
runInAction(() => {
this.isLoading = false;
this.isAuthenticated = false;
this.error = {
status: "user-fetch-error",
message: "Failed to fetch current user",
};
});
throw error;
}
};
/**
* @description updates the current user
* @param data
* @returns {Promise<IUser>}
*/
updateCurrentUser = async (data: Partial<IUser>): Promise<IUser> => {
const currentUserData = this.data;
try {
if (currentUserData) {
Object.keys(data).forEach((key: string) => {
const userKey: keyof IUser = key as keyof IUser;
if (this.data) set(this.data, userKey, data[userKey]);
});
}
const user = await this.userService.updateUser(data);
return user;
} catch (error) {
if (currentUserData) {
Object.keys(currentUserData).forEach((key: string) => {
const userKey: keyof IUser = key as keyof IUser;
if (this.data) set(this.data, userKey, currentUserData[userKey]);
});
}
runInAction(() => {
this.error = {
status: "user-update-error",
message: "Failed to update current user",
};
});
throw error;
}
};
/**
* @description resets the user store
* @returns {void}
*/
reset = (): void => {
runInAction(() => {
this.isAuthenticated = false;
this.isLoading = false;
this.error = undefined;
this.data = undefined;
this.userProfile = new ProfileStore(this.store);
});
};
/**
* @description signs out the current user
* @returns {Promise<void>}
*/
signOut = async (): Promise<void> => {
// await this.authService.signOut(API_BASE_URL);
// this.store.resetOnSignOut();
};
}

6
space/types/issue-filters.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export interface ILayoutDisplayFiltersOptions {
filters: (keyof IIssueFilterOptions)[];
display_properties: boolean | null;
display_filters: null;
extra_options: null;
}

View File

@ -170,3 +170,38 @@ export interface IssueDetailType {
votes: any[]; votes: any[];
}; };
} }
export type TIssueGroupByOptions = "state" | "priority" | "labels" | null;
export type TIssueParams = "priority" | "state" | "labels";
export interface IIssueFilterOptions {
state?: string[] | null;
labels?: string[] | null;
priority?: string[] | null;
}
// issues
export interface IGroupedIssues {
[group_id: string]: string[];
}
export interface ISubGroupedIssues {
[sub_grouped_id: string]: {
[group_id: string]: string[];
};
}
export type TUnGroupedIssues = string[];
export interface IIssueResponse {
[issue_id: string]: IIssue;
}
export type TLoader = "init-loader" | "mutation" | undefined;
export interface ViewFlags {
enableQuickAdd: boolean;
enableIssueCreation: boolean;
enableInlineEditing: boolean;
}