forked from github/plane
[WEB-1404] chore: auth and onboarding improvements (#4564)
* chore: `switch account` modal revamp. * chore: workspace invitation page message display logic update. * chore: update `showDeleteAccountModal` state to `showSwitchAccountModal`.
This commit is contained in:
parent
073d453752
commit
ddc28d37d3
@ -44,9 +44,9 @@ export const buttonStyling: IButtonStyling = {
|
|||||||
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`,
|
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`,
|
||||||
},
|
},
|
||||||
"accent-primary": {
|
"accent-primary": {
|
||||||
default: `bg-custom-primary-10 text-custom-primary-100`,
|
default: `bg-custom-primary-100/20 text-custom-primary-100`,
|
||||||
hover: `hover:bg-custom-primary-20 hover:text-custom-primary-200`,
|
hover: `hover:bg-custom-primary-100/10 hover:text-custom-primary-200`,
|
||||||
pressed: `focus:bg-custom-primary-20`,
|
pressed: `focus:bg-custom-primary-100/10`,
|
||||||
disabled: `cursor-not-allowed !text-custom-primary-60`,
|
disabled: `cursor-not-allowed !text-custom-primary-60`,
|
||||||
},
|
},
|
||||||
"outline-primary": {
|
"outline-primary": {
|
||||||
|
@ -6,7 +6,7 @@ import { useTheme } from "next-themes";
|
|||||||
// types
|
// types
|
||||||
import { IWorkspaceMemberInvitation, TOnboardingSteps } from "@plane/types";
|
import { IWorkspaceMemberInvitation, TOnboardingSteps } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
import { Invitations, OnboardingHeader, SwitchOrDeleteAccountDropdown, CreateWorkspace } from "@/components/onboarding";
|
import { Invitations, OnboardingHeader, SwitchAccountDropdown, CreateWorkspace } from "@/components/onboarding";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
@ -55,7 +55,7 @@ export const CreateOrJoinWorkspaces: React.FC<Props> = observer((props) => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<OnboardingHeader currentStep={totalSteps - 1} totalSteps={totalSteps} />
|
<OnboardingHeader currentStep={totalSteps - 1} totalSteps={totalSteps} />
|
||||||
<div className="shrink-0 lg:hidden">
|
<div className="shrink-0 lg:hidden">
|
||||||
<SwitchOrDeleteAccountDropdown />
|
<SwitchAccountDropdown />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full items-center justify-center p-8 mt-6">
|
<div className="flex flex-col w-full items-center justify-center p-8 mt-6">
|
||||||
@ -79,7 +79,7 @@ export const CreateOrJoinWorkspaces: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
||||||
<SwitchOrDeleteAccountDropdown />
|
<SwitchAccountDropdown />
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image
|
||||||
src={resolvedTheme === "dark" ? CreateJoinWorkspaceDark : CreateJoinWorkspace}
|
src={resolvedTheme === "dark" ? CreateJoinWorkspaceDark : CreateJoinWorkspace}
|
||||||
|
@ -5,6 +5,6 @@ export * from "./profile-setup";
|
|||||||
export * from "./create-workspace";
|
export * from "./create-workspace";
|
||||||
export * from "./invitations";
|
export * from "./invitations";
|
||||||
export * from "./step-indicator";
|
export * from "./step-indicator";
|
||||||
export * from "./switch-or-delete-account-dropdown";
|
export * from "./switch-account-dropdown";
|
||||||
export * from "./switch-delete-account-modal";
|
export * from "./switch-account-modal";
|
||||||
export * from "./header";
|
export * from "./header";
|
||||||
|
@ -34,7 +34,7 @@ import InviteMembersDark from "public/onboarding/invite-members-dark.svg";
|
|||||||
import InviteMembersLight from "public/onboarding/invite-members-light.svg";
|
import InviteMembersLight from "public/onboarding/invite-members-light.svg";
|
||||||
// components
|
// components
|
||||||
import { OnboardingHeader } from "./header";
|
import { OnboardingHeader } from "./header";
|
||||||
import { SwitchOrDeleteAccountDropdown } from "./switch-or-delete-account-dropdown";
|
import { SwitchAccountDropdown } from "./switch-account-dropdown";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
finishOnboarding: () => Promise<void>;
|
finishOnboarding: () => Promise<void>;
|
||||||
@ -366,7 +366,7 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
{/* Since this will always be the last step */}
|
{/* Since this will always be the last step */}
|
||||||
<OnboardingHeader currentStep={totalSteps} totalSteps={totalSteps} />
|
<OnboardingHeader currentStep={totalSteps} totalSteps={totalSteps} />
|
||||||
<div className="shrink-0 lg:hidden">
|
<div className="shrink-0 lg:hidden">
|
||||||
<SwitchOrDeleteAccountDropdown />
|
<SwitchAccountDropdown />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full items-center justify-center p-8 mt-6 md:w-4/5 mx-auto">
|
<div className="flex flex-col w-full items-center justify-center p-8 mt-6 md:w-4/5 mx-auto">
|
||||||
@ -433,7 +433,7 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
||||||
<SwitchOrDeleteAccountDropdown />
|
<SwitchAccountDropdown />
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image
|
||||||
src={resolvedTheme === "dark" ? InviteMembersDark : InviteMembersLight}
|
src={resolvedTheme === "dark" ? InviteMembersDark : InviteMembersLight}
|
||||||
|
@ -11,7 +11,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { PasswordStrengthMeter } from "@/components/account";
|
import { PasswordStrengthMeter } from "@/components/account";
|
||||||
import { UserImageUploadModal } from "@/components/core";
|
import { UserImageUploadModal } from "@/components/core";
|
||||||
import { OnboardingHeader, SwitchOrDeleteAccountDropdown } from "@/components/onboarding";
|
import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding";
|
||||||
// constants
|
// constants
|
||||||
import { USER_DETAILS } from "@/constants/event-tracker";
|
import { USER_DETAILS } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
@ -276,7 +276,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<OnboardingHeader currentStep={isCurrentStepUserPersonalization ? 2 : 1} totalSteps={totalSteps} />
|
<OnboardingHeader currentStep={isCurrentStepUserPersonalization ? 2 : 1} totalSteps={totalSteps} />
|
||||||
<div className="shrink-0 lg:hidden">
|
<div className="shrink-0 lg:hidden">
|
||||||
<SwitchOrDeleteAccountDropdown fullName={`${watch("first_name")} ${watch("last_name")}`} />
|
<SwitchAccountDropdown fullName={`${watch("first_name")} ${watch("last_name")}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full items-center justify-center p-8 mt-6">
|
<div className="flex flex-col w-full items-center justify-center p-8 mt-6">
|
||||||
@ -567,7 +567,7 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
|
||||||
<SwitchOrDeleteAccountDropdown fullName={`${watch("first_name")} ${watch("last_name")}`} />
|
<SwitchAccountDropdown fullName={`${watch("first_name")} ${watch("last_name")}`} />
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
{profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION ? (
|
{profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION ? (
|
||||||
<Image
|
<Image
|
||||||
|
@ -9,16 +9,16 @@ import { cn } from "@/helpers/common.helper";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// components
|
// components
|
||||||
import { SwitchOrDeleteAccountModal } from "./switch-delete-account-modal";
|
import { SwitchAccountModal } from "./switch-account-modal";
|
||||||
|
|
||||||
type TSwithOrDeleteAccountDropdownProps = {
|
type TSwitchAccountDropdownProps = {
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProps> = observer((props) => {
|
export const SwitchAccountDropdown: FC<TSwitchAccountDropdownProps> = observer((props) => {
|
||||||
const { fullName } = props;
|
const { fullName } = props;
|
||||||
// states
|
// states
|
||||||
const [showDeleteAccountModal, setShowDeleteAccountModal] = useState(false);
|
const [showSwitchAccountModal, setShowSwitchAccountModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full shrink-0 justify-end">
|
<div className="flex w-full shrink-0 justify-end">
|
||||||
<SwitchOrDeleteAccountModal isOpen={showDeleteAccountModal} onClose={() => setShowDeleteAccountModal(false)} />
|
<SwitchAccountModal isOpen={showSwitchAccountModal} onClose={() => setShowSwitchAccountModal(false)} />
|
||||||
<div className="flex items-center gap-x-2 pr-4 z-10">
|
<div className="flex items-center gap-x-2 pr-4 z-10">
|
||||||
{user?.avatar && (
|
{user?.avatar && (
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -64,7 +64,7 @@ export const SwitchOrDeleteAccountDropdown: FC<TSwithOrDeleteAccountDropdownProp
|
|||||||
"bg-custom-background-80": active,
|
"bg-custom-background-80": active,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onClick={() => setShowDeleteAccountModal(true)}
|
onClick={() => setShowSwitchAccountModal(true)}
|
||||||
>
|
>
|
||||||
Wrong e-mail address?
|
Wrong e-mail address?
|
||||||
</Menu.Item>
|
</Menu.Item>
|
@ -1,34 +1,31 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { mutate } from "swr";
|
import { ArrowRightLeft } from "lucide-react";
|
||||||
import { Trash2 } from "lucide-react";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// hooks
|
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
|
||||||
import { useUser } from "@/hooks/store";
|
|
||||||
// ui
|
// ui
|
||||||
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
// hooks
|
||||||
|
import { useUser } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
|
export const SwitchAccountModal: React.FC<Props> = (props) => {
|
||||||
const { isOpen, onClose } = props;
|
const { isOpen, onClose } = props;
|
||||||
// states
|
// states
|
||||||
const [switchingAccount, setSwitchingAccount] = useState(false);
|
const [switchingAccount, setSwitchingAccount] = useState(false);
|
||||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { signOut, deactivateAccount } = useUser();
|
const { data: userData, signOut } = useUser();
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setSwitchingAccount(false);
|
setSwitchingAccount(false);
|
||||||
setIsDeactivating(false);
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,32 +48,6 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
|
|||||||
.finally(() => setSwitchingAccount(false));
|
.finally(() => setSwitchingAccount(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeactivateAccount = async () => {
|
|
||||||
setIsDeactivating(true);
|
|
||||||
|
|
||||||
await deactivateAccount()
|
|
||||||
.then(() => {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.SUCCESS,
|
|
||||||
title: "Success!",
|
|
||||||
message: "Account deleted successfully.",
|
|
||||||
});
|
|
||||||
mutate("CURRENT_USER_DETAILS", null);
|
|
||||||
signOut();
|
|
||||||
setTheme("system");
|
|
||||||
router.push("/");
|
|
||||||
handleClose();
|
|
||||||
})
|
|
||||||
.catch((err: any) =>
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: "Error!",
|
|
||||||
message: err?.error,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.finally(() => setIsDeactivating(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||||
@ -104,32 +75,30 @@ export const SwitchOrDeleteAccountModal: React.FC<Props> = (props) => {
|
|||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
|
||||||
<div className="px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
<div className="p-6 pb-1">
|
||||||
<div>
|
<div className="flex gap-x-4">
|
||||||
<div className="flex items-center gap-x-4">
|
<div className="flex items-start">
|
||||||
<div className="grid place-items-center rounded-full bg-red-500/20 p-4">
|
<div className="grid place-items-center rounded-full bg-custom-primary-100/20 p-4">
|
||||||
<Trash2 className="h-6 w-6 text-red-600" aria-hidden="true" />
|
<ArrowRightLeft className="h-5 w-5 text-custom-primary-100" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<Dialog.Title as="h3" className="text-2xl font-medium leading-6 text-onboarding-text-100">
|
|
||||||
Not the right workspace?
|
|
||||||
</Dialog.Title>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col py-3 gap-y-6">
|
||||||
<div className="mt-6 px-4">
|
<Dialog.Title as="h3" className="text-2xl font-medium leading-6 text-onboarding-text-100">
|
||||||
<ul className="list-disc text-base font-normal text-onboarding-text-300">
|
Switch account
|
||||||
<li>Delete this account if you have another and won{"'"}t use this account.</li>
|
</Dialog.Title>
|
||||||
<li>Switch to another account if you{"'"}d like to come back to this account another time.</li>
|
{userData?.email && (
|
||||||
</ul>
|
<div className="text-base font-normal text-onboarding-text-200">
|
||||||
|
If you have signed up via <span className="text-custom-primary-100">{userData.email}</span>{" "}
|
||||||
|
un-intentionally, you can switch your account to a different one from here.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2 flex items-center justify-end gap-3 p-4 sm:px-6">
|
<div className="mb-2 flex items-center justify-end gap-3 p-4 sm:px-6">
|
||||||
<Button variant="neutral-primary" onClick={handleSwitchAccount} disabled={switchingAccount}>
|
<Button variant="accent-primary" onClick={handleSwitchAccount} disabled={switchingAccount}>
|
||||||
{switchingAccount ? "Switching..." : "Switch account"}
|
{switchingAccount ? "Switching..." : "Switch account"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline-danger" onClick={handleDeactivateAccount} loading={isDeactivating}>
|
|
||||||
{isDeactivating ? "Deleting..." : "Delete account"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
@ -70,53 +70,47 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
{invitationDetail ? (
|
{invitationDetail && !invitationDetail.responded_at ? (
|
||||||
<>
|
error ? (
|
||||||
{error ? (
|
<div className="flex w-full flex-col space-y-4 rounded border border-custom-border-200 bg-custom-background-100 px-4 py-8 text-center shadow-2xl md:w-1/3">
|
||||||
<div className="flex w-full flex-col space-y-4 rounded border border-custom-border-200 bg-custom-background-100 px-4 py-8 text-center shadow-2xl md:w-1/3">
|
<h2 className="text-xl uppercase">INVITATION NOT FOUND</h2>
|
||||||
<h2 className="text-xl uppercase">INVITATION NOT FOUND</h2>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<EmptySpace
|
||||||
<>
|
title={`You have been invited to ${invitationDetail.workspace.name}`}
|
||||||
{invitationDetail.accepted ? (
|
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||||
<>
|
>
|
||||||
<EmptySpace
|
<EmptySpaceItem Icon={Check} title="Accept" action={handleAccept} />
|
||||||
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
<EmptySpaceItem Icon={X} title="Ignore" action={handleReject} />
|
||||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
</EmptySpace>
|
||||||
>
|
)
|
||||||
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
) : error || invitationDetail?.responded_at ? (
|
||||||
</EmptySpace>
|
invitationDetail?.accepted ? (
|
||||||
</>
|
<EmptySpace
|
||||||
) : (
|
title={`You are already a member of ${invitationDetail.workspace.name}`}
|
||||||
<EmptySpace
|
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||||
title={`You have been invited to ${invitationDetail.workspace.name}`}
|
>
|
||||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
|
||||||
>
|
|
||||||
<EmptySpaceItem Icon={Check} title="Accept" action={handleAccept} />
|
|
||||||
<EmptySpaceItem Icon={X} title="Ignore" action={handleReject} />
|
|
||||||
</EmptySpace>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : error ? (
|
|
||||||
<EmptySpace
|
|
||||||
title="This invitation link is not active anymore."
|
|
||||||
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
|
||||||
link={{ text: "Or start from an empty project", href: "/" }}
|
|
||||||
>
|
|
||||||
{!currentUser ? (
|
|
||||||
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
|
|
||||||
) : (
|
|
||||||
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
||||||
)}
|
</EmptySpace>
|
||||||
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
|
) : (
|
||||||
<EmptySpaceItem
|
<EmptySpace
|
||||||
Icon={Share2}
|
title="This invitation link is not active anymore."
|
||||||
title="Join our community of active creators"
|
description="Your workspace is where you'll create projects, collaborate on your issues, and organize different streams of work in your Plane account."
|
||||||
href="https://discord.com/invite/8SR2N9PAcJ"
|
link={{ text: "Or start from an empty project", href: "/" }}
|
||||||
/>
|
>
|
||||||
</EmptySpace>
|
{!currentUser ? (
|
||||||
|
<EmptySpaceItem Icon={User2} title="Sign in to continue" href="/" />
|
||||||
|
) : (
|
||||||
|
<EmptySpaceItem Icon={Boxes} title="Continue to home" href="/" />
|
||||||
|
)}
|
||||||
|
<EmptySpaceItem Icon={Star} title="Star us on GitHub" href="https://github.com/makeplane" />
|
||||||
|
<EmptySpaceItem
|
||||||
|
Icon={Share2}
|
||||||
|
title="Join our community of active creators"
|
||||||
|
href="https://discord.com/invite/A92xrEGCge"
|
||||||
|
/>
|
||||||
|
</EmptySpace>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<LogoSpinner />
|
<LogoSpinner />
|
||||||
|
Loading…
Reference in New Issue
Block a user