chore: UI/UX improvements (#3319)

* chore: add proper message for cycle/ module having start & end date but isn't active yet.

* fix: infinite loader after updating workspace settings.

* fix: user profile icon dropdown doesn't closes automatically.

* style: fix inconsistent padding in cycle empty state.

* chore: remove multiple `empty state` in labels settings and improve add label logic.

* style: fix inconsistent padding in project label, integration and estimates empty state.

* style: fix integrations settings breadcrumb title.

* style: add proper `disabled` styles for email field in profile settings.

* style: fix cycle layout height.
This commit is contained in:
Prateek Shourya 2024-01-05 14:13:04 +05:30 committed by GitHub
parent d98b688342
commit 9dd8c8ba14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 48 additions and 55 deletions

View File

@ -43,7 +43,7 @@ export const NewEmptyState: React.FC<Props> = ({
return ( return (
<div className="flex items-center justify-center overflow-y-auto"> <div className="flex items-center justify-center overflow-y-auto">
<div className=" flex h-full w-full flex-col items-center justify-center "> <div className=" flex h-full w-full flex-col items-center justify-center ">
<div className="m-5 flex max-w-6xl flex-col gap-5 rounded-xl border border-custom-border-200 px-10 py-7 shadow-sm md:m-16"> <div className="m-5 flex max-w-6xl flex-col gap-5 rounded-xl border border-custom-border-200 px-10 py-7 shadow-sm md:m-8">
<h3 className="text-2xl font-semibold">{title}</h3> <h3 className="text-2xl font-semibold">{title}</h3>
{description && <p className=" text-lg">{description}</p>} {description && <p className=" text-lg">{description}</p>}
<div className="relative w-full max-w-6xl"> <div className="relative w-full max-w-6xl">

View File

@ -539,7 +539,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<AlertCircle height={14} width={14} className="text-custom-text-200" /> <AlertCircle height={14} width={14} className="text-custom-text-200" />
<span className="text-xs italic text-custom-text-200"> <span className="text-xs italic text-custom-text-200">
Invalid date. Please enter valid date. {cycleDetails?.start_date && cycleDetails?.end_date
? "This cycle isn't active yet."
: "Invalid date. Please enter valid date."}
</span> </span>
</div> </div>
)} )}

View File

@ -102,7 +102,7 @@ export const EstimatesList: React.FC = observer(() => {
))} ))}
</section> </section>
) : ( ) : (
<div className="h-full w-full overflow-y-auto"> <div className="w-full py-8">
<EmptyState <EmptyState
title="No estimates yet" title="No estimates yet"
description="Estimates help you communicate the complexity of an issue." description="Estimates help you communicate the complexity of an issue."

View File

@ -96,9 +96,9 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
Add label Add label
</Button> </Button>
</div> </div>
<div className="w-full"> <div className="w-full py-8">
{showLabelForm && ( {showLabelForm && (
<div className="w-full rounded border border-custom-border-200 px-3.5 py-2"> <div className="w-full rounded border border-custom-border-200 px-3.5 py-2 my-2">
<CreateUpdateLabelInline <CreateUpdateLabelInline
labelForm={showLabelForm} labelForm={showLabelForm}
setLabelForm={setLabelForm} setLabelForm={setLabelForm}
@ -112,7 +112,7 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
</div> </div>
)} )}
{projectLabels ? ( {projectLabels ? (
projectLabels.length === 0 ? ( projectLabels.length === 0 && !showLabelForm ? (
<EmptyState <EmptyState
title="No labels yet" title="No labels yet"
description="Create labels to help organize and filter issues in you project" description="Create labels to help organize and filter issues in you project"
@ -203,25 +203,14 @@ export const ProjectSettingsLabelList: React.FC = observer(() => {
) )
) )
) : ( ) : (
!showLabelForm && (
<Loader className="space-y-5"> <Loader className="space-y-5">
<Loader.Item height="42px" /> <Loader.Item height="42px" />
<Loader.Item height="42px" /> <Loader.Item height="42px" />
<Loader.Item height="42px" /> <Loader.Item height="42px" />
<Loader.Item height="42px" /> <Loader.Item height="42px" />
</Loader> </Loader>
)} )
{/* empty state */}
{projectLabels && projectLabels.length === 0 && (
<EmptyState
title="No labels yet"
description="Create labels to help organize and filter issues in your project."
image={emptyLabel}
primaryButton={{
text: "Add label",
onClick: () => newLabel(),
}}
/>
)} )}
</div> </div>
</> </>

View File

@ -550,7 +550,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<AlertCircle height={14} width={14} className="text-custom-text-200" /> <AlertCircle height={14} width={14} className="text-custom-text-200" />
<span className="text-xs italic text-custom-text-200"> <span className="text-xs italic text-custom-text-200">
Invalid date. Please enter valid date. {moduleDetails?.start_date && moduleDetails?.target_date
? "This module isn't active yet."
: "Invalid date. Please enter valid date."}
</span> </span>
</div> </div>
)} )}

View File

