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 left" },
{ 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
return (
<>

View File

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

View File

@ -40,9 +40,15 @@ type Props = {
label: string | React.ReactNode;
value: string | null;
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 router = useRouter();
@ -117,6 +123,7 @@ export const ImagePickerPopover: React.FC<Props> = ({ label, value, onChange })
<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"
onClick={() => setIsOpen((prev) => !prev)}
disabled={disabled}
>
{label}
</Popover.Button>

View File

@ -29,6 +29,7 @@ type Props = {
isCollapsed: boolean;
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
disableUserActions: boolean;
disableAddIssue: boolean;
viewProps: IIssueViewProps;
};
@ -39,6 +40,7 @@ export const BoardHeader: React.FC<Props> = ({
isCollapsed,
setIsCollapsed,
disableUserActions,
disableAddIssue,
viewProps,
}) => {
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" />
)}
</button>
{!disableUserActions && selectedGroup !== "created_by" && (
{!disableAddIssue && !disableUserActions && selectedGroup !== "created_by" && (
<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"

View File

@ -53,6 +53,8 @@ export const SingleBoard: React.FC<Props> = ({
const router = useRouter();
const { cycleId, moduleId } = router.query;
const isSubscribedIssues = router.pathname.includes("subscribed");
const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
// 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}
setIsCollapsed={setIsCollapsed}
disableUserActions={disableUserActions}
disableAddIssue={isSubscribedIssues}
viewProps={viewProps}
/>
{isCollapsed && (
@ -150,7 +153,8 @@ export const SingleBoard: React.FC<Props> = ({
</div>
{selectedGroup !== "created_by" && (
<div>
{type === "issue" ? (
{type === "issue"
? !isSubscribedIssues && (
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
@ -159,8 +163,8 @@ export const SingleBoard: React.FC<Props> = ({
<PlusIcon className="h-4 w-4" />
Add Issue
</button>
) : (
!disableUserActions && (
)
: !disableUserActions && (
<CustomMenu
customButton={
<button
@ -183,7 +187,6 @@ export const SingleBoard: React.FC<Props> = ({
</CustomMenu.MenuItem>
)}
</CustomMenu>
)
)}
</div>
)}

View File

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

View File

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

View File

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

View File

@ -323,7 +323,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
)}
{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">
<ViewIssueLabel issue={issue} maxRender={1} />
<ViewIssueLabel labelDetails={issue.label_details} maxRender={1} />
</div>
)}

View File

@ -31,6 +31,8 @@ import {
CompletedStateIcon,
} from "components/icons";
import { StarIcon } from "@heroicons/react/24/outline";
// components
import { ViewIssueLabel } from "components/issues";
// helpers
import {
getDateRangeStatus,
@ -441,7 +443,10 @@ export const ActiveCycleDetails: React.FC = () => {
issues.map((issue) => (
<div
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>
@ -474,27 +479,7 @@ export const ActiveCycleDetails: React.FC = () => {
>
{getPriorityIcon(issue.priority, "text-sm")}
</div>
{issue.label_details.length > 0 ? (
<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>
) : (
""
)}
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
<div className={`flex items-center gap-2 text-custom-text-200`}>
{issue.assignees &&
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 [openColorPicker, setOpenColorPicker] = useState(false);
const [activeColor, setActiveColor] = useState<string>("rgb(var(--color-text-200))");
@ -40,7 +46,11 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange, onIconColorC
return (
<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}
</Popover.Button>
<Transition

View File

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

View File

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

View File

@ -6,16 +6,16 @@ import { Tooltip } from "components/ui";
import { IIssue } from "types";
type Props = {
issue: IIssue;
labelDetails: any[];
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 ? (
issue.label_details.length <= maxRender ? (
{labelDetails.length > 0 ? (
labelDetails.length <= maxRender ? (
<>
{issue.label_details.map((label, index) => (
{labelDetails.map((label) => (
<div
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"
@ -39,11 +39,11 @@ export const ViewIssueLabel: React.FC<Props> = ({ issue, maxRender = 1 }) => (
<Tooltip
position="top"
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">
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-custom-primary" />
{`${issue.label_details.length} Labels`}
{`${labelDetails.length} Labels`}
</div>
</Tooltip>
</div>

View File

@ -93,7 +93,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId 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,
assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""],

View File

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

View File

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

View File

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

View File

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

View File

@ -6,12 +6,16 @@ import { Loader, Tooltip } from "components/ui";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
// types
import { IUserWorkspaceDashboard } from "types";
import { useRouter } from "next/router";
type Props = {
data: IUserWorkspaceDashboard | undefined;
};
export const IssuesStats: React.FC<Props> = ({ data }) => (
export const IssuesStats: React.FC<Props> = ({ data }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<div className="grid grid-cols-1 rounded-[10px] border border-custom-border-200 bg-custom-background-100 lg:grid-cols-3">
<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">
<div className="flex">
@ -19,7 +23,12 @@ export const IssuesStats: React.FC<Props> = ({ data }) => (
<h4 className="text-sm">Issues assigned to you</h4>
<h5 className="mt-2 text-2xl font-semibold">
{data ? (
data.assigned_issues_count
<div
className="cursor-pointer"
onClick={() => router.push(`/${workspaceSlug}/me/my-issues`)}
>
{data.assigned_issues_count}
</div>
) : (
<Loader>
<Loader.Item height="25px" width="50%" />
@ -81,3 +90,4 @@ export const IssuesStats: React.FC<Props> = ({ data }) => (
</div>
</div>
);
};

View File

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

View File

@ -187,7 +187,7 @@ const GeneralSettings: NextPage = () => {
/>
<form onSubmit={handleSubmit(onSubmit)} className="p-8">
<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="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Icon & Name</h4>
@ -206,6 +206,7 @@ const GeneralSettings: NextPage = () => {
label={value ? renderEmoji(value) : "Icon"}
value={value}
onChange={onChange}
disabled={!isAdmin}
/>
)}
/>
@ -225,6 +226,7 @@ const GeneralSettings: NextPage = () => {
validations={{
required: "Name is required",
}}
disabled={!isAdmin}
/>
) : (
<Loader>
@ -248,6 +250,7 @@ const GeneralSettings: NextPage = () => {
placeholder="Enter project description"
validations={{}}
className="min-h-[46px] text-sm"
disabled={!isAdmin}
/>
) : (
<Loader className="w-full">
@ -279,6 +282,7 @@ const GeneralSettings: NextPage = () => {
setValue("cover_image", imageUrl);
}}
value={watch("cover_image")}
disabled={!isAdmin}
/>
</div>
</div>
@ -319,6 +323,7 @@ const GeneralSettings: NextPage = () => {
message: "Identifier must at most be of 5 characters",
},
}}
disabled={!isAdmin}
/>
) : (
<Loader>
@ -343,6 +348,7 @@ const GeneralSettings: NextPage = () => {
onChange={onChange}
label={currentNetwork?.label ?? "Select network"}
input
disabled={!isAdmin}
>
{NETWORK_CHOICES.map((network) => (
<CustomSelect.Option key={network.key} value={network.key}>
@ -359,6 +365,9 @@ const GeneralSettings: NextPage = () => {
)}
</div>
</div>
{isAdmin && (
<>
<div className="sm:text-right">
{projectDetails ? (
<SecondaryButton type="submit" loading={isSubmitting} disabled={!isAdmin}>
@ -370,14 +379,14 @@ const GeneralSettings: NextPage = () => {
</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.
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">
@ -397,6 +406,7 @@ const GeneralSettings: NextPage = () => {
)}
</div>
</div>
</>
)}
</div>
</form>

View File

@ -181,7 +181,7 @@ const WorkspaceSettings: NextPage = () => {
<div className="p-8">
<SettingsHeader />
{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="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold">Logo</h4>
@ -191,7 +191,11 @@ const WorkspaceSettings: NextPage = () => {
</div>
<div className="col-span-12 sm:col-span-6">
<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") !== "" ? (
<div className="relative mx-auto flex h-12 w-12">
<img
@ -206,6 +210,7 @@ const WorkspaceSettings: NextPage = () => {
</div>
)}
</button>
{isAdmin && (
<div className="flex gap-4">
<SecondaryButton
onClick={() => {
@ -223,6 +228,7 @@ const WorkspaceSettings: NextPage = () => {
</DangerButton>
)}
</div>
)}
</div>
</div>
</div>
@ -288,6 +294,7 @@ const WorkspaceSettings: NextPage = () => {
message: "Workspace name should not exceed 80 characters",
},
}}
disabled={!isAdmin}
/>
</div>
</div>
@ -309,6 +316,7 @@ const WorkspaceSettings: NextPage = () => {
}
width="w-full"
input
disabled={!isAdmin}
>
{ORGANIZATION_SIZE?.map((item) => (
<CustomSelect.Option key={item} value={item}>
@ -320,6 +328,9 @@ const WorkspaceSettings: NextPage = () => {
/>
</div>
</div>
{isAdmin && (
<>
<div className="sm:text-right">
<SecondaryButton
onClick={handleSubmit(onSubmit)}
@ -329,15 +340,14 @@ const WorkspaceSettings: NextPage = () => {
{isSubmitting ? "Updating..." : "Update Workspace"}
</SecondaryButton>
</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 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.
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">
@ -346,6 +356,7 @@ const WorkspaceSettings: NextPage = () => {
</DangerButton>
</div>
</div>
</>
)}
</div>
) : (