chore: bug fixes and ui/ux enhancements (#2036)

This commit is contained in:
Anmol Singh Bhatia 2023-09-01 16:52:44 +05:30 committed by GitHub
parent 8a95a41100
commit 74bf9062b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 316 additions and 229 deletions

View File

@ -22,8 +22,6 @@ const shortcuts = [
{ keys: "↓", description: "Move down" }, { keys: "↓", description: "Move down" },
{ keys: "←", description: "Move left" }, { keys: "←", description: "Move left" },
{ keys: "→", description: "Move right" }, { keys: "→", description: "Move right" },
{ keys: "Enter", description: "Select" },
{ keys: "Esc", description: "Close" },
], ],
}, },
{ {

View File

@ -353,6 +353,28 @@ const activityDetails: {
. .
</> </>
); );
else if (activity.verb === "updated")
return (
<>
updated the{" "}
<a
href={`${activity.old_value}`}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
link
<Icon iconName="launch" className="!text-xs" />
</a>
{showIssue && (
<>
{" "}
from <IssueLink activity={activity} />
</>
)}
.
</>
);
else else
return ( return (
<> <>

View File

@ -158,7 +158,7 @@ export const IssuesFilterView: React.FC = () => {
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
> >
View Display
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Popover.Button> </Popover.Button>

View File

@ -40,9 +40,15 @@ type Props = {
label: string | React.ReactNode; label: string | React.ReactNode;
value: string | null; value: string | null;
onChange: (data: string) => void; onChange: (data: string) => void;
disabled?: boolean;
}; };
export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange }) => { export const ImagePickerPopover: React.FC<Props> = ({
label,
value,
onChange,
disabled = false,
}) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const router = useRouter(); const router = useRouter();
@ -117,6 +123,7 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
<Popover.Button <Popover.Button
className="rounded-md border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100" className="rounded-md border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100"
onClick={() => setIsOpen((prev) => !prev)} onClick={() => setIsOpen((prev) => !prev)}
disabled={disabled}
> >
{label} {label}
</Popover.Button> </Popover.Button>

View File

@ -29,6 +29,7 @@ type Props = {
isCollapsed: boolean; isCollapsed: boolean;
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>; setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
disableUserActions: boolean; disableUserActions: boolean;
disableAddIssue: boolean;
viewProps: IIssueViewProps; viewProps: IIssueViewProps;
}; };
@ -39,6 +40,7 @@ export const BoardHeader: React.FC<Props> = ({
isCollapsed, isCollapsed,
setIsCollapsed, setIsCollapsed,
disableUserActions, disableUserActions,
disableAddIssue,
viewProps, viewProps,
}) => { }) => {
const router = useRouter(); const router = useRouter();
@ -181,7 +183,7 @@ export const BoardHeader: React.FC<Props> = ({
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" /> <Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
)} )}
</button> </button>
{!disableUserActions && selectedGroup !== "created_by" && ( {!disableAddIssue && !disableUserActions && selectedGroup !== "created_by" && (
<button <button
type="button" type="button"
className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80" className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80"

View File

@ -53,6 +53,8 @@ export const SingleBoard: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { cycleId, moduleId } = router.query; const { cycleId, moduleId } = router.query;
const isSubscribedIssues = router.pathname.includes("subscribed");
const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
// Check if it has at least 4 tickets since it is enough to accommodate the Calendar height // Check if it has at least 4 tickets since it is enough to accommodate the Calendar height
@ -70,6 +72,7 @@ export const SingleBoard: React.FC<Props> = ({
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed} setIsCollapsed={setIsCollapsed}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
disableAddIssue={isSubscribedIssues}
viewProps={viewProps} viewProps={viewProps}
/> />
{isCollapsed && ( {isCollapsed && (
@ -150,41 +153,41 @@ export const SingleBoard: React.FC<Props> = ({
</div> </div>
{selectedGroup !== "created_by" && ( {selectedGroup !== "created_by" && (
<div> <div>
{type === "issue" ? ( {type === "issue"
<button ? !isSubscribedIssues && (
type="button" <button
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1" type="button"
onClick={addIssueToGroup} className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
> onClick={addIssueToGroup}
<PlusIcon className="h-4 w-4" /> >
Add Issue <PlusIcon className="h-4 w-4" />
</button> Add Issue
) : ( </button>
!disableUserActions && ( )
<CustomMenu : !disableUserActions && (
customButton={ <CustomMenu
<button customButton={
type="button" <button
className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap" type="button"
> className="flex items-center gap-2 font-medium text-custom-primary outline-none whitespace-nowrap"
<PlusIcon className="h-4 w-4" /> >
Add Issue <PlusIcon className="h-4 w-4" />
</button> Add Issue
} </button>
position="left" }
noBorder position="left"
> noBorder
<CustomMenu.MenuItem onClick={addIssueToGroup}> >
Create new <CustomMenu.MenuItem onClick={addIssueToGroup}>
</CustomMenu.MenuItem> Create new
{openIssuesListModal && (
<CustomMenu.MenuItem onClick={openIssuesListModal}>
Add an existing issue
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)} {openIssuesListModal && (
</CustomMenu> <CustomMenu.MenuItem onClick={openIssuesListModal}>
) Add an existing issue
)} </CustomMenu.MenuItem>
)}
</CustomMenu>
)}
</div> </div>
)} )}
</div> </div>

View File

@ -350,7 +350,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
/> />
)} )}
{properties.labels && issue.labels.length > 0 && ( {properties.labels && issue.labels.length > 0 && (
<ViewIssueLabel issue={issue} maxRender={2} /> <ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
)} )}
{properties.assignee && ( {properties.assignee && (
<ViewAssigneeSelect <ViewAssigneeSelect

View File

@ -36,9 +36,21 @@ import { LayerDiagonalIcon } from "components/icons";
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
import { handleIssuesMutation } from "constants/issue"; import { handleIssuesMutation } from "constants/issue";
// types // types
import { ICurrentUserResponse, IIssue, IIssueViewProps, ISubIssueResponse, UserAuth } from "types"; import {
ICurrentUserResponse,
IIssue,
IIssueViewProps,
ISubIssueResponse,
IUserProfileProjectSegregation,
UserAuth,
} from "types";
// fetch-keys // fetch-keys
import { CYCLE_DETAILS, MODULE_DETAILS, SUB_ISSUES } from "constants/fetch-keys"; import {
CYCLE_DETAILS,
MODULE_DETAILS,
SUB_ISSUES,
USER_PROFILE_PROJECT_SEGREGATION,
} from "constants/fetch-keys";
type Props = { type Props = {
type?: string; type?: string;
@ -74,7 +86,7 @@ export const SingleListIssue: React.FC<Props> = ({
const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null); const [contextMenuPosition, setContextMenuPosition] = useState<React.MouseEvent | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, userId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -126,6 +138,11 @@ export const SingleListIssue: React.FC<Props> = ({
.then(() => { .then(() => {
mutateIssues(); mutateIssues();
if (userId)
mutate<IUserProfileProjectSegregation>(
USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
);
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
}); });
@ -134,6 +151,7 @@ export const SingleListIssue: React.FC<Props> = ({
workspaceSlug, workspaceSlug,
cycleId, cycleId,
moduleId, moduleId,
userId,
groupTitle, groupTitle,
index, index,
selectedGroup, selectedGroup,
@ -261,7 +279,7 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}
/> />
)} )}
{properties.labels && <ViewIssueLabel issue={issue} maxRender={3} />} {properties.labels && <ViewIssueLabel labelDetails={issue.label_details} maxRender={3} />}
{properties.assignee && ( {properties.assignee && (
<ViewAssigneeSelect <ViewAssigneeSelect
issue={issue} issue={issue}

View File

@ -60,6 +60,7 @@ export const SingleList: React.FC<Props> = ({
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const isSubscribedIssues = router.pathname.includes("subscribed");
const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
@ -180,13 +181,15 @@ export const SingleList: React.FC<Props> = ({
{isArchivedIssues ? ( {isArchivedIssues ? (
"" ""
) : type === "issue" ? ( ) : type === "issue" ? (
<button !isSubscribedIssues && (
type="button" <button
className="p-1 text-custom-text-200 hover:bg-custom-background-80" type="button"
onClick={addIssueToGroup} className="p-1 text-custom-text-200 hover:bg-custom-background-80"
> onClick={addIssueToGroup}
<PlusIcon className="h-4 w-4" /> >
</button> <PlusIcon className="h-4 w-4" />
</button>
)
) : disableUserActions ? ( ) : disableUserActions ? (
"" ""
) : ( ) : (

View File

@ -323,7 +323,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
)} )}
{properties.labels && ( {properties.labels && (
<div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200"> <div className="flex items-center text-xs text-custom-text-200 text-center p-2 group-hover:bg-custom-background-80 border-custom-border-200">
<ViewIssueLabel issue={issue} maxRender={1} /> <ViewIssueLabel labelDetails={issue.label_details} maxRender={1} />
</div> </div>
)} )}

View File

@ -31,6 +31,8 @@ import {
CompletedStateIcon, CompletedStateIcon,
} from "components/icons"; } from "components/icons";
import { StarIcon } from "@heroicons/react/24/outline"; import { StarIcon } from "@heroicons/react/24/outline";
// components
import { ViewIssueLabel } from "components/issues";
// helpers // helpers
import { import {
getDateRangeStatus, getDateRangeStatus,
@ -441,7 +443,10 @@ export const ActiveCycleDetails: React.FC = () => {
issues.map((issue) => ( issues.map((issue) => (
<div <div
key={issue.id} key={issue.id}
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5" onClick={() =>
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)
}
className="flex flex-wrap cursor-pointer rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5"
> >
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div> <div>
@ -474,27 +479,7 @@ export const ActiveCycleDetails: React.FC = () => {
> >
{getPriorityIcon(issue.priority, "text-sm")} {getPriorityIcon(issue.priority, "text-sm")}
</div> </div>
{issue.label_details.length > 0 ? ( <ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
<div className="flex flex-wrap gap-1">
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border border-custom-border-200 px-2 py-0.5 text-xs text-custom-text-200"
>
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor:
label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
</span>
))}
</div>
) : (
""
)}
<div className={`flex items-center gap-2 text-custom-text-200`}> <div className={`flex items-center gap-2 text-custom-text-200`}>
{issue.assignees && {issue.assignees &&
issue.assignees.length > 0 && issue.assignees.length > 0 &&

View File

@ -23,7 +23,13 @@ const tabOptions = [
}, },
]; ];
const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorChange }) => { const EmojiIconPicker: React.FC<Props> = ({
label,
value,
onChange,
onIconColorChange,
disabled = false,
}) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [openColorPicker, setOpenColorPicker] = useState(false); const [openColorPicker, setOpenColorPicker] = useState(false);
const [activeColor, setActiveColor] = useState<string>("rgb(var(--color-text-200))"); const [activeColor, setActiveColor] = useState<string>("rgb(var(--color-text-200))");
@ -40,7 +46,11 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
return ( return (
<Popover className="relative z-[1]"> <Popover className="relative z-[1]">
<Popover.Button onClick={() => setIsOpen((prev) => !prev)} className="outline-none"> <Popover.Button
onClick={() => setIsOpen((prev) => !prev)}
className="outline-none"
disabled={disabled}
>
{label} {label}
</Popover.Button> </Popover.Button>
<Transition <Transition

View File

@ -10,4 +10,5 @@ export type Props = {
} }
) => void; ) => void;
onIconColorChange?: (data: any) => void; onIconColorChange?: (data: any) => void;
disabled?: boolean;
}; };

View File

@ -33,16 +33,16 @@ type Props = {
isOpen: boolean; isOpen: boolean;
handleClose: () => void; handleClose: () => void;
data: IIssue | null; data: IIssue | null;
onSubmit?: () => Promise<void>;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
onSubmit?: () => Promise<void>;
}; };
export const DeleteIssueModal: React.FC<Props> = ({ export const DeleteIssueModal: React.FC<Props> = ({
isOpen, isOpen,
handleClose, handleClose,
data, data,
onSubmit,
user, user,
onSubmit,
}) => { }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
@ -138,6 +138,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
console.log(error); console.log(error);
setIsDeleteLoading(false); setIsDeleteLoading(false);
}); });
if (onSubmit) await onSubmit();
}; };
const handleArchivedIssueDeletion = async () => { const handleArchivedIssueDeletion = async () => {

View File

@ -6,16 +6,16 @@ import { Tooltip } from "components/ui";
import { IIssue } from "types"; import { IIssue } from "types";
type Props = { type Props = {
issue: IIssue; labelDetails: any[];
maxRender?: number; maxRender?: number;
}; };
export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => ( export const ViewIssueLabel: React.FC<Props> = ({ labelDetails, maxRender = 1 }) => (
<> <>
{issue.label_details.length > 0 ? ( {labelDetails.length > 0 ? (
issue.label_details.length <= maxRender ? ( labelDetails.length <= maxRender ? (
<> <>
{issue.label_details.map((label, index) => ( {labelDetails.map((label) => (
<div <div
key={label.id} key={label.id}
className="flex cursor-default items-center flex-shrink-0 rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm" className="flex cursor-default items-center flex-shrink-0 rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
@ -39,11 +39,11 @@ export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
<Tooltip <Tooltip
position="top" position="top"
tooltipHeading="Labels" tooltipHeading="Labels"
tooltipContent={issue.label_details.map((l) => l.name).join(", ")} tooltipContent={labelDetails.map((l) => l.name).join(", ")}
> >
<div className="flex items-center gap-1.5 text-custom-text-200"> <div className="flex items-center gap-1.5 text-custom-text-200">
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" /> <span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" />
{`${issue.label_details.length} Labels`} {`${labelDetails.length} Labels`}
</div> </div>
</Tooltip> </Tooltip>
</div> </div>

View File

@ -93,7 +93,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
if (router.asPath.includes("my-issues")) if (router.asPath.includes("my-issues") || router.asPath.includes("assigned"))
prePopulateData = { prePopulateData = {
...prePopulateData, ...prePopulateData,
assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""], assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""],

View File

@ -123,7 +123,7 @@ export const MyIssuesViewOptions: React.FC = () => {
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
> >
View Display
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Popover.Button> </Popover.Button>

View File

@ -234,6 +234,9 @@ export const MyIssuesView: React.FC<Props> = ({
isOpen={deleteIssueModal} isOpen={deleteIssueModal}
data={issueToDelete} data={issueToDelete}
user={user} user={user}
onSubmit={async () => {
mutateMyIssues();
}}
/> />
{areFiltersApplied && ( {areFiltersApplied && (
<> <>

View File

@ -149,7 +149,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
> >
View Display
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Popover.Button> </Popover.Button>

View File

@ -248,6 +248,9 @@ export const ProfileIssuesView = () => {
isOpen={deleteIssueModal} isOpen={deleteIssueModal}
data={issueToDelete} data={issueToDelete}
user={user} user={user}
onSubmit={async () => {
mutateProfileIssues();
}}
/> />
{areFiltersApplied && ( {areFiltersApplied && (
<> <>

View File

@ -6,78 +6,88 @@ import { Loader, Tooltip } from "components/ui";
import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { InformationCircleIcon } from "@heroicons/react/24/outline";
// types // types
import { IUserWorkspaceDashboard } from "types"; import { IUserWorkspaceDashboard } from "types";
import { useRouter } from "next/router";
type Props = { type Props = {
data: IUserWorkspaceDashboard | undefined; data: IUserWorkspaceDashboard | undefined;
}; };
export const IssuesStats: React.FC<Props> = ({ data }) => ( export const IssuesStats: React.FC<Props> = ({ data }) => {
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3"> const router = useRouter();
<div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0"> const { workspaceSlug } = router.query;
<div className="flex"> return (
<div className="basis-1/2 p-4"> <div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
<h4 className="text-sm">Issues assigned to you</h4> <div className="grid grid-cols-1 divide-y divide-custom-border-200 border-b border-custom-border-200 lg:border-r lg:border-b-0">
<h5 className="mt-2 text-2xl font-semibold"> <div className="flex">
{data ? ( <div className="basis-1/2 p-4">
data.assigned_issues_count <h4 className="text-sm">Issues assigned to you</h4>
) : ( <h5 className="mt-2 text-2xl font-semibold">
<Loader> {data ? (
<Loader.Item height="25px" width="50%" /> <div
</Loader> className="cursor-pointer"
)} onClick={() => router.push(`/${workspaceSlug}/me/my-issues`)}
</h5> >
{data.assigned_issues_count}
</div>
) : (
<Loader>
<Loader.Item height="25px" width="50%" />
</Loader>
)}
</h5>
</div>
<div className="basis-1/2 border-l border-custom-border-200 p-4">
<h4 className="text-sm">Pending issues</h4>
<h5 className="mt-2 text-2xl font-semibold">
{data ? (
data.pending_issues_count
) : (
<Loader>
<Loader.Item height="25px" width="50%" />
</Loader>
)}
</h5>
</div>
</div> </div>
<div className="basis-1/2 border-l border-custom-border-200 p-4"> <div className="flex">
<h4 className="text-sm">Pending issues</h4> <div className="basis-1/2 p-4">
<h5 className="mt-2 text-2xl font-semibold"> <h4 className="text-sm">Completed issues</h4>
{data ? ( <h5 className="mt-2 text-2xl font-semibold">
data.pending_issues_count {data ? (
) : ( data.completed_issues_count
<Loader> ) : (
<Loader.Item height="25px" width="50%" /> <Loader>
</Loader> <Loader.Item height="25px" width="50%" />
)} </Loader>
</h5> )}
</h5>
</div>
<div className="basis-1/2 border-l border-custom-border-200 p-4">
<h4 className="text-sm">Issues due by this week</h4>
<h5 className="mt-2 text-2xl font-semibold">
{data ? (
data.issues_due_week_count
) : (
<Loader>
<Loader.Item height="25px" width="50%" />
</Loader>
)}
</h5>
</div>
</div> </div>
</div> </div>
<div className="flex"> <div className="p-4 lg:col-span-2">
<div className="basis-1/2 p-4"> <h3 className="mb-2 font-semibold capitalize flex items-center gap-2">
<h4 className="text-sm">Completed issues</h4> Activity Graph
<h5 className="mt-2 text-2xl font-semibold"> <Tooltip
{data ? ( tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
data.completed_issues_count className="w-72 border border-custom-border-200"
) : ( >
<Loader> <InformationCircleIcon className="h-3 w-3" />
<Loader.Item height="25px" width="50%" /> </Tooltip>
</Loader> </h3>
)} <ActivityGraph activities={data?.issue_activities} />
</h5>
</div>
<div className="basis-1/2 border-l border-custom-border-200 p-4">
<h4 className="text-sm">Issues due by this week</h4>
<h5 className="mt-2 text-2xl font-semibold">
{data ? (
data.issues_due_week_count
) : (
<Loader>
<Loader.Item height="25px" width="50%" />
</Loader>
)}
</h5>
</div>
</div> </div>
</div> </div>
<div className="p-4 lg:col-span-2"> );
<h3 className="mb-2 font-semibold capitalize flex items-center gap-2"> };
Activity Graph
<Tooltip
tooltipContent="Your profile activity graph is a record of actions you've performed on issues across the workspace."
className="w-72 border border-custom-border-200"
>
<InformationCircleIcon className="h-3 w-3" />
</Tooltip>
</h3>
<ActivityGraph activities={data?.issue_activities} />
</div>
</div>
);

View File

@ -476,7 +476,7 @@ const SinglePage: NextPage = () => {
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
> >
View Display
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Popover.Button> </Popover.Button>

View File

@ -187,7 +187,7 @@ const GeneralSettings: NextPage = () => {
/> />
<form onSubmit={handleSubmit(onSubmit)} className="p-8"> <form onSubmit={handleSubmit(onSubmit)} className="p-8">
<SettingsHeader /> <SettingsHeader />
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12 opacity-60">
<div className="grid grid-cols-12 items-start gap-4 sm:gap-16"> <div className="grid grid-cols-12 items-start gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Icon & Name</h4> <h4 className="text-lg font-semibold">Icon & Name</h4>
@ -206,6 +206,7 @@ const GeneralSettings: NextPage = () => {
label={value ? renderEmoji(value) : "Icon"} label={value ? renderEmoji(value) : "Icon"}
value={value} value={value}
onChange={onChange} onChange={onChange}
disabled={!isAdmin}
/> />
)} )}
/> />
@ -225,6 +226,7 @@ const GeneralSettings: NextPage = () => {
validations={{ validations={{
required: "Name is required", required: "Name is required",
}} }}
disabled={!isAdmin}
/> />
) : ( ) : (
<Loader> <Loader>
@ -248,6 +250,7 @@ const GeneralSettings: NextPage = () => {
placeholder="Enter project description" placeholder="Enter project description"
validations={{}} validations={{}}
className="min-h-[46px] text-sm" className="min-h-[46px] text-sm"
disabled={!isAdmin}
/> />
) : ( ) : (
<Loader className="w-full"> <Loader className="w-full">
@ -279,6 +282,7 @@ const GeneralSettings: NextPage = () => {
setValue("cover_image", imageUrl); setValue("cover_image", imageUrl);
}} }}
value={watch("cover_image")} value={watch("cover_image")}
disabled={!isAdmin}
/> />
</div> </div>
</div> </div>
@ -319,6 +323,7 @@ const GeneralSettings: NextPage = () => {
message: "Identifier must at most be of 5 characters", message: "Identifier must at most be of 5 characters",
}, },
}} }}
disabled={!isAdmin}
/> />
) : ( ) : (
<Loader> <Loader>
@ -343,6 +348,7 @@ const GeneralSettings: NextPage = () => {
onChange={onChange} onChange={onChange}
label={currentNetwork?.label ?? "Select network"} label={currentNetwork?.label ?? "Select network"}
input input
disabled={!isAdmin}
> >
{NETWORK_CHOICES.map((network) => ( {NETWORK_CHOICES.map((network) => (
<CustomSelect.Option key={network.key} value={network.key}> <CustomSelect.Option key={network.key} value={network.key}>
@ -359,44 +365,48 @@ const GeneralSettings: NextPage = () => {
)} )}
</div> </div>
</div> </div>
<div className="sm:text-right">
{projectDetails ? ( {isAdmin && (
<SecondaryButton type="submit" loading={isSubmitting} disabled={!isAdmin}> <>
{isSubmitting ? "Updating Project..." : "Update Project"} <div className="sm:text-right">
</SecondaryButton>
) : (
<Loader className="mt-2 w-full">
<Loader.Item height="34px" width="100px" />
</Loader>
)}
</div>
{memberDetails?.role === 20 && (
<div className="grid grid-cols-12 gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Danger Zone</h4>
<p className="text-sm text-custom-text-200">
The danger zone of the project delete page is a critical area that requires
careful consideration and attention. When deleting a project, all of the data and
resources within that project will be permanently removed and cannot be recovered.
</p>
</div>
<div className="col-span-12 sm:col-span-6">
{projectDetails ? ( {projectDetails ? (
<div> <SecondaryButton type="submit" loading={isSubmitting} disabled={!isAdmin}>
<DangerButton {isSubmitting ? "Updating Project..." : "Update Project"}
onClick={() => setSelectedProject(projectDetails.id ?? null)} </SecondaryButton>
outline
>
Delete Project
</DangerButton>
</div>
) : ( ) : (
<Loader className="mt-2 w-full"> <Loader className="mt-2 w-full">
<Loader.Item height="46px" width="100px" /> <Loader.Item height="34px" width="100px" />
</Loader> </Loader>
)} )}
</div> </div>
</div> <div className="grid grid-cols-12 gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Danger Zone</h4>
<p className="text-sm text-custom-text-200">
The danger zone of the project delete page is a critical area that requires
careful consideration and attention. When deleting a project, all of the data
and resources within that project will be permanently removed and cannot be
recovered.
</p>
</div>
<div className="col-span-12 sm:col-span-6">
{projectDetails ? (
<div>
<DangerButton
onClick={() => setSelectedProject(projectDetails.id ?? null)}
outline
>
Delete Project
</DangerButton>
</div>
) : (
<Loader className="mt-2 w-full">
<Loader.Item height="46px" width="100px" />
</Loader>
)}
</div>
</div>
</>
)} )}
</div> </div>
</form> </form>

View File

@ -181,7 +181,7 @@ const WorkspaceSettings: NextPage = () => {
<div className="p-8"> <div className="p-8">
<SettingsHeader /> <SettingsHeader />
{activeWorkspace ? ( {activeWorkspace ? (
<div className="space-y-8 sm:space-y-12"> <div className="space-y-8 sm:space-y-12 opacity-60">
<div className="grid grid-cols-12 gap-4 sm:gap-16"> <div className="grid grid-cols-12 gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Logo</h4> <h4 className="text-lg font-semibold">Logo</h4>
@ -191,7 +191,11 @@ const WorkspaceSettings: NextPage = () => {
</div> </div>
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}> <button
type="button"
onClick={() => setIsImageUploadModalOpen(true)}
disabled={!isAdmin}
>
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? ( {watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
<div className="relative mx-auto flex h-12 w-12"> <div className="relative mx-auto flex h-12 w-12">
<img <img
@ -206,23 +210,25 @@ const WorkspaceSettings: NextPage = () => {
</div> </div>
)} )}
</button> </button>
<div className="flex gap-4"> {isAdmin && (
<SecondaryButton <div className="flex gap-4">
onClick={() => { <SecondaryButton
setIsImageUploadModalOpen(true); onClick={() => {
}} setIsImageUploadModalOpen(true);
> }}
{isImageUploading ? "Uploading..." : "Upload"}
</SecondaryButton>
{activeWorkspace.logo && activeWorkspace.logo !== "" && (
<DangerButton
onClick={() => handleDelete(activeWorkspace.logo)}
loading={isImageRemoving}
> >
{isImageRemoving ? "Removing..." : "Remove"} {isImageUploading ? "Uploading..." : "Upload"}
</DangerButton> </SecondaryButton>
)} {activeWorkspace.logo && activeWorkspace.logo !== "" && (
</div> <DangerButton
onClick={() => handleDelete(activeWorkspace.logo)}
loading={isImageRemoving}
>
{isImageRemoving ? "Removing..." : "Remove"}
</DangerButton>
)}
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@ -288,6 +294,7 @@ const WorkspaceSettings: NextPage = () => {
message: "Workspace name should not exceed 80 characters", message: "Workspace name should not exceed 80 characters",
}, },
}} }}
disabled={!isAdmin}
/> />
</div> </div>
</div> </div>
@ -309,6 +316,7 @@ const WorkspaceSettings: NextPage = () => {
} }
width="w-full" width="w-full"
input input
disabled={!isAdmin}
> >
{ORGANIZATION_SIZE?.map((item) => ( {ORGANIZATION_SIZE?.map((item) => (
<CustomSelect.Option key={item} value={item}> <CustomSelect.Option key={item} value={item}>
@ -320,32 +328,35 @@ const WorkspaceSettings: NextPage = () => {
/> />
</div> </div>
</div> </div>
<div className="sm:text-right">
<SecondaryButton {isAdmin && (
onClick={handleSubmit(onSubmit)} <>
loading={isSubmitting} <div className="sm:text-right">
disabled={!isAdmin} <SecondaryButton
> onClick={handleSubmit(onSubmit)}
{isSubmitting ? "Updating..." : "Update Workspace"} loading={isSubmitting}
</SecondaryButton> disabled={!isAdmin}
</div> >
{memberDetails?.role === 20 && ( {isSubmitting ? "Updating..." : "Update Workspace"}
<div className="grid grid-cols-12 gap-4 sm:gap-16"> </SecondaryButton>
<div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Danger Zone</h4>
<p className="text-sm text-custom-text-200">
The danger zone of the workspace delete page is a critical area that requires
careful consideration and attention. When deleting a workspace, all of the data
and resources within that workspace will be permanently removed and cannot be
recovered.
</p>
</div> </div>
<div className="col-span-12 sm:col-span-6"> <div className="grid grid-cols-12 gap-4 sm:gap-16">
<DangerButton onClick={() => setIsOpen(true)} outline> <div className="col-span-12 sm:col-span-6">
Delete the workspace <h4 className="text-lg font-semibold">Danger Zone</h4>
</DangerButton> <p className="text-sm text-custom-text-200">
The danger zone of the workspace delete page is a critical area that requires
careful consideration and attention. When deleting a workspace, all of the
data and resources within that workspace will be permanently removed and
cannot be recovered.
</p>
</div>
<div className="col-span-12 sm:col-span-6">
<DangerButton onClick={() => setIsOpen(true)} outline>
Delete the workspace
</DangerButton>
</div>
</div> </div>
</div> </>
)} )}
</div> </div>
) : ( ) : (