@ -278,14 +278,14 @@ export const WorkspaceSidebarDropdown = observer(() => {
<div className="flex flex-col gap-2.5 pb-2"> <div className="flex flex-col gap-2.5 pb-2">
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span> <span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
{profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( {profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Menu.Item key={index} as="button" type="button"> <Link key={index} href={link.link}>
<Link href={link.link}> <Menu.Item key={index} as="div">
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"> <span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
<link.icon className="h-4 w-4 stroke-[1.5]" /> <link.icon className="h-4 w-4 stroke-[1.5]" />
{link.name} {link.name}
</span> </span>
</Link>
</Menu.Item> </Menu.Item>
</Link>
))} ))}
</div> </div>
<div className={`pt-2 ${isUserInstanceAdmin ? "pb-2" : ""}`}> <div className={`pt-2 ${isUserInstanceAdmin ? "pb-2" : ""}`}>
@ -301,13 +301,13 @@ export const WorkspaceSidebarDropdown = observer(() => {
</div> </div>
{isUserInstanceAdmin && ( {isUserInstanceAdmin && (
<div className="p-2 pb-0"> <div className="p-2 pb-0">
<Menu.Item as="button" type="button" className="w-full">
<Link href="/god-mode"> <Link href="/god-mode">
<Menu.Item as="button" type="button" className="w-full">
<span className="flex w-full items-center justify-center rounded bg-custom-primary-100/20 px-2 py-1 text-sm font-medium text-custom-primary-100 hover:bg-custom-primary-100/30 hover:text-custom-primary-200"> <span className="flex w-full items-center justify-center rounded bg-custom-primary-100/20 px-2 py-1 text-sm font-medium text-custom-primary-100 hover:bg-custom-primary-100/30 hover:text-custom-primary-200">
Enter God Mode Enter God Mode
</span> </span>
</Link>
</Menu.Item> </Menu.Item>
</Link>
</div> </div>
)} )}
</Menu.Items> </Menu.Items>

View File

@ -59,7 +59,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
if (!workspaceSlug || !projectId) return null; if (!workspaceSlug || !projectId) return null;
return ( return (
<> <div className="w-full h-full">
<CycleCreateUpdateModal <CycleCreateUpdateModal
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()} projectId={projectId.toString()}
@ -67,7 +67,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
handleClose={() => setCreateModal(false)} handleClose={() => setCreateModal(false)}
/> />
{totalCycles === 0 ? ( {totalCycles === 0 ? (
<div className="grid h-full place-items-center"> <div className="h-full place-items-center">
<NewEmptyState <NewEmptyState
title="Group and timebox your work in Cycles." title="Group and timebox your work in Cycles."
description="Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team." description="Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team."
@ -194,7 +194,7 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>
)} )}
</> </div>
); );
}); });

View File

@ -54,6 +54,7 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => {
))} ))}
</div> </div>
) : ( ) : (
<div className="w-full py-8">
<EmptyState <EmptyState
title="You haven't configured integrations" title="You haven't configured integrations"
description="Configure GitHub and other integrations to sync your project issues." description="Configure GitHub and other integrations to sync your project issues."
@ -64,6 +65,7 @@ const ProjectIntegrationsPage: NextPageWithLayout = () => {
}} }}
disabled={!isAdmin} disabled={!isAdmin}
/> />
</div>
) )
) : ( ) : (
<Loader className="space-y-5"> <Loader className="space-y-5">

View File

@ -65,7 +65,7 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => {
WorkspaceIntegrationsPage.getLayout = function getLayout(page: ReactElement) { WorkspaceIntegrationsPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<WorkspaceSettingHeader title="Export Settings" />}> <AppLayout header={<WorkspaceSettingHeader title="Integrations Settings" />}>
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout> <WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
</AppLayout> </AppLayout>
); );

View File

@ -218,7 +218,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
<div className="grid grid-cols-1 gap-6 px-8 lg:grid-cols-2 2xl:grid-cols-3"> <div className="grid grid-cols-1 gap-6 px-8 lg:grid-cols-2 2xl:grid-cols-3">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-sm"> <h4 className="text-sm">
First name <span className="text-red-500">*</span> First name<span className="text-red-500">*</span>
</h4> </h4>
<Controller <Controller
control={control} control={control}
@ -275,17 +275,16 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => {
rules={{ rules={{
required: "Email is required.", required: "Email is required.",
}} }}
render={({ field: { value, onChange, ref } }) => ( render={({ field: { value, ref } }) => (
<Input <Input
id="email" id="email"
name="email" name="email"
type="email" type="email"
value={value} value={value}
onChange={onChange}
ref={ref} ref={ref}
hasError={Boolean(errors.email)} hasError={Boolean(errors.email)}
placeholder="Enter your email" placeholder="Enter your email"
className={`w-full rounded-md ${errors.email ? "border-red-500" : ""}`} className={`w-full rounded-md cursor-not-allowed !bg-custom-background-80 ${errors.email ? "border-red-500" : ""}`}
disabled disabled
/> />
)} )}

View File

@ -14,10 +14,9 @@ import {
IWorkspaceBulkInviteFormData, IWorkspaceBulkInviteFormData,
IWorkspaceViewProps, IWorkspaceViewProps,
IUserProjectsRole, IUserProjectsRole,
TIssueMap,
TIssue, TIssue,
IWorkspaceView,
} from "@plane/types"; } from "@plane/types";
import { IWorkspaceView } from "@plane/types";
export class WorkspaceService extends APIService { export class WorkspaceService extends APIService {
constructor() { constructor() {

View File

@ -135,7 +135,7 @@ export class WorkspaceRootStore implements IWorkspaceRootStore {
updateWorkspace = async (workspaceSlug: string, data: Partial<IWorkspace>) => updateWorkspace = async (workspaceSlug: string, data: Partial<IWorkspace>) =>
await this.workspaceService.updateWorkspace(workspaceSlug, data).then((response) => { await this.workspaceService.updateWorkspace(workspaceSlug, data).then((response) => {
runInAction(() => { runInAction(() => {
set(this.workspaces, response.id, data); set(this.workspaces, response.id, response);
}); });
return response; return response;
}); });