chore: user auth layer (#749)

* chore: use estimate points hook created

* chore: user auth layer

* fix: build error
This commit is contained in:
Aaryan Khandelwal 2023-04-08 18:05:54 +05:30 committed by GitHub
parent 3fe32606a9
commit 1026ae3eb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 603 additions and 487 deletions

View File

@ -21,9 +21,10 @@ import useToast from "hooks/use-toast";
import { import {
ViewAssigneeSelect, ViewAssigneeSelect,
ViewDueDateSelect, ViewDueDateSelect,
ViewEstimateSelect,
ViewPrioritySelect, ViewPrioritySelect,
ViewStateSelect, ViewStateSelect,
} from "components/issues/view-select"; } from "components/issues";
// ui // ui
import { ContextMenu, CustomMenu } from "components/ui"; import { ContextMenu, CustomMenu } from "components/ui";
// icons // icons
@ -48,6 +49,7 @@ import {
MODULE_ISSUES_WITH_PARAMS, MODULE_ISSUES_WITH_PARAMS,
PROJECT_ISSUES_LIST_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
import useEstimateOption from "hooks/use-estimate-option";
type Props = { type Props = {
type?: string; type?: string;
@ -90,6 +92,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
const { orderBy, params } = useIssuesView(); const { orderBy, params } = useIssuesView();
const { estimateValue } = useEstimateOption(issue.estimate_point);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
@ -342,6 +346,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
selfPositioned selfPositioned
/> />
)} )}
{properties.estimate && (
<ViewEstimateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
isNotAllowed={isNotAllowed}
selfPositioned
/>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,44 +1,41 @@
import React, { useState } from "react"; import React, { useState } from "react";
// ui
import { CustomMenu, PrimaryButton } from "components/ui";
// types
import { IEstimate, IProject } from "types";
//icons
import { PencilIcon, TrashIcon, SquaresPlusIcon, ListBulletIcon } from "@heroicons/react/24/outline";
import useSWR, { mutate } from "swr";
import useToast from "hooks/use-toast";
import estimatesService from "services/estimates.service";
import projectService from "services/project.service";
import { EstimatePointsModal } from "./estimate-points-modal";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys"; import useSWR from "swr";
import { PlusIcon } from "components/icons";
interface IEstimatePoints { // services
key: string; import estimatesService from "services/estimates.service";
value: string; import projectService from "services/project.service";
} // hooks
import useToast from "hooks/use-toast";
import useProjectDetails from "hooks/use-project-details";
// components
import { EstimatePointsModal } from "components/estimates";
// ui
import { CustomMenu } from "components/ui";
//icons
import {
PencilIcon,
TrashIcon,
SquaresPlusIcon,
ListBulletIcon,
} from "@heroicons/react/24/outline";
// types
import { IEstimate, IProject } from "types";
// fetch-keys
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
type Props = { type Props = {
estimate: IEstimate; estimate: IEstimate;
editEstimate: (estimate: IEstimate) => void; editEstimate: (estimate: IEstimate) => void;
handleEstimateDelete: (estimateId: string) => void; handleEstimateDelete: (estimateId: string) => void;
activeEstimate: IEstimate | null;
setActiveEstimate: React.Dispatch<React.SetStateAction<IEstimate | null>>;
}; };
export const SingleEstimate: React.FC<Props> = ({ export const SingleEstimate: React.FC<Props> = ({
estimate, estimate,
editEstimate, editEstimate,
handleEstimateDelete, handleEstimateDelete,
activeEstimate,
setActiveEstimate,
}) => { }) => {
const [isEstimatePointsModalOpen, setIsEstimatePointsModalOpen] = useState(false); const [isEstimatePointsModalOpen, setIsEstimatePointsModalOpen] = useState(false);
@ -47,6 +44,8 @@ export const SingleEstimate: React.FC<Props> = ({
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { projectDetails, mutateProjectDetails } = useProjectDetails();
const { data: estimatePoints } = useSWR( const { data: estimatePoints } = useSWR(
workspaceSlug && projectId ? ESTIMATE_POINTS_LIST(estimate.id) : null, workspaceSlug && projectId ? ESTIMATE_POINTS_LIST(estimate.id) : null,
workspaceSlug && projectId workspaceSlug && projectId
@ -59,12 +58,19 @@ export const SingleEstimate: React.FC<Props> = ({
: null : null
); );
const handleActiveEstimate = async () => { const handleUseEstimate = async () => {
if (!workspaceSlug || !projectId || !estimate) return; if (!workspaceSlug || !projectId) return;
const payload: Partial<IProject> = {
const payload = {
estimate: estimate.id, estimate: estimate.id,
}; };
setActiveEstimate(estimate);
mutateProjectDetails((prevData) => {
if (!prevData) return prevData;
return { ...prevData, estimate: estimate.id };
}, false);
await projectService await projectService
.updateProject(workspaceSlug as string, projectId as string, payload) .updateProject(workspaceSlug as string, projectId as string, payload)
.catch(() => { .catch(() => {
@ -77,57 +83,64 @@ export const SingleEstimate: React.FC<Props> = ({
}; };
return ( return (
<div className="divide-y"> <>
<EstimatePointsModal <EstimatePointsModal
isOpen={isEstimatePointsModalOpen} isOpen={isEstimatePointsModalOpen}
estimate={estimate} estimate={estimate}
onClose={() => setIsEstimatePointsModalOpen(false)} onClose={() => setIsEstimatePointsModalOpen(false)}
/> />
<div className="gap-2 space-y-3 my-3 bg-white"> <div className="gap-2 py-3">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="items-start"> <div>
<h6 className="font-medium text-base w-[40vw] truncate">{estimate.name}</h6> <h6 className="flex items-center gap-2 font-medium text-base w-[40vw] truncate">
{estimate.name}
{projectDetails?.estimate && projectDetails?.estimate === estimate.id && (
<span className="capitalize px-2 py-0.5 text-xs rounded bg-green-100 text-green-500">
In use
</span>
)}
</h6>
<p className="font-sm text-gray-400 font-normal text-[14px] w-[40vw] truncate"> <p className="font-sm text-gray-400 font-normal text-[14px] w-[40vw] truncate">
{estimate.description} {estimate.description}
</p> </p>
</div> </div>
<div className="flex items-center gap-8">
<CustomMenu ellipsis> <CustomMenu ellipsis>
<CustomMenu.MenuItem onClick={handleActiveEstimate}> {projectDetails?.estimate && projectDetails?.estimate !== estimate.id && (
<span className="flex items-center justify-start gap-2"> <CustomMenu.MenuItem onClick={handleUseEstimate}>
<SquaresPlusIcon className="h-4 w-4" /> <div className="flex items-center justify-start gap-2">
<SquaresPlusIcon className="h-3.5 w-3.5" />
<span>Use estimate</span> <span>Use estimate</span>
</span> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={() => setIsEstimatePointsModalOpen(true)}> <CustomMenu.MenuItem onClick={() => setIsEstimatePointsModalOpen(true)}>
<span className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<ListBulletIcon className="h-4 w-4" /> <ListBulletIcon className="h-3.5 w-3.5" />
{estimatePoints?.length === 8 ? "Update points" : "Create points"} <span>{estimatePoints?.length === 8 ? "Update points" : "Create points"}</span>
</span> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={() => { onClick={() => {
editEstimate(estimate); editEstimate(estimate);
}} }}
> >
<span className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<PencilIcon className="h-4 w-4" /> <PencilIcon className="h-3.5 w-3.5" />
<span>Edit estimate</span> <span>Edit estimate</span>
</span> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={() => { onClick={() => {
handleEstimateDelete(estimate.id); handleEstimateDelete(estimate.id);
}} }}
> >
<span className="flex items-center justify-start gap-2"> <div className="flex items-center justify-start gap-2">
<TrashIcon className="h-4 w-4" /> <TrashIcon className="h-3.5 w-3.5" />
<span>Delete estimate</span> <span>Delete estimate</span>
</span> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
</CustomMenu> </CustomMenu>
</div> </div>
</div>
{estimatePoints && estimatePoints.length > 0 ? ( {estimatePoints && estimatePoints.length > 0 ? (
<div className="flex gap-2"> <div className="flex gap-2">
{estimatePoints.length > 0 && "Estimate points ("} {estimatePoints.length > 0 && "Estimate points ("}
@ -141,10 +154,10 @@ export const SingleEstimate: React.FC<Props> = ({
</div> </div>
) : ( ) : (
<div> <div>
<p className= " text-sm text-gray-300">No estimate points</p> <p className=" text-sm text-gray-300">No estimate points</p>
</div> </div>
)} )}
</div> </div>
</div> </>
); );
}; };

View File

@ -1,5 +1,6 @@
export * from "./comment"; export * from "./comment";
export * from "./sidebar-select"; export * from "./sidebar-select";
export * from "./view-select";
export * from "./activity"; export * from "./activity";
export * from "./delete-issue-modal"; export * from "./delete-issue-modal";
export * from "./description-form"; export * from "./description-form";

View File

@ -0,0 +1,69 @@
import React from "react";
// ui
import { CustomSelect, Tooltip } from "components/ui";
// icons
import { getPriorityIcon } from "components/icons/priority-icon";
// types
import { IIssue } from "types";
// constants
import { PRIORITIES } from "constants/project";
// services
import trackEventServices from "services/track-event.service";
import useEstimateOption from "hooks/use-estimate-option";
type Props = {
issue: IIssue;
partialUpdateIssue: (formData: Partial<IIssue>) => void;
position?: "left" | "right";
selfPositioned?: boolean;
isNotAllowed: boolean;
};
export const ViewEstimateSelect: React.FC<Props> = ({
issue,
partialUpdateIssue,
position = "left",
selfPositioned = false,
isNotAllowed,
}) => {
const { isEstimateActive, estimatePoints, estimateValue } = useEstimateOption(
issue.estimate_point
);
return (
<CustomSelect
value={issue.priority}
onChange={(data: string) => {
partialUpdateIssue({ priority: data, state: issue.state, target_date: issue.target_date });
trackEventServices.trackIssuePartialPropertyUpdateEvent(
{
workspaceSlug: issue.workspace_detail.slug,
workspaceId: issue.workspace_detail.id,
projectId: issue.project_detail.id,
projectIdentifier: issue.project_detail.identifier,
projectName: issue.project_detail.name,
issueId: issue.id,
},
"ISSUE_PROPERTY_UPDATE_PRIORITY"
);
}}
label={
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue}>
<>{estimateValue}</>
</Tooltip>
}
maxHeight="md"
noChevron
disabled={isNotAllowed}
position={position}
selfPositioned={selfPositioned}
>
{estimatePoints?.map((estimate) => (
<CustomSelect.Option key={estimate.id} value={estimate.key} className="capitalize">
<>{estimate.value}</>
</CustomSelect.Option>
))}
</CustomSelect>
);
};

View File

@ -1,4 +1,5 @@
export * from "./assignee"; export * from "./assignee";
export * from "./due-date"; export * from "./due-date";
export * from "./estimate";
export * from "./priority"; export * from "./priority";
export * from "./state"; export * from "./state";

View File

@ -1,13 +1,11 @@
import React, { createContext, ReactElement } from "react"; import React, { createContext, ReactElement } from "react";
// next
import { useRouter } from "next/router";
// swr
import useSWR, { KeyedMutator } from "swr"; import useSWR, { KeyedMutator } from "swr";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// constants // constants
import { CURRENT_USER } from "constants/fetch-keys"; import { CURRENT_USER } from "constants/fetch-keys";
// types // types
import type { IUser } from "types"; import type { IUser } from "types";
@ -22,18 +20,11 @@ interface IUserContextProps {
export const UserContext = createContext<IUserContextProps>({} as IUserContextProps); export const UserContext = createContext<IUserContextProps>({} as IUserContextProps);
export const UserProvider = ({ children }: { children: ReactElement }) => { export const UserProvider = ({ children }: { children: ReactElement }) => {
const router = useRouter();
// API to fetch user information // API to fetch user information
const { data, error, mutate } = useSWR<IUser>(CURRENT_USER, () => userService.currentUser(), { const { data, error, mutate } = useSWR<IUser>(CURRENT_USER, () => userService.currentUser(), {
shouldRetryOnError: false, shouldRetryOnError: false,
}); });
if (error) {
router.push("/signin");
return null;
}
return ( return (
<UserContext.Provider <UserContext.Provider
value={{ value={{

View File

@ -0,0 +1,49 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import estimatesService from "services/estimates.service";
// hooks
import useProjectDetails from "hooks/use-project-details";
// fetch-keys
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
const useEstimateOption = (estimateKey?: number) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const { projectDetails } = useProjectDetails();
const { data: estimatePoints, error: estimatePointsError } = useSWR(
workspaceSlug && projectId && projectDetails && projectDetails?.estimate
? ESTIMATE_POINTS_LIST(projectDetails.estimate as string)
: null,
workspaceSlug && projectId && projectDetails && projectDetails.estimate
? () =>
estimatesService.getEstimatesPointsList(
workspaceSlug as string,
projectId as string,
projectDetails.estimate
)
: null
);
const estimateValue: any =
(estimateKey && estimatePoints?.find((e) => e.key === estimateKey)?.value) ?? "None";
useEffect(() => {
if (estimatePointsError?.status === 404) router.push("/404");
else if (estimatePointsError) router.push("/error");
}, [estimatePointsError, router]);
return {
isEstimateActive: projectDetails?.estimate ? true : false,
estimatePoints,
estimateValue,
};
};
export default useEstimateOption;

View File

@ -15,6 +15,7 @@ const initialValues: Properties = {
priority: false, priority: false,
state: true, state: true,
sub_issue_count: false, sub_issue_count: false,
estimate: false,
}; };
const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => { const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
@ -90,6 +91,7 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
priority: properties.priority, priority: properties.priority,
state: properties.state, state: properties.state,
sub_issue_count: properties.sub_issue_count, sub_issue_count: properties.sub_issue_count,
estimate: properties.estimate,
}; };
return [newProperties, updateIssueProperties] as const; return [newProperties, updateIssueProperties] as const;

View File

@ -24,6 +24,7 @@ const initialValues: Properties = {
priority: false, priority: false,
state: true, state: true,
sub_issue_count: false, sub_issue_count: false,
estimate: false,
}; };
// TODO: Refactor this logic // TODO: Refactor this logic

View File

@ -0,0 +1,42 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import projectService from "services/project.service";
// fetch-keys
import { PROJECT_DETAILS } from "constants/fetch-keys";
const useProjectDetails = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const {
data: projectDetails,
error: projectDetailsError,
mutate: mutateProjectDetails,
} = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
);
useEffect(() => {
if (projectDetailsError?.status === 404) {
router.push("/404");
} else if (projectDetailsError) {
router.push("/error");
}
}, [projectDetailsError, router]);
return {
projectDetails,
projectDetailsError,
mutateProjectDetails,
};
};
export default useProjectDetails;

View File

@ -1,40 +1,11 @@
import { useContext, useEffect } from "react"; import { useContext } from "react";
import { useRouter } from "next/router";
// context // context
import { UserContext } from "contexts/user.context"; import { UserContext } from "contexts/user.context";
interface useUserOptions { const useUser = () => {
redirectTo?: string;
}
const useUser = (options: useUserOptions = {}) => {
// props
const { redirectTo = null } = options;
// context // context
const contextData = useContext(UserContext); const contextData = useContext(UserContext);
// router
const router = useRouter();
/**
* Checks for redirect url and user details from the API.
* if the user is not authenticated, user will be redirected
* to the provided redirectTo route.
*/
useEffect(() => {
if (!contextData?.user || !redirectTo) return;
if (!contextData?.user) {
if (redirectTo) {
router?.pathname !== redirectTo && router.push(redirectTo);
}
router?.pathname !== "/signin" && router.push("/signin");
}
if (contextData?.user) {
if (redirectTo) {
router?.pathname !== redirectTo && router.push(redirectTo);
}
}
}, [contextData?.user, redirectTo, router]);
return { ...contextData }; return { ...contextData };
}; };

View File

@ -5,13 +5,12 @@ import { useRouter } from "next/router";
// contexts // contexts
import { useProjectMyMembership, ProjectMemberProvider } from "contexts/project-member.context"; import { useProjectMyMembership, ProjectMemberProvider } from "contexts/project-member.context";
// hooks
import useUser from "hooks/use-user";
// layouts // layouts
import Container from "layouts/container"; import Container from "layouts/container";
import AppHeader from "layouts/app-layout/app-header"; import AppHeader from "layouts/app-layout/app-header";
import AppSidebar from "layouts/app-layout/app-sidebar"; import AppSidebar from "layouts/app-layout/app-sidebar";
import SettingsNavbar from "layouts/settings-navbar"; import SettingsNavbar from "layouts/settings-navbar";
import { WorkspaceAuthorizationLayout } from "./workspace-authorization-wrapper";
// components // components
import { NotAuthorizedView, JoinProject } from "components/auth-screens"; import { NotAuthorizedView, JoinProject } from "components/auth-screens";
import { CommandPalette } from "components/command-palette"; import { CommandPalette } from "components/command-palette";
@ -59,13 +58,12 @@ const ProjectAuthorizationWrapped: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const user = useUser();
const { loading, error, memberRole: memberType } = useProjectMyMembership(); const { loading, error, memberRole: memberType } = useProjectMyMembership();
const settingsLayout = router.pathname.includes("/settings"); const settingsLayout = router.pathname.includes("/settings");
return ( return (
<WorkspaceAuthorizationLayout>
<Container meta={meta}> <Container meta={meta}>
<CommandPalette /> <CommandPalette />
<div className="flex h-screen w-full overflow-x-hidden"> <div className="flex h-screen w-full overflow-x-hidden">
@ -117,7 +115,11 @@ const ProjectAuthorizationWrapped: React.FC<Props> = ({
className={`flex w-full flex-grow flex-col ${ className={`flex w-full flex-grow flex-col ${
noPadding ? "" : settingsLayout ? "p-8 lg:px-28" : "p-8" noPadding ? "" : settingsLayout ? "p-8 lg:px-28" : "p-8"
} ${ } ${
bg === "primary" ? "bg-primary" : bg === "secondary" ? "bg-secondary" : "bg-primary" bg === "primary"
? "bg-primary"
: bg === "secondary"
? "bg-secondary"
: "bg-primary"
}`} }`}
> >
{settingsLayout && ( {settingsLayout && (
@ -137,5 +139,6 @@ const ProjectAuthorizationWrapped: React.FC<Props> = ({
)} )}
</div> </div>
</Container> </Container>
</WorkspaceAuthorizationLayout>
); );
}; };

View File

@ -0,0 +1,28 @@
import useSWR from "swr";
import { CURRENT_USER } from "constants/fetch-keys";
import userService from "services/user.service";
import { useRouter } from "next/router";
type Props = {
children: React.ReactNode;
};
export const UserAuthorizationLayout: React.FC<Props> = ({ children }) => {
const router = useRouter();
const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser());
if (!currentUser && !error) {
return <div className="grid place-items-center h-screen">Loading...</div>;
}
if (error?.status === 401) {
const redirectTo = router.asPath;
router.push(`/signin?next=${redirectTo}`);
return null;
}
return <>{children}</>;
};

View File

@ -7,8 +7,6 @@ import useSWR from "swr";
// services // services
import workspaceServices from "services/workspace.service"; import workspaceServices from "services/workspace.service";
// hooks
import useUser from "hooks/use-user";
// layouts // layouts
import Container from "layouts/container"; import Container from "layouts/container";
import AppSidebar from "layouts/app-layout/app-sidebar"; import AppSidebar from "layouts/app-layout/app-sidebar";
@ -22,6 +20,7 @@ import { PrimaryButton } from "components/ui";
import { LayerDiagonalIcon } from "components/icons"; import { LayerDiagonalIcon } from "components/icons";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
import { UserAuthorizationLayout } from "./user-authorization-wrapper";
type Meta = { type Meta = {
title?: string | null; title?: string | null;
@ -58,8 +57,6 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const user = useUser();
const { data: workspaceMemberMe, error } = useSWR( const { data: workspaceMemberMe, error } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null, workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
workspaceSlug ? () => workspaceServices.workspaceMemberMe(workspaceSlug.toString()) : null, workspaceSlug ? () => workspaceServices.workspaceMemberMe(workspaceSlug.toString()) : null,
@ -99,6 +96,7 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
}; };
return ( return (
<UserAuthorizationLayout>
<Container meta={meta}> <Container meta={meta}>
<CommandPalette /> <CommandPalette />
<div className="flex h-screen w-full overflow-x-hidden"> <div className="flex h-screen w-full overflow-x-hidden">
@ -130,7 +128,11 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
className={`flex w-full flex-grow flex-col ${ className={`flex w-full flex-grow flex-col ${
noPadding ? "" : settingsLayout || profilePage ? "p-8 lg:px-28" : "p-8" noPadding ? "" : settingsLayout || profilePage ? "p-8 lg:px-28" : "p-8"
} ${ } ${
bg === "primary" ? "bg-primary" : bg === "secondary" ? "bg-secondary" : "bg-primary" bg === "primary"
? "bg-primary"
: bg === "secondary"
? "bg-secondary"
: "bg-primary"
}`} }`}
> >
{(settingsLayout || profilePage) && ( {(settingsLayout || profilePage) && (
@ -154,5 +156,6 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
)} )}
</div> </div>
</Container> </Container>
</UserAuthorizationLayout>
); );
}; };

View File

@ -6,13 +6,12 @@ import useSWR, { mutate } from "swr";
// services // services
import estimatesService from "services/estimates.service"; import estimatesService from "services/estimates.service";
import projectService from "services/project.service"; // hooks
import useProjectDetails from "hooks/use-project-details";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// components // components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates"; import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
//hooks //hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
@ -21,10 +20,10 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// types // types
import { IEstimate, IProject } from "types"; import { IEstimate } from "types";
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { ESTIMATES_LIST } from "constants/fetch-keys";
const EstimatesSettings: NextPage = () => { const EstimatesSettings: NextPage = () => {
const [estimateFormOpen, setEstimateFormOpen] = useState(false); const [estimateFormOpen, setEstimateFormOpen] = useState(false);
@ -32,14 +31,14 @@ const EstimatesSettings: NextPage = () => {
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>(); const [estimateToUpdate, setEstimateToUpdate] = useState<IEstimate | undefined>();
const [activeEstimate, setActiveEstimate] = useState<IEstimate | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const scollToRef = useRef<HTMLDivElement>(null); const { projectDetails } = useProjectDetails();
const scrollToRef = useRef<HTMLDivElement>(null);
const { data: estimatesList } = useSWR<IEstimate[]>( const { data: estimatesList } = useSWR<IEstimate[]>(
workspaceSlug && projectId ? ESTIMATES_LIST(projectId as string) : null, workspaceSlug && projectId ? ESTIMATES_LIST(projectId as string) : null,
@ -74,13 +73,6 @@ const EstimatesSettings: NextPage = () => {
}); });
}; };
const { data: projectDetails } = useSWR<IProject>(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
);
return ( return (
<> <>
<ProjectAuthorizationWrapper <ProjectAuthorizationWrapper
@ -122,7 +114,6 @@ const EstimatesSettings: NextPage = () => {
</div> </div>
</div> </div>
</section> </section>
<hr className="h-[1px] w-full mt-4" />
{estimatesList && estimatesList.length > 0 && ( {estimatesList && estimatesList.length > 0 && (
<section className="mt-4 divide-y px-6 mb-8 rounded-xl border bg-white"> <section className="mt-4 divide-y px-6 mb-8 rounded-xl border bg-white">
<> <>
@ -131,8 +122,6 @@ const EstimatesSettings: NextPage = () => {
<SingleEstimate <SingleEstimate
key={estimate.id} key={estimate.id}
estimate={estimate} estimate={estimate}
activeEstimate={activeEstimate}
setActiveEstimate={setActiveEstimate}
editEstimate={(estimate) => editEstimate(estimate)} editEstimate={(estimate) => editEstimate(estimate)}
handleEstimateDelete={(estimateId) => removeEstimate(estimateId)} handleEstimateDelete={(estimateId) => removeEstimate(estimateId)}
/> />

View File

@ -3,14 +3,13 @@ import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Image from "next/image"; import Image from "next/image";
// constants
import { requiredAuth } from "lib/auth";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper";
// images // images
import Logo from "public/onboarding/logo.svg"; import Logo from "public/onboarding/logo.svg";
// types // types
import type { NextPage, NextPageContext } from "next"; import type { NextPage } from "next";
// constants // constants
import { CreateWorkspaceForm } from "components/workspace"; import { CreateWorkspaceForm } from "components/workspace";
@ -23,6 +22,7 @@ const CreateWorkspace: NextPage = () => {
}; };
return ( return (
<UserAuthorizationLayout>
<DefaultLayout> <DefaultLayout>
<div className="grid h-full place-items-center p-5"> <div className="grid h-full place-items-center p-5">
<div className="w-full space-y-4"> <div className="w-full space-y-4">
@ -37,28 +37,8 @@ const CreateWorkspace: NextPage = () => {
</div> </div>
</div> </div>
</DefaultLayout> </DefaultLayout>
</UserAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default CreateWorkspace; export default CreateWorkspace;

View File

@ -1,10 +1,10 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// lib
import { requiredAuth } from "lib/auth";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// hooks // hooks
@ -12,6 +12,7 @@ import useUser from "hooks/use-user";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper";
// components // components
import SingleInvitation from "components/workspace/single-invitation"; import SingleInvitation from "components/workspace/single-invitation";
// ui // ui
@ -19,7 +20,7 @@ import { Spinner, EmptySpace, EmptySpaceItem, SecondaryButton, PrimaryButton } f
// icons // icons
import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline"; import { CubeIcon, PlusIcon } from "@heroicons/react/24/outline";
// types // types
import type { NextPage, NextPageContext } from "next"; import type { NextPage } from "next";
import type { IWorkspaceMemberInvitation } from "types"; import type { IWorkspaceMemberInvitation } from "types";
const OnBoard: NextPage = () => { const OnBoard: NextPage = () => {
@ -77,6 +78,7 @@ const OnBoard: NextPage = () => {
}; };
return ( return (
<UserAuthorizationLayout>
<DefaultLayout <DefaultLayout
meta={{ meta={{
title: "Plane - Welcome to Plane", title: "Plane - Welcome to Plane",
@ -164,28 +166,8 @@ const OnBoard: NextPage = () => {
</div> </div>
</div> </div>
</DefaultLayout> </DefaultLayout>
</UserAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: NextPageContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.req?.url;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default OnBoard; export default OnBoard;

View File

@ -3,14 +3,13 @@ import { useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// lib
import { requiredAuth } from "lib/auth";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// hooks // hooks
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper";
// components // components
import { InviteMembers, OnboardingCard, UserDetails, Workspace } from "components/onboarding"; import { InviteMembers, OnboardingCard, UserDetails, Workspace } from "components/onboarding";
// ui // ui
@ -20,7 +19,7 @@ import { ONBOARDING_CARDS } from "constants/workspace";
// images // images
import Logo from "public/onboarding/logo.svg"; import Logo from "public/onboarding/logo.svg";
// types // types
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage } from "next";
const Onboarding: NextPage = () => { const Onboarding: NextPage = () => {
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
@ -33,6 +32,7 @@ const Onboarding: NextPage = () => {
const { user } = useUser(); const { user } = useUser();
return ( return (
<UserAuthorizationLayout>
<DefaultLayout> <DefaultLayout>
<div className="grid h-full place-items-center p-5"> <div className="grid h-full place-items-center p-5">
{step <= 3 ? ( {step <= 3 ? (
@ -88,28 +88,8 @@ const Onboarding: NextPage = () => {
)} )}
</div> </div>
</DefaultLayout> </DefaultLayout>
</UserAuthorizationLayout>
); );
}; };
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const user = await requiredAuth(ctx.req?.headers.cookie);
const redirectAfterSignIn = ctx.resolvedUrl;
if (!user) {
return {
redirect: {
destination: `/signin?next=${redirectAfterSignIn}`,
permanent: false,
},
};
}
return {
props: {
user,
},
};
};
export default Onboarding; export default Onboarding;

View File

@ -30,12 +30,10 @@ class UserService extends APIService {
} }
async currentUser(): Promise<any> { async currentUser(): Promise<any> {
if (!this.getAccessToken()) return null;
return this.get("/api/users/me/") return this.get("/api/users/me/")
.then((response) => response?.data) .then((response) => response?.data)
.catch((error) => { .catch((error) => {
this.purgeAccessToken(); throw error?.response;
throw error?.response?.data;
}); });
} }

View File

@ -186,6 +186,7 @@ export type Properties = {
priority: boolean; priority: boolean;
state: boolean; state: boolean;
sub_issue_count: boolean; sub_issue_count: boolean;
estimate: boolean;
}; };
export interface IIssueLabels { export interface IIssueLabels {