mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Fix: bug fixes and UI / UX improvements (#2906)
* Fix: issue with project publish modal data not updating immediately. * fix: issue with workspace list not scrollable in profile settings. * fix: update redirect workspace slug logic to redirect to prev workspace instead of `/`. * style: update API tokens and webhooks empty state designs.
This commit is contained in:
parent
d84e043c93
commit
c4fb543372
@ -14,7 +14,7 @@ export const ApiTokenEmptyState: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full`}
|
||||
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full lg:w-3/4`}
|
||||
>
|
||||
<div className="text-center flex flex-col items-center w-full">
|
||||
<Image src={emptyApiTokens} className="w-52 sm:w-60" alt="empty" />
|
||||
|
@ -119,7 +119,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
||||
|
||||
reset({ ...updatedData });
|
||||
}
|
||||
}, [reset, projectPublishStore.projectPublishSettings]);
|
||||
}, [reset, projectPublishStore.projectPublishSettings, isOpen]);
|
||||
|
||||
// fetch publish settings
|
||||
useEffect(() => {
|
||||
|
@ -11,7 +11,7 @@ export const WebhooksEmptyState = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full`}
|
||||
className={`flex items-center justify-center mx-auto rounded-sm border border-custom-border-200 bg-custom-background-90 py-10 px-16 w-full lg:w-3/4`}
|
||||
>
|
||||
<div className="text-center flex flex-col items-center w-full">
|
||||
<Image src={EmptyWebhook} className="w-52 sm:w-60" alt="empty" />
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { mutate } from "swr";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// icons
|
||||
import { LogIn, LogOut, MoveLeft, Plus, User, UserPlus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { Avatar, Tooltip } from "@plane/ui";
|
||||
import { Fragment } from "react";
|
||||
import { mutate } from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
const SIDEBAR_LINKS = [
|
||||
{
|
||||
@ -28,6 +30,11 @@ const SIDEBAR_LINKS = [
|
||||
];
|
||||
|
||||
export const ProfileLayoutSidebar = observer(() => {
|
||||
// states
|
||||
const [isScrolled, setIsScrolled] = useState(false); // scroll animation state
|
||||
// refs
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { setTheme } = useTheme();
|
||||
@ -62,6 +69,27 @@ export const ProfileLayoutSidebar = observer(() => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementing scroll animation styles based on the scroll length of the container
|
||||
*/
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (containerRef.current) {
|
||||
const scrollTop = containerRef.current.scrollTop;
|
||||
setIsScrolled(scrollTop > 0);
|
||||
}
|
||||
};
|
||||
const currentContainerRef = containerRef.current;
|
||||
if (currentContainerRef) {
|
||||
currentContainerRef.addEventListener("scroll", handleScroll);
|
||||
}
|
||||
return () => {
|
||||
if (currentContainerRef) {
|
||||
currentContainerRef.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed md:relative inset-y-0 flex flex-col bg-custom-sidebar-background-100 h-full flex-shrink-0 flex-grow-0 border-r border-custom-sidebar-border-200 z-20 duration-300 ${
|
||||
@ -166,11 +194,16 @@ export const ProfileLayoutSidebar = observer(() => {
|
||||
))}
|
||||
</div>
|
||||
{workspaces && workspaces.length > 0 && (
|
||||
<div className="flex flex-col px-4 flex-shrink-0">
|
||||
<div className="flex flex-col h-full overflow-x-hidden px-4">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="rounded text-custom-sidebar-text-400 px-1.5 text-sm font-semibold">Your workspaces</div>
|
||||
)}
|
||||
<div className="space-y-2 mt-2">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`space-y-2 mt-2 pt-2 h-full overflow-y-auto ${
|
||||
isScrolled ? "border-t border-custom-sidebar-border-300" : ""
|
||||
}`}
|
||||
>
|
||||
{workspaces.map((workspace) => (
|
||||
<Link
|
||||
key={workspace.id}
|
||||
@ -208,7 +241,7 @@ export const ProfileLayoutSidebar = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-grow flex items-end px-4 py-2">
|
||||
<div className="flex-grow flex items-end px-4 py-2 border-t border-custom-border-200">
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none md:hidden"
|
||||
|
@ -48,25 +48,27 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
|
||||
<>
|
||||
<CreateApiTokenModal isOpen={isCreateTokenModalOpen} onClose={() => setIsCreateTokenModalOpen(false)} />
|
||||
{tokens ? (
|
||||
tokens.length > 0 ? (
|
||||
<section className="pr-9 py-8 w-full overflow-y-auto">
|
||||
<div className="flex items-center justify-between py-3.5 border-b border-custom-border-200 mb-2">
|
||||
<h3 className="text-xl font-medium">API tokens</h3>
|
||||
<Button variant="primary" onClick={() => setIsCreateTokenModalOpen(true)}>
|
||||
Add API token
|
||||
</Button>
|
||||
<section className="pr-9 py-8 w-full overflow-y-auto">
|
||||
{tokens.length > 0 ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between py-3.5 border-b border-custom-border-200 mb-2">
|
||||
<h3 className="text-xl font-medium">API tokens</h3>
|
||||
<Button variant="primary" onClick={() => setIsCreateTokenModalOpen(true)}>
|
||||
Add API token
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
{tokens.map((token) => (
|
||||
<ApiTokenListItem key={token.id} token={token} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="mx-auto">
|
||||
<ApiTokenEmptyState onClick={() => setIsCreateTokenModalOpen(true)} />
|
||||
</div>
|
||||
<div>
|
||||
{tokens.map((token) => (
|
||||
<ApiTokenListItem key={token.id} token={token} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<div className="mx-auto py-8">
|
||||
<ApiTokenEmptyState onClick={() => setIsCreateTokenModalOpen(true)} />
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</section>
|
||||
) : (
|
||||
<div className="h-full w-full grid place-items-center p-4">
|
||||
<Spinner />
|
||||
|
@ -4,6 +4,7 @@ import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { useTheme } from "next-themes";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// services
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
import { UserService } from "services/user.service";
|
||||
@ -30,15 +31,23 @@ import type { IWorkspaceMemberInvitation } from "types";
|
||||
import { ROLE } from "constants/workspace";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// mobx-store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
const workspaceService = new WorkspaceService();
|
||||
const userService = new UserService();
|
||||
|
||||
const UserInvitationsPage: NextPageWithLayout = () => {
|
||||
const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||
|
||||
// store
|
||||
const {
|
||||
workspace: { workspaceSlug },
|
||||
user: { currentUserSettings },
|
||||
} = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { theme } = useTheme();
|
||||
@ -51,6 +60,12 @@ const UserInvitationsPage: NextPageWithLayout = () => {
|
||||
workspaceService.userWorkspaceInvitations()
|
||||
);
|
||||
|
||||
const redirectWorkspaceSlug =
|
||||
workspaceSlug ||
|
||||
currentUserSettings?.workspace?.last_workspace_slug ||
|
||||
currentUserSettings?.workspace?.fallback_workspace_slug ||
|
||||
"";
|
||||
|
||||
const handleInvitation = (workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw") => {
|
||||
if (action === "accepted") {
|
||||
setInvitationsRespond((prevData) => [...prevData, workspace_invitation.id]);
|
||||
@ -180,7 +195,7 @@ const UserInvitationsPage: NextPageWithLayout = () => {
|
||||
>
|
||||
Accept & Join
|
||||
</Button>
|
||||
<Link href="/">
|
||||
<Link href={`/${redirectWorkspaceSlug}`}>
|
||||
<a>
|
||||
<Button variant="neutral-primary" size="md">
|
||||
Go Home
|
||||
@ -197,8 +212,8 @@ const UserInvitationsPage: NextPageWithLayout = () => {
|
||||
description="You can see here if someone invites you to a workspace."
|
||||
image={emptyInvitation}
|
||||
primaryButton={{
|
||||
text: "Back to Dashboard",
|
||||
onClick: () => router.push("/"),
|
||||
text: "Back to dashboard",
|
||||
onClick: () => router.push(`/${redirectWorkspaceSlug}`),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -206,7 +221,7 @@ const UserInvitationsPage: NextPageWithLayout = () => {
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
UserInvitationsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user