fix: onboarding bugs & improvements (#2839)

* fix: terms & condition alignment

* fix: onboarding page scrolling

* fix: create workspace name clear

* fix: setup profile sidebar workspace name

* fix: invite team screen button text

* fix: inner div min height

* fix: allow single invite also in invite member

* fix: UI clipping in invite members

* fix: signin screen scroll

* fix: sidebar notification icon

* fix: sidebar project name & icon

* fix: user detail bottom image alignment

* fix: step indicator in invite member

* fix: try different account modal state

* fix: setup profile remove image

* fix: workspace slug clear

* fix: invite member UI & focus

* fix: step indicator size

* fix: inner div placement

* fix: invite member validation logic

* fix: cuurent user data persistency

* fix: sidebar animation colors

* feat: signup & resend

* fix: sign out theme persist from popover

* fix: imports

* chore: signin responsiveness

* fix: sign-in, sign-up top padding
This commit is contained in:
Lakhan Baheti 2023-11-23 13:45:00 +05:30 committed by sriram veeraghanta
parent f9590929dc
commit 3c89ef8cc3
21 changed files with 607 additions and 375 deletions

View file

@ -2,8 +2,6 @@
import React, { useState } from "react";
// next
import { useRouter } from "next/router";
// components
import { Button } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
// services
@ -11,8 +9,10 @@ import { AuthService } from "services/auth.service";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// icons
import { AlertTriangle } from "lucide-react";
import { Trash2 } from "lucide-react";
import { UserService } from "services/user.service";
import { useTheme } from "next-themes";
import { mutate } from "swr";
type Props = {
isOpen: boolean;
@ -25,13 +25,17 @@ const userService = new UserService();
const DeleteAccountModal: React.FC<Props> = (props) => {
const { isOpen, onClose } = props;
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const router = useRouter();
const { setTheme } = useTheme();
const { setToastAlert } = useToast();
const handleSignOut = async () => {
await authService
.signOut()
.then(() => {
mutate("CURRENT_USER_DETAILS", null);
setTheme("system");
router.push("/");
})
.catch(() =>
@ -53,6 +57,8 @@ const DeleteAccountModal: React.FC<Props> = (props) => {
title: "Success!",
message: "Account deleted successfully.",
});
mutate("CURRENT_USER_DETAILS", null);
setTheme("system");
router.push("/");
})
.catch((err) =>
@ -100,7 +106,7 @@ const DeleteAccountModal: React.FC<Props> = (props) => {
<div className="">
<div className="flex items-center gap-x-4">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
<Trash2 className="h-5 w-5 text-red-600" aria-hidden="true" />
</div>
<Dialog.Title as="h3" className="text-2xl font-medium leading-6 text-onboarding-text-100">
Not the right workspace?

View file

@ -1,17 +1,16 @@
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { XCircle } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// components
import { AuthType } from "components/page-views";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import useTimer from "hooks/use-timer";
// icons
import { XCircle } from "lucide-react";
import { useTheme } from "next-themes";
// types
type EmailCodeFormValues = {
email: string;
key?: string;
@ -20,7 +19,14 @@ type EmailCodeFormValues = {
const authService = new AuthService();
export const EmailCodeForm = ({ handleSignIn }: any) => {
type Props = {
handleSignIn: any;
authType: AuthType;
};
export const EmailCodeForm: React.FC<Props> = (Props) => {
const { handleSignIn, authType } = Props;
// states
const [codeSent, setCodeSent] = useState(false);
const [codeResent, setCodeResent] = useState(false);
const [isCodeResending, setIsCodeResending] = useState(false);
@ -37,7 +43,6 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
setError,
setValue,
getValues,
watch,
formState: { errors, isSubmitting, isValid, isDirty },
} = useForm<EmailCodeFormValues>({
defaultValues: {
@ -49,14 +54,13 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
reValidateMode: "onChange",
});
const isResendDisabled = resendCodeTimer > 0 || isCodeResending || isSubmitting || errorResendingCode;
const isResendDisabled = resendCodeTimer > 0 || isCodeResending || isSubmitting;
const onSubmit = async ({ email }: EmailCodeFormValues) => {
setErrorResendingCode(false);
await authService
.emailCode({ email })
.then((res) => {
console.log(res);
setSentEmail(email);
setValue("key", res.key);
setCodeSent(true);
@ -139,12 +143,20 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
) : (
<>
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-onboarding-text-100">
Lets get you prepped!
{authType === "sign-in" ? "Get on your flight deck!" : "Lets get you prepped!"}
</h1>
<p className="text-center text-sm text-onboarding-text-200 mt-3">
This whole thing will take less than two minutes.
</p>
<p className="text-center text-sm text-onboarding-text-200 mt-1">Promise!</p>
{authType == "sign-up" ? (
<div>
<p className="text-center text-sm text-onboarding-text-200 mt-3">
This whole thing will take less than two minutes.
</p>
<p className="text-center text-sm text-onboarding-text-200 mt-1">Promise!</p>
</div>
) : (
<p className="text-center text-sm text-onboarding-text-200 px-20 mt-3">
Sign in with the email you used to sign up for Plane
</p>
)}
</>
)}
@ -216,11 +228,39 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
onChange={onChange}
ref={ref}
hasError={Boolean(errors.token)}
placeholder="get-set-fly"
placeholder="gets-sets-flys"
className="border-onboarding-border-100 h-[46px] w-full"
/>
)}
/>
{resendCodeTimer <= 0 && !isResendDisabled && (
<button
type="button"
className={`flex absolute w-fit right-3.5 justify-end text-xs outline-none cursor-pointer text-custom-primary-100`}
onClick={() => {
setIsCodeResending(true);
onSubmit({ email: getValues("email") }).then(() => {
setCodeResent(true);
setIsCodeResending(false);
setResendCodeTimer(30);
});
}}
disabled={isResendDisabled}
>
<span className="font-medium">Resend</span>
</button>
)}
</div>
<div
className={`flex w-full justify-end text-xs outline-none ${
isResendDisabled ? "cursor-default text-custom-text-200" : "cursor-pointer text-custom-primary-100"
} `}
>
{resendCodeTimer > 0 ? (
<span className="text-right">Request new code in {resendCodeTimer}s</span>
) : isCodeResending ? (
"Sending new code..."
) : null}
</div>
</>
)}
@ -238,8 +278,8 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
>
{isLoading ? "Signing in..." : "Next step"}
</Button>
<div className="w-[70%] my-4 mx-auto">
<p className="text-xs text-onboarding-text-300">
<div className="w-3/4 my-4 mx-auto">
<p className="text-xs text-center text-onboarding-text-300">
When you click the button above, you agree with our{" "}
<a
href="https://plane.so/terms-and-conditions"

View file

@ -6,16 +6,18 @@ import Image from "next/image";
import { useRouter } from "next/router";
import { useTheme } from "next-themes";
// images
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
import githubLightModeImage from "/public/logos/github-black.png";
import githubDarkModeImage from "/public/logos/github-dark.svg";
import { AuthType } from "components/page-views";
export interface GithubLoginButtonProps {
handleSignIn: React.Dispatch<string>;
clientId: string;
authType: AuthType;
}
export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
const { handleSignIn, clientId } = props;
const { handleSignIn, clientId, authType } = props;
// states
const [loginCallBackURL, setLoginCallBackURL] = useState(undefined);
const [gitCode, setGitCode] = useState<null | string>(null);
@ -24,7 +26,7 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
query: { code },
} = useRouter();
// theme
const { theme } = useTheme();
const { resolvedTheme } = useTheme();
useEffect(() => {
if (code && !gitCode) {
@ -37,22 +39,23 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
setLoginCallBackURL(`${origin}/` as any);
}, []);
return (
<div className="w-full flex justify-center items-center">
<Link
href={`https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
>
<button
className={`flex w-full items-center justify-center gap-2 hover:bg-onboarding-background-300 rounded border border-onboarding-border-200 p-2 text-sm font-medium text-custom-text-100 duration-300 h-[46px]`}
className={`flex w-full items-center justify-center gap-2 hover:bg-onboarding-background-300 rounded border px-2 text-sm font-medium text-custom-text-100 duration-300 h-[42px] ${
resolvedTheme === "dark" ? "bg-[#2F3135] border-[#43484F]" : "border-[#D9E4FF]"
}`}
>
<Image
src={theme === "dark" ? githubWhiteImage : githubBlackImage}
src={resolvedTheme === "dark" ? githubDarkModeImage : githubLightModeImage}
height={20}
width={20}
alt="GitHub Logo"
/>
<span className="text-onboarding-text-200">Sign in with GitHub</span>
<span className="text-onboarding-text-200">{authType == "sign-in" ? "Sign-in" : "Sign-up"} with GitHub</span>
</button>
</Link>
</div>

View file

@ -1,12 +1,7 @@
import React, { useEffect } from "react";
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// react-hook-form
import { useTheme } from "next-themes";
import Image from "next/image";
import { Control, Controller, UseFormSetValue, UseFormWatch } from "react-hook-form";
// types
import { IWorkspace } from "types";
// icons
import {
BarChart2,
Briefcase,
@ -19,7 +14,16 @@ import {
PenSquare,
Search,
Settings,
Bell,
} from "lucide-react";
import { Avatar, DiceIcon, PhotoFilterIcon } from "@plane/ui";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// types
import { IWorkspace } from "types";
// assets
import projectEmoji from "public/emoji/project-emoji.svg";
const workspaceLinks = [
{
@ -39,7 +43,7 @@ const workspaceLinks = [
name: "All Issues",
},
{
Icon: CheckCircle,
Icon: Bell,
name: "Notifications",
},
];
@ -89,22 +93,23 @@ const DummySidebar: React.FC<Props> = (props) => {
const { workspace: workspaceStore, user: userStore } = useMobxStore();
const workspace = workspaceStore.workspaces ? workspaceStore.workspaces[0] : null;
const { resolvedTheme } = useTheme();
const handleZoomWorkspace = (value: string) => {
// console.log(lastWorkspaceName,value);
if (lastWorkspaceName === value) return;
lastWorkspaceName = value;
if (timer > 0) {
timer += 2;
timer = Math.min(timer, 4);
timer = Math.min(timer, 2);
} else {
timer = 2;
timer = Math.min(timer, 4);
timer = Math.min(timer, 2);
const interval = setInterval(() => {
if (timer < 0) {
setValue!("name", lastWorkspaceName);
clearInterval(interval);
}
console.log("timer", timer);
timer--;
}, 1000);
}
@ -112,7 +117,7 @@ const DummySidebar: React.FC<Props> = (props) => {
useEffect(() => {
if (watch) {
watch();
watch("name");
}
});
@ -126,22 +131,34 @@ const DummySidebar: React.FC<Props> = (props) => {
render={({ field: { value } }) => {
if (value.length > 0) {
handleZoomWorkspace(value);
} else {
lastWorkspaceName = "";
}
return timer > 0 ? (
<div className="py-6 pl-4 top-3 mt-4 transition-all bg-onboarding-background-200 w-full max-w-screen-sm flex items-center ml-6 border-8 border-onboarding-background-100 rounded-md">
<div className="bg-onboarding-background-100 w-full p-1 flex items-center">
<div className="flex flex-shrink-0">
<Avatar
name={value.length > 0 ? value[0].toLocaleUpperCase() : "N"}
src={""}
size={30}
shape="square"
fallbackBackgroundColor="black"
className="!text-base"
/>
</div>
<div
className={`top-3 mt-4 transition-all bg-onboarding-background-200 w-full max-w-screen-sm flex items-center ml-6 border-[6px] ${
resolvedTheme == "dark" ? "border-onboarding-background-100" : "border-custom-primary-20"
} rounded-xl`}
>
<div className="border rounded-lg py-6 pl-4 w-full border-onboarding-background-400">
<div
className={`${
resolvedTheme == "light" ? "bg-[#F5F5F5]" : "bg-[#363A40]"
} w-full p-1 flex items-center`}
>
<div className="flex flex-shrink-0">
<Avatar
name={value.length > 0 ? value[0].toLocaleUpperCase() : "N"}
src={""}
size={30}
shape="square"
fallbackBackgroundColor="black"
className="!text-base"
/>
</div>
<span className="text-xl font-medium text-onboarding-text-100 ml-2 truncate">{value}</span>
<span className="text-xl font-medium text-onboarding-text-100 ml-2 truncate">{value}</span>
</div>
</div>
</div>
) : (
@ -206,7 +223,7 @@ const DummySidebar: React.FC<Props> = (props) => {
<div className={`flex items-center justify-between w-full px-1 mb-3 gap-2 mt-4 `}>
<div
className={`relative flex items-center justify-between w-full rounded gap-1 group
px-3 shadow-custom-sidebar-shadow-2xs border-[0.5px] border-custom-border-200
px-3 shadow-custom-shadow-2xs border-onboarding-border-100 border
`}
>
<div className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 outline-none`}>
@ -217,7 +234,7 @@ const DummySidebar: React.FC<Props> = (props) => {
<div
className={`flex items-center justify-center rounded flex-shrink-0 p-2 outline-none
shadow-custom-sidebar-shadow-2xs border-[0.5px] border-onboarding-border-200
shadow-custom-shadow-2xs border border-onboarding-border-100
`}
>
<Search className="h-4 w-4 text-onboarding-text-200" />
@ -244,11 +261,15 @@ const DummySidebar: React.FC<Props> = (props) => {
<div className="px-3">
{" "}
<div className="w-4/5 flex items-center text-base font-medium text-custom-text-200 mb-3 justify-between">
<span> Plane web</span>
<div className="flex items-center gap-x-2">
<Image src={projectEmoji} alt="Plane Logo" className="h-4 w-4" />
<span> Plane</span>
</div>
<ChevronDown className="h-4 w-4" />
</div>
{projectLinks.map((link) => (
<a className="block w-full">
<a className="block ml-6 w-full">
<div
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-base font-medium outline-none
text-custom-sidebar-text-200 focus:bg-custom-sidebar-background-80

View file

@ -2,17 +2,17 @@ import React from "react";
const OnboardingStepIndicator = ({ step }: { step: number }) => (
<div className="flex items-center justify-center">
<div className="h-4 w-4 rounded-full bg-custom-primary-100 z-10" />
<div className="h-3 w-3 rounded-full bg-custom-primary-100 z-10" />
<div className={`h-1 w-14 -ml-1 ${step >= 2 ? "bg-custom-primary-100" : "bg-onboarding-background-100"}`} />
<div
className={` z-10 -ml-1 rounded-full ${
step >= 2 ? "bg-custom-primary-100 h-4 w-4" : " h-3 w-3 bg-onboarding-background-100"
step >= 2 ? "bg-custom-primary-100 h-3 w-3" : " h-2 w-2 bg-onboarding-background-100"
}`}
/>
<div className={`h-1 w-14 -ml-1 ${step >= 3 ? "bg-custom-primary-100" : "bg-onboarding-background-100"}`} />
<div
className={`rounded-full -ml-1 z-10 ${
step >= 3 ? "bg-custom-primary-100 h-4 w-4" : "h-3 w-3 bg-onboarding-background-100"
step >= 3 ? "bg-custom-primary-100 h-3 w-3" : "h-2 w-2 bg-onboarding-background-100"
}`}
/>
</div>