mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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:
parent
c2da9783a3
commit
9ba724b78d
@ -36,6 +36,8 @@ module.exports = {
|
||||
"custom-sidebar-shadow-xl": "var(--color-sidebar-shadow-xl)",
|
||||
"custom-sidebar-shadow-2xl": "var(--color-sidebar-shadow-2xl)",
|
||||
"custom-sidebar-shadow-3xl": "var(--color-sidebar-shadow-3xl)",
|
||||
"onbording-shadow-sm": "var(--color-onboarding-shadow-sm)",
|
||||
|
||||
},
|
||||
colors: {
|
||||
custom: {
|
||||
@ -192,6 +194,7 @@ module.exports = {
|
||||
border: {
|
||||
100: convertToRGB("--color-onboarding-border-100"),
|
||||
200: convertToRGB("--color-onboarding-border-200"),
|
||||
300: convertToRGB("--color-onboarding-border-300"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -372,8 +375,9 @@ module.exports = {
|
||||
96: "21.6rem",
|
||||
},
|
||||
backgroundImage: {
|
||||
"onboarding-gradient-primary": "var( --gradient-onboarding-primary)",
|
||||
"onboarding-gradient-secondary": "var( --gradient-onboarding-secondary)",
|
||||
"onboarding-gradient-100": "var( --gradient-onboarding-100)",
|
||||
"onboarding-gradient-200": "var( --gradient-onboarding-200)",
|
||||
"onboarding-gradient-300": "var( --gradient-onboarding-300)",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
|
@ -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?
|
||||
|
@ -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">
|
||||
Let’s get you prepped!
|
||||
{authType === "sign-in" ? "Get on your flight deck!" : "Let’s 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"
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTheme } from "next-themes";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Cog, LogIn, LogOut, Settings, UserCircle2 } from "lucide-react";
|
||||
import { mutate } from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
@ -39,6 +41,7 @@ export const InstanceSidebarDropdown = observer(() => {
|
||||
} = useMobxStore();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
// redirect url for normal mode
|
||||
const redirectWorkspaceSlug =
|
||||
@ -51,6 +54,8 @@ export const InstanceSidebarDropdown = observer(() => {
|
||||
await authService
|
||||
.signOut()
|
||||
.then(() => {
|
||||
mutate("CURRENT_USER_DETAILS", null);
|
||||
setTheme("system");
|
||||
router.push("/");
|
||||
})
|
||||
.catch(() =>
|
||||
@ -70,13 +75,13 @@ export const InstanceSidebarDropdown = observer(() => {
|
||||
sidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
>
|
||||
<div className={`flex-shrink-0 flex items-center justify-center h-6 w-6 bg-custom-sidebar-background-80 rounded`}>
|
||||
<div
|
||||
className={`flex-shrink-0 flex items-center justify-center h-6 w-6 bg-custom-sidebar-background-80 rounded`}
|
||||
>
|
||||
<Cog className="h-5 w-5 text-custom-text-200" />
|
||||
</div>
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<h4 className="text-custom-text-200 font-medium text-base truncate">Instance Admin</h4>
|
||||
)}
|
||||
{!sidebarCollapsed && <h4 className="text-custom-text-200 font-medium text-base truncate">Instance Admin</h4>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
// react
|
||||
import React, { useState } from "react";
|
||||
import { CheckCircle2, Search } from "lucide-react";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
// components
|
||||
import { Button, Loader } from "@plane/ui";
|
||||
|
||||
@ -9,16 +11,12 @@ import { truncateText } from "helpers/string.helper";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
// swr
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// contants
|
||||
import { USER_WORKSPACES, USER_WORKSPACE_INVITATIONS } from "constants/fetch-keys";
|
||||
import { ROLE } from "constants/workspace";
|
||||
// types
|
||||
import { IWorkspaceMemberInvitation } from "types";
|
||||
// icons
|
||||
import { CheckCircle2, Search } from "lucide-react";
|
||||
import { trackEvent } from "helpers/event-tracker.helper";
|
||||
|
||||
type Props = {
|
||||
handleNextStep: () => void;
|
||||
@ -147,11 +145,11 @@ const Invitations: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyInvitation email={currentUser!.email} />
|
||||
<EmptyInvitation email={currentUser!.email} setTryDiffAccount={setTryDiffAccount} />
|
||||
);
|
||||
};
|
||||
|
||||
const EmptyInvitation = ({ email }: { email: string }) => (
|
||||
const EmptyInvitation = ({ email, setTryDiffAccount }: { email: string; setTryDiffAccount: () => void }) => (
|
||||
<div className="items-center md:w-4/5 bg-onboarding-background-300/30 my-16 border-onboarding-border-200 py-5 px-10 rounded border justify-center ">
|
||||
<p className="text-lg text-onboarding-text-300 text-center font-semibold">Is your team already on Plane?</p>
|
||||
<p className="text-sm text-onboarding-text-300 mt-6 text-center">
|
||||
@ -159,7 +157,7 @@ const EmptyInvitation = ({ email }: { email: string }) => (
|
||||
</p>
|
||||
<div
|
||||
className="bg-onboarding-background-200 mt-6 py-3 text-center hover:cursor-pointer text-custom-text-200 rounded-md text-sm font-medium border border-custom-border-200"
|
||||
onClick={() => {}}
|
||||
onClick={setTryDiffAccount}
|
||||
>
|
||||
Try a different email address
|
||||
</div>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
// next
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// headless ui
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// react-hook-form
|
||||
@ -24,6 +25,8 @@ import { ROLE } from "constants/workspace";
|
||||
// assets
|
||||
import user1 from "public/users/user-1.png";
|
||||
import user2 from "public/users/user-2.png";
|
||||
import userDark from "public/onboarding/user-dark.svg";
|
||||
import userLight from "public/onboarding/user-light.svg";
|
||||
|
||||
type Props = {
|
||||
finishOnboarding: () => Promise<void>;
|
||||
@ -48,13 +51,15 @@ type InviteMemberFormProps = {
|
||||
field: FieldArrayWithId<FormValues, "emails", "id">;
|
||||
fields: FieldArrayWithId<FormValues, "emails", "id">[];
|
||||
errors: any;
|
||||
isInvitationDisabled: boolean;
|
||||
setIsInvitationDisabled: (value: boolean) => void;
|
||||
};
|
||||
|
||||
// services
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
const { control, index, fields, remove, errors } = props;
|
||||
const { control, index, fields, remove, errors, isInvitationDisabled, setIsInvitationDisabled } = props;
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
@ -70,7 +75,6 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
control={control}
|
||||
name={`emails.${index}.email`}
|
||||
rules={{
|
||||
required: "Email ID is required",
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: "Invalid Email ID",
|
||||
@ -82,7 +86,32 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
name={`emails.${index}.email`}
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onChange={(event) => {
|
||||
if (event.target.value === "") {
|
||||
const validEmail = !fields
|
||||
.filter((ele) => {
|
||||
ele.id !== `emails.${index}.email`;
|
||||
})
|
||||
.map((ele) => ele.email)
|
||||
.includes("");
|
||||
if (validEmail) {
|
||||
setIsInvitationDisabled(false);
|
||||
} else {
|
||||
setIsInvitationDisabled(true);
|
||||
}
|
||||
} else if (
|
||||
isInvitationDisabled &&
|
||||
/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(event.target.value)
|
||||
) {
|
||||
setIsInvitationDisabled(false);
|
||||
} else if (
|
||||
!isInvitationDisabled &&
|
||||
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(event.target.value)
|
||||
) {
|
||||
setIsInvitationDisabled(true);
|
||||
}
|
||||
onChange(event);
|
||||
}}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.emails?.[index]?.email)}
|
||||
placeholder="Enter their email..."
|
||||
@ -173,7 +202,10 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||
export const InviteMembers: React.FC<Props> = (props) => {
|
||||
const { finishOnboarding, stepChange, workspace } = props;
|
||||
|
||||
const [isInvitationDisabled, setIsInvitationDisabled] = useState(true);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const {
|
||||
control,
|
||||
@ -198,7 +230,8 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
const onSubmit = async (formData: FormValues) => {
|
||||
if (!workspace) return;
|
||||
|
||||
const payload = { ...formData };
|
||||
let payload = { ...formData };
|
||||
payload = { emails: payload.emails.filter((email) => email.email !== "") };
|
||||
|
||||
await workspaceService
|
||||
.inviteWorkspace(workspace.slug, payload)
|
||||
@ -220,36 +253,41 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (fields.length === 0) {
|
||||
append([
|
||||
{ email: "", role: 15 },
|
||||
{ email: "", role: 15 },
|
||||
{ email: "", role: 15 },
|
||||
]);
|
||||
append(
|
||||
[
|
||||
{ email: "", role: 15 },
|
||||
{ email: "", role: 15 },
|
||||
{ email: "", role: 15 },
|
||||
],
|
||||
{
|
||||
focusIndex: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [fields, append]);
|
||||
|
||||
return (
|
||||
<div className="flex py-14">
|
||||
<div className="flex w-full py-14 ">
|
||||
<div
|
||||
className={`hidden lg:block w-1/4 p-3 ml-auto rounded bg-onboarding-gradient-secondary border border-onboarding-border-100 border-opacity-10`}
|
||||
className={`fixed ml-16 hidden lg:block w-1/5 p-4 pb-40 h-fit rounded bg-onboarding-gradient-300 border-x border-t border-onboarding-border-300 border-opacity-10`}
|
||||
>
|
||||
<p className="text-base text-onboarding-text-400 font-semibold">Members</p>
|
||||
|
||||
{Array.from({ length: 4 }).map(() => (
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<div className="w-8 h-8 flex justify-center items-center flex-shrink-0 rounded-full bg-onboarding-background-400">
|
||||
<User2 className="h-4 w-4 stroke-onboarding-background-300 fill-onboarding-background-400" />
|
||||
<div className="flex items-center gap-2 mt-6">
|
||||
<div className="h-8 w-8 flex justify-center items-center flex-shrink-0 rounded-full">
|
||||
<Image src={resolvedTheme === "dark" ? userDark : userLight} alt="user" className="object-cover" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="rounded-md h-2.5 my-2 bg-onboarding-background-100 w-2/3" />
|
||||
<div className="rounded-md h-2 bg-onboarding-background-400 w-1/2" />
|
||||
<div className="rounded-md h-2.5 bg-onboarding-background-400 my-2 w-1/2" />
|
||||
<div className="rounded-md h-2 bg-onboarding-background-100 w-1/3" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="relative mt-20 h-32">
|
||||
<div className="flex absolute bg-onboarding-background-200 p-2 rounded-full gap-x-2 border border-onboarding-border-100 w-full mt-1 -left-1/2">
|
||||
<div className="w-8 h-8 flex-shrink-0 rounded-full bg-custom-primary-10">
|
||||
<div className="mt-20 relative">
|
||||
<div className="flex absolute bg-onboarding-background-200 p-2 rounded-full shadow-onbording-shadow-sm gap-x-2 border border-onboarding-border-100 w-full mt-1 right-24">
|
||||
<div className="w-10 h-10 flex-shrink-0 rounded-full bg-custom-primary-10">
|
||||
<Image src={user2} alt="user" />
|
||||
</div>
|
||||
<div>
|
||||
@ -258,8 +296,8 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex absolute bg-onboarding-background-200 p-2 rounded-full gap-x-2 border border-onboarding-border-100 -left-1/3 mt-14 w-full">
|
||||
<div className="w-8 h-8 flex-shrink-0 rounded-full bg-custom-primary-10">
|
||||
<div className="flex absolute bg-onboarding-background-200 p-2 rounded-full shadow-onbording-shadow-sm gap-x-2 border border-onboarding-border-100 w-full right-12 mt-16">
|
||||
<div className="w-10 h-10 flex-shrink-0 rounded-full bg-custom-primary-10">
|
||||
<Image src={user1} alt="user" />
|
||||
</div>
|
||||
<div>
|
||||
@ -269,54 +307,64 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
className="px-7 sm:px-0 md:w-4/5 lg:w-1/2 mx-auto space-y-7 sm:space-y-10 overflow-hidden flex flex-col"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.code === "Enter") e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold">Invite your team to work with you</h2>
|
||||
<OnboardingStepIndicator step={2} />
|
||||
</div>
|
||||
|
||||
<div className="md:w-4/5 text-sm flex flex-col overflow-hidden">
|
||||
<div className="space-y-3 sm:space-y-4 mb-3 h-full overflow-y-auto">
|
||||
{fields.map((field, index) => (
|
||||
<InviteMemberForm
|
||||
control={control}
|
||||
errors={errors}
|
||||
field={field}
|
||||
fields={fields}
|
||||
index={index}
|
||||
remove={remove}
|
||||
key={field.id}
|
||||
/>
|
||||
))}
|
||||
<div className="lg:w-2/3 w-full ml-auto ">
|
||||
<form
|
||||
className="px-7 lg:px-0 ml-auto w-full lg:w-5/6 space-y-7 sm:space-y-10 mx-auto"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.code === "Enter") e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold">Invite your team to work with you</h2>
|
||||
<OnboardingStepIndicator step={3} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 outline-custom-primary-100 bg-transparent text-custom-primary-100 text-sm font-semibold py-2 pr-3"
|
||||
onClick={appendField}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add another
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="primary" type="submit" disabled={!isValid} loading={isSubmitting} size="md">
|
||||
{isSubmitting ? "Sending..." : "Send Invite"}
|
||||
</Button>
|
||||
{/* <Button variant="outline-primary" size="md" onClick={nextStep}>
|
||||
|
||||
<div className="xl:w-5/6 w-full text-sm">
|
||||
<div className="space-y-3 sm:space-y-4 mb-3">
|
||||
{fields.map((field, index) => (
|
||||
<InviteMemberForm
|
||||
isInvitationDisabled={isInvitationDisabled}
|
||||
setIsInvitationDisabled={(value: boolean) => setIsInvitationDisabled(value)}
|
||||
control={control}
|
||||
errors={errors}
|
||||
field={field}
|
||||
fields={fields}
|
||||
index={index}
|
||||
remove={remove}
|
||||
key={field.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 outline-custom-primary-100 bg-transparent text-custom-primary-100 text-sm font-semibold py-2 pr-3"
|
||||
onClick={appendField}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
Add another
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={isInvitationDisabled || !isValid}
|
||||
loading={isSubmitting}
|
||||
size="md"
|
||||
>
|
||||
{isSubmitting ? "Inviting..." : "Invite members"}
|
||||
</Button>
|
||||
{/* <Button variant="outline-primary" size="md" onClick={nextStep}>
|
||||
Copy invite link
|
||||
</Button> */}
|
||||
|
||||
<span className="text-sm text-onboarding-text-400 hover:cursor-pointer" onClick={nextStep}>
|
||||
Do this later
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<span className="text-sm text-onboarding-text-400 hover:cursor-pointer" onClick={nextStep}>
|
||||
Do this later
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// components
|
||||
@ -8,8 +9,6 @@ import OnboardingStepIndicator from "components/account/step-indicator";
|
||||
import { Workspace } from "./workspace";
|
||||
// types
|
||||
import { IWorkspace, TOnboardingSteps } from "types";
|
||||
// react-hook-form
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
type Props = {
|
||||
finishOnboarding: () => Promise<void>;
|
||||
@ -39,8 +38,8 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full">
|
||||
<div className="hidden lg:block w-3/12">
|
||||
<div className="flex w-full">
|
||||
<div className="h-full fixed hidden lg:block w-1/5 max-w-[320px]">
|
||||
<Controller
|
||||
control={control}
|
||||
name="name"
|
||||
@ -55,28 +54,29 @@ export const JoinWorkspaces: React.FC<Props> = ({ stepChange, setTryDiffAccount
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-1/2 md:w-4/5 md:px-0 px-7 my-16 mx-auto">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="font-semibold text-onboarding-text-200 text-xl sm:text-2xl">What will your workspace be?</p>
|
||||
<OnboardingStepIndicator step={1} />
|
||||
</div>
|
||||
<Workspace
|
||||
stepChange={stepChange}
|
||||
user={user}
|
||||
control={control}
|
||||
handleSubmit={handleSubmit}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
<div className="flex md:w-4/5 items-center my-8">
|
||||
<hr className="border-onboarding-border-100 w-full" />
|
||||
<p className="text-center text-sm text-custom-text-400 mx-3 flex-shrink-0">Or</p>
|
||||
<hr className="border-onboarding-border-100 w-full" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Invitations setTryDiffAccount={setTryDiffAccount} handleNextStep={handleNextStep} />
|
||||
<div className="lg:w-2/3 w-full ml-auto ">
|
||||
<div className="w-full lg:w-4/5 px-7 lg:px-0 my-16 mx-auto">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="font-semibold text-onboarding-text-200 text-xl sm:text-2xl">What will your workspace be?</p>
|
||||
<OnboardingStepIndicator step={1} />
|
||||
</div>
|
||||
<Workspace
|
||||
stepChange={stepChange}
|
||||
user={user}
|
||||
control={control}
|
||||
handleSubmit={handleSubmit}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
<div className="flex md:w-1/2 items-center my-8">
|
||||
<hr className="border-onboarding-border-100 w-full" />
|
||||
<p className="text-center text-sm text-custom-text-400 mx-3 flex-shrink-0">Or</p>
|
||||
<hr className="border-onboarding-border-100 w-full" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Invitations setTryDiffAccount={setTryDiffAccount} handleNextStep={handleNextStep} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,8 @@
|
||||
// react
|
||||
import React, { useState } from "react";
|
||||
// next
|
||||
import Image from "next/image";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Camera, User2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
@ -14,11 +13,11 @@ import OnboardingStepIndicator from "components/account/step-indicator";
|
||||
import { IUser } from "types";
|
||||
// constants
|
||||
import { TIME_ZONES } from "constants/timezones";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// assets
|
||||
import IssuesSvg from "public/onboarding/onboarding-issues.svg";
|
||||
import { ImageUploadModal } from "components/core";
|
||||
// icons
|
||||
import { Camera, User2 } from "lucide-react";
|
||||
|
||||
const defaultValues: Partial<IUser> = {
|
||||
first_name: "",
|
||||
@ -36,14 +35,31 @@ const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
|
||||
content: timeZone.label,
|
||||
}));
|
||||
|
||||
const useCases = [
|
||||
"Build Products",
|
||||
"Manage Feedbacks",
|
||||
"Service delivery",
|
||||
"Field force management",
|
||||
"Code Repository Integration",
|
||||
"Bug Tracking",
|
||||
"Test Case Management",
|
||||
"Rescource allocation",
|
||||
];
|
||||
|
||||
const fileService = new FileService();
|
||||
|
||||
export const UserDetails: React.FC<Props> = observer((props) => {
|
||||
const { user } = props;
|
||||
const [isRemoving, setIsRemoving] = useState(false);
|
||||
const [selectedUsecase, setSelectedUsecase] = useState<number | null>();
|
||||
const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false);
|
||||
const { user: userStore } = useMobxStore();
|
||||
|
||||
const {
|
||||
user: userStore,
|
||||
workspace: { workspaces },
|
||||
} = useMobxStore();
|
||||
const workspaceName = workspaces ? workspaces[0]?.name : "New Workspace";
|
||||
const {
|
||||
getValues,
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
@ -69,28 +85,28 @@ export const UserDetails: React.FC<Props> = observer((props) => {
|
||||
|
||||
await userStore.updateCurrentUser(payload);
|
||||
};
|
||||
const handleDelete = (url: string | null | undefined) => {
|
||||
if (!url) return;
|
||||
|
||||
const useCases = [
|
||||
"Build Products",
|
||||
"Manage Feedbacks",
|
||||
"Service delivery",
|
||||
"Field force management",
|
||||
"Code Repository Integration",
|
||||
"Bug Tracking",
|
||||
"Test Case Management",
|
||||
"Rescource allocation",
|
||||
];
|
||||
setIsRemoving(true);
|
||||
fileService.deleteUserFile(url).finally(() => {
|
||||
setValue("avatar", "");
|
||||
setIsRemoving(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full space-y-7 sm:space-y-10 overflow-y-auto flex ">
|
||||
<div className="hidden lg:block w-3/12">
|
||||
<DummySidebar showProject workspaceName="New Workspace" />
|
||||
<div className="w-full h-full space-y-7 sm:space-y-10 overflow-y-auto flex ">
|
||||
<div className="h-full fixed hidden lg:block w-1/5 max-w-[320px]">
|
||||
<DummySidebar showProject workspaceName={workspaceName} />
|
||||
</div>
|
||||
<ImageUploadModal
|
||||
isOpen={isImageUploadModalOpen}
|
||||
onClose={() => setIsImageUploadModalOpen(false)}
|
||||
isRemoving={isRemoving}
|
||||
handleDelete={() => {}}
|
||||
handleDelete={() => {
|
||||
handleDelete(getValues("avatar"));
|
||||
}}
|
||||
onSuccess={(url) => {
|
||||
setValue("avatar", url);
|
||||
setIsImageUploadModalOpen(false);
|
||||
@ -98,99 +114,102 @@ export const UserDetails: React.FC<Props> = observer((props) => {
|
||||
value={watch("avatar") !== "" ? watch("avatar") : undefined}
|
||||
userImage
|
||||
/>
|
||||
<div className="flex lg:w-3/5 md:w-4/5 md:px-0 px-7 mx-auto flex-col">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="md:w-11/12 mx-auto">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="font-semibold text-xl sm:text-2xl">What do we call you? </p>
|
||||
<OnboardingStepIndicator step={2} />
|
||||
</div>
|
||||
<div className="flex mt-5 w-full ">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
{!watch("avatar") || watch("avatar") === "" ? (
|
||||
<div className="h-16 hover:cursor-pointer justify-center items-center flex w-16 rounded-full flex-shrink-0 mr-3 relative bg-onboarding-background-300">
|
||||
<div className="h-6 w-6 flex justify-center items-center bottom-1 border border-onboarding-border-100 -right-1 bg-onboarding-background-100 rounded-full absolute">
|
||||
<Camera className="h-4 w-4 stroke-onboarding-background-400" />
|
||||
<div className="lg:w-2/3 w-full flex flex-col justify-between ml-auto ">
|
||||
<div className="flex lg:w-4/5 md:px-0 px-7 pt-3 mx-auto flex-col">
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="md:w-11/12 ml-auto">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="font-semibold text-xl sm:text-2xl">What do we call you? </p>
|
||||
<OnboardingStepIndicator step={2} />
|
||||
</div>
|
||||
<div className="flex mt-6 w-full ">
|
||||
<button type="button" onClick={() => setIsImageUploadModalOpen(true)}>
|
||||
{!watch("avatar") || watch("avatar") === "" ? (
|
||||
<div className="h-16 hover:cursor-pointer justify-center items-center flex w-16 rounded-full flex-shrink-0 mr-3 relative bg-onboarding-background-300">
|
||||
<div className="h-6 w-6 flex justify-center items-center bottom-1 border border-onboarding-border-100 -right-1 bg-onboarding-background-100 rounded-full absolute">
|
||||
<Camera className="h-4 w-4 stroke-onboarding-background-400" />
|
||||
</div>
|
||||
<User2 className="h-10 w-10 stroke-onboarding-background-300 fill-onboarding-background-400" />
|
||||
</div>
|
||||
<User2 className="h-10 w-10 stroke-onboarding-background-300 fill-onboarding-background-400" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative h-16 w-16 overflow-hidden mr-3">
|
||||
<img
|
||||
src={watch("avatar")}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded-full"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
alt={user?.display_name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<div className="relative h-16 w-16 overflow-hidden mr-3">
|
||||
<img
|
||||
src={watch("avatar")}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded-full"
|
||||
onClick={() => setIsImageUploadModalOpen(true)}
|
||||
alt={user?.display_name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="my-2 bg-onboarding-background-200 w-full mr-10 rounded-md flex text-sm">
|
||||
<div className="my-2 bg-onboarding-background-200 w-full mr-10 rounded-md flex text-sm">
|
||||
<Controller
|
||||
control={control}
|
||||
name="first_name"
|
||||
rules={{
|
||||
required: "First name is required",
|
||||
maxLength: {
|
||||
value: 24,
|
||||
message: "First name cannot exceed the limit of 24 characters",
|
||||
},
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
value={value}
|
||||
autoFocus={true}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.first_name)}
|
||||
placeholder="Enter your full name..."
|
||||
className="w-full focus:border-custom-primary-100 border-onboarding-border-100"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-14 mb-10">
|
||||
<Controller
|
||||
control={control}
|
||||
name="first_name"
|
||||
rules={{
|
||||
required: "First name is required",
|
||||
maxLength: {
|
||||
value: 24,
|
||||
message: "First name cannot exceed the limit of 24 characters",
|
||||
},
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<Input
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
value={value}
|
||||
autoFocus={true}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.first_name)}
|
||||
placeholder="Enter your full name..."
|
||||
className="w-full focus:border-custom-primary-100 border-onboarding-border-100"
|
||||
/>
|
||||
render={({ field: { value } }) => (
|
||||
<p className="font-medium text-onboarding-text-200 text-xl sm:text-2xl p-0">
|
||||
And how will you use Plane{value.length > 0 ? ", " : ""}
|
||||
{value}?
|
||||
</p>
|
||||
)}
|
||||
/>
|
||||
|
||||
<p className="font-medium text-onboarding-text-300 text-sm my-3">Choose just one</p>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="use_case"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="flex flex-wrap break-all overflow-auto">
|
||||
{useCases.map((useCase) => (
|
||||
<div
|
||||
className={`border mb-3 hover:cursor-pointer hover:bg-onboarding-background-300/30 flex-shrink-0 ${
|
||||
value === useCase ? "border-custom-primary-100" : "border-onboarding-border-100"
|
||||
} mr-3 rounded-sm p-3 text-sm font-medium`}
|
||||
onClick={() => onChange(useCase)}
|
||||
>
|
||||
{useCase}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-14 mb-10">
|
||||
<Controller
|
||||
control={control}
|
||||
name="first_name"
|
||||
render={({ field: { value } }) => (
|
||||
<p className="font-medium text-onboarding-text-200 text-xl sm:text-2xl p-0">
|
||||
And how will you use Plane{value.length>0?", ":""}{value}?
|
||||
</p>
|
||||
)}
|
||||
/>
|
||||
|
||||
<p className="font-medium text-onboarding-text-300 text-sm my-3">Choose just one</p>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="use_case"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="flex flex-wrap break-all overflow-auto">
|
||||
{useCases.map((useCase) => (
|
||||
<div
|
||||
className={`border mb-3 hover:cursor-pointer hover:bg-onboarding-background-300/30 flex-shrink-0 ${
|
||||
value === useCase ? "border-custom-primary-100" : "border-onboarding-border-100"
|
||||
} mr-3 rounded-sm p-3 text-sm font-medium`}
|
||||
onClick={() => onChange(useCase)}
|
||||
>
|
||||
{useCase}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button variant="primary" type="submit" size="md" disabled={!isValid} loading={isSubmitting}>
|
||||
{isSubmitting ? "Updating..." : "Continue"}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="mt-3 flex ml-auto">
|
||||
<Button variant="primary" type="submit" size="md" disabled={!isValid} loading={isSubmitting}>
|
||||
{isSubmitting ? "Updating..." : "Continue"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="md:w-11/12 relative flex justify-end bottom-0 ml-auto">
|
||||
<Image src={IssuesSvg} className="w-2/3 h-[w-2/3] object-cover" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Control, Controller, FieldErrors, UseFormHandleSubmit, UseFormSetValue } from "react-hook-form";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
@ -11,8 +12,6 @@ import { WorkspaceService } from "services/workspace.service";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// constants
|
||||
import { RESTRICTED_URLS } from "constants/workspace";
|
||||
// react-hook-form
|
||||
import { Control, Controller, FieldErrors, UseFormHandleSubmit, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
type Props = {
|
||||
stepChange: (steps: Partial<TOnboardingSteps>) => Promise<void>;
|
||||
@ -149,8 +148,12 @@ export const Workspace: React.FC<Props> = (props) => {
|
||||
onChange={(e) => {
|
||||
const host = window.location.host;
|
||||
const slug = e.currentTarget.value.split("/");
|
||||
/^[a-zA-Z0-9_-]+$/.test(slug[slug.length - 1]) ? setInvalidSlug(false) : setInvalidSlug(true);
|
||||
setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`);
|
||||
if (slug.length > 1) {
|
||||
/^[a-zA-Z0-9_-]+$/.test(slug[slug.length - 1]) ? setInvalidSlug(false) : setInvalidSlug(true);
|
||||
setValue("slug", `${host}/${slug[slug.length - 1].toLocaleLowerCase().trim().replace(/ /g, "-")}`);
|
||||
} else {
|
||||
setValue("slug", `${host}/`);
|
||||
}
|
||||
}}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.slug)}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/router";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
@ -22,12 +24,14 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||
import signInIssues from "public/onboarding/onboarding-issues.svg";
|
||||
// types
|
||||
import { IUser, IUserSettings } from "types";
|
||||
// icons
|
||||
import { Lightbulb } from "lucide-react";
|
||||
|
||||
export type AuthType = "sign-in" | "sign-up";
|
||||
|
||||
const authService = new AuthService();
|
||||
|
||||
export const SignInView = observer(() => {
|
||||
// states
|
||||
const [authType, setAuthType] = useState<AuthType>("sign-up");
|
||||
const {
|
||||
user: { fetchCurrentUser, fetchCurrentUserSettings },
|
||||
appConfig: { envConfig },
|
||||
@ -39,6 +43,7 @@ export const SignInView = observer(() => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
// computed.
|
||||
const enableEmailPassword =
|
||||
@ -184,79 +189,112 @@ export const SignInView = observer(() => {
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`bg-onboarding-gradient-primary h-full overflow-y-auto`}>
|
||||
<div className="sm:py-5 pl-8 pb-4 sm:pl-16 lg:pl-28 ">
|
||||
<div className="flex text-3xl items-center mt-16 font-semibold">
|
||||
<div className="h-[30px] w-[30px] mr-2">
|
||||
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" />
|
||||
</div>
|
||||
Plane
|
||||
<div className={`bg-onboarding-gradient-100 h-full w-full`}>
|
||||
<div className="flex items-center justify-between sm:py-5 px-8 pb-4 sm:px-16 lg:px-28 ">
|
||||
<div className="flex gap-x-2 py-10 items-center">
|
||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
||||
<span className="font-semibold text-2xl sm:text-3xl">Plane</span>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
{authType === "sign-in" && (
|
||||
<div className="mx-auto text-right text-onboarding-text-300 text-sm">
|
||||
New to Plane?{" "}
|
||||
<p
|
||||
className="text-custom-primary-100 hover text-base font-medium hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
setAuthType("sign-up");
|
||||
}}
|
||||
>
|
||||
Create a new account
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:w-2/3 sm:w-4/5 rounded-md mx-auto shadow-sm border border-custom-border-200">
|
||||
<div className={`p-4`}>
|
||||
<div className={`px-7 sm:px-0 bg-onboarding-gradient-secondary h-full pt-32 pb-20 rounded-md`}>
|
||||
{!envConfig ? (
|
||||
<div className="pt-10 mx-auto flex justify-center">
|
||||
<div>
|
||||
<Loader className="space-y-4 w-full pb-4 mx-auto">
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
</Loader>
|
||||
<div className="h-full bg-onboarding-gradient-100 md:w-2/3 sm:w-4/5 px-4 pt-4 rounded-t-md mx-auto shadow-sm border-x border-t border-custom-border-200 ">
|
||||
<div className={`px-7 sm:px-0 bg-onboarding-gradient-200 h-full pt-24 pb-56 rounded-t-md overflow-auto`}>
|
||||
{!envConfig ? (
|
||||
<div className="pt-10 mx-auto flex justify-center">
|
||||
<div>
|
||||
<Loader className="space-y-4 w-full pb-4 mx-auto">
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
</Loader>
|
||||
|
||||
<Loader className="space-y-4 w-full pt-4 mx-auto">
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
</Loader>
|
||||
</div>
|
||||
<Loader className="space-y-4 w-full pt-4 mx-auto">
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
<Loader.Item height="46px" width="360px" />
|
||||
</Loader>
|
||||
</div>
|
||||
) : (
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<>
|
||||
<>
|
||||
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
|
||||
{envConfig?.magic_login && (
|
||||
<div className="flex flex-col divide-y divide-custom-border-200">
|
||||
<div className="pb-2">
|
||||
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
|
||||
</div>
|
||||
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
|
||||
{envConfig?.magic_login && (
|
||||
<div className="sm:w-96 mx-auto flex flex-col divide-y divide-custom-border-200">
|
||||
<div className="pb-2">
|
||||
<EmailCodeForm authType={authType} handleSignIn={handleEmailCodeSignIn} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex sm:w-96 items-center mt-4 mx-auto">
|
||||
<hr className={`border-onboarding-border-100 w-full`} />
|
||||
<p className="text-center text-sm text-onboarding-text-400 mx-3 flex-shrink-0">
|
||||
Or continue with
|
||||
</p>
|
||||
<hr className={`border-onboarding-border-100 w-full`} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-96 mx-auto overflow-hidden">
|
||||
{envConfig?.google_client_id && (
|
||||
<GoogleLoginButton clientId={envConfig?.google_client_id} handleSignIn={handleGoogleSignIn} />
|
||||
)}
|
||||
{envConfig?.github_client_id && (
|
||||
<GithubLoginButton clientId={envConfig?.github_client_id} handleSignIn={handleGitHubSignIn} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<div className={`flex py-2 bg-onboarding-background-100 mx-auto rounded-sm sm:w-96 mt-16`}>
|
||||
<Lightbulb className="h-7 w-7 mr-2 mx-3" />
|
||||
<p className={`text-sm text-left text-onboarding-text-200`}>
|
||||
Try the latest features, like Tiptap editor, to write compelling responses.{" "}
|
||||
<span className="font-medium underline hover:cursor-pointer" onClick={() => {}}>
|
||||
See new features
|
||||
</span>
|
||||
)}
|
||||
<div className="flex sm:w-96 items-center mt-4 mx-auto">
|
||||
<hr className={`border-onboarding-border-100 w-full`} />
|
||||
<p className="text-center text-sm text-onboarding-text-400 mx-3 flex-shrink-0">
|
||||
Or continue with
|
||||
</p>
|
||||
<hr className={`border-onboarding-border-100 w-full`} />
|
||||
</div>
|
||||
<div className="flex justify-center sm:w-96 sm:h-64 object-cover mt-8 mx-auto rounded-md ">
|
||||
<Image
|
||||
src={signInIssues}
|
||||
alt="Plane Logo"
|
||||
className={`flex object-cover rounded-md bg-onboarding-background-100`}
|
||||
/>
|
||||
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:flex-row sm:w-96 mx-auto overflow-hidden">
|
||||
{envConfig?.google_client_id && (
|
||||
<GoogleLoginButton clientId={envConfig?.google_client_id} handleSignIn={handleGoogleSignIn} />
|
||||
)}
|
||||
{envConfig?.github_client_id && (
|
||||
<GithubLoginButton
|
||||
authType={authType}
|
||||
clientId={envConfig?.github_client_id}
|
||||
handleSignIn={handleGitHubSignIn}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{authType === "sign-up" && (
|
||||
<div className="sm:w-96 text-center mx-auto mt-6 text-onboarding-text-400 text-sm">
|
||||
Already using Plane?{" "}
|
||||
<span
|
||||
className="text-custom-primary-80 hover text-sm font-medium underline hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
setAuthType("sign-in");
|
||||
}}
|
||||
>
|
||||
Sign in
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`flex py-2 bg-onboarding-background-100 border border-onboarding-border-200 mx-auto rounded-[3.5px] sm:w-96 mt-16`}
|
||||
>
|
||||
<Lightbulb className="h-7 w-7 mr-2 mx-3" />
|
||||
<p className={`text-sm text-left text-onboarding-text-100`}>
|
||||
Try the latest features, like Tiptap editor, to write compelling responses.{" "}
|
||||
<span className="font-medium text-sm underline hover:cursor-pointer" onClick={() => {}}>
|
||||
See new features
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center border border-onboarding-border-200 sm:w-96 sm:h-52 object-cover mt-8 mx-auto rounded-md bg-onboarding-background-100 ">
|
||||
<Image
|
||||
src={signInIssues}
|
||||
alt="Plane Issues"
|
||||
className={`flex object-cover rounded-md ${
|
||||
resolvedTheme === "dark" ? "bg-onboarding-background-100" : "bg-custom-primary-70"
|
||||
} `}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,7 +2,9 @@ import { Fragment } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { mutate } from "swr";
|
||||
import { Check, ChevronDown, LogOut, Plus, Settings, UserCircle2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
@ -57,6 +59,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
} = useMobxStore();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
|
||||
updateCurrentUser({
|
||||
@ -78,6 +81,8 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
await authService
|
||||
.signOut()
|
||||
.then(() => {
|
||||
mutate("CURRENT_USER_DETAILS", null);
|
||||
setTheme("system");
|
||||
router.push("/");
|
||||
})
|
||||
.catch(() =>
|
||||
@ -251,7 +256,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
{!sidebarCollapsed && (
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
<Menu.Button className="grid place-items-center outline-none">
|
||||
<Avatar
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={currentUser?.avatar}
|
||||
size={24}
|
||||
|
@ -3,29 +3,28 @@ import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
import useSWR from "swr";
|
||||
// keys
|
||||
import { CURRENT_USER } from "constants/fetch-keys";
|
||||
// services
|
||||
import { UserService } from "services/user.service";
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
// types
|
||||
import type { IUser } from "types";
|
||||
// mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const userService = new UserService();
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "admin") => {
|
||||
const router = useRouter();
|
||||
const { next_url } = router.query as { next_url: string };
|
||||
const { next_url } = router.query;
|
||||
|
||||
const [isRouteAccess, setIsRouteAccess] = useState(true);
|
||||
const {
|
||||
user: { fetchCurrentUser },
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
data: user,
|
||||
isLoading,
|
||||
error,
|
||||
mutate,
|
||||
} = useSWR<IUser>(CURRENT_USER, () => userService.currentUser(), {
|
||||
} = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
||||
refreshInterval: 0,
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
@ -91,7 +90,7 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm
|
||||
if (!isLoading) {
|
||||
setIsRouteAccess(() => true);
|
||||
if (user) {
|
||||
if (next_url) router.push(next_url);
|
||||
if (next_url) router.push(next_url.toString());
|
||||
else handleUserRouteAuthentication();
|
||||
} else {
|
||||
if (routeAuth === "sign-in") {
|
||||
|
@ -108,8 +108,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
|
||||
}}
|
||||
/>
|
||||
{user && step !== null ? (
|
||||
<div className={` bg-onboarding-gradient-primary h-full overflow-y-auto`}>
|
||||
<div className="sm:py-14 py-10 px-4 sm:px-7 md:px-14 lg:pl-28 lg:pr-24 flex items-center">
|
||||
<div className={`bg-onboarding-gradient-100 h-full flex flex-col fixed w-full`}>
|
||||
<div className="sm:pt-14 sm:pb-8 py-10 px-4 sm:px-7 md:px-14 lg:pl-28 lg:pr-24 flex items-center">
|
||||
<div className="w-full flex items-center justify-between font-semibold ">
|
||||
<div className="text-3xl flex items-center gap-x-1">
|
||||
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" height={30} width={30} />
|
||||
@ -164,28 +164,26 @@ const OnboardingPage: NextPageWithLayout = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full lg:w-4/5 xl:w-3/4 sm:w-4/5 rounded-md mx-auto shadow-sm border border-custom-border-200">
|
||||
<div className={`bg-onboarding-gradient-primary p-4`}>
|
||||
<div className={`bg-onboarding-gradient-secondary h-full rounded-md`}>
|
||||
{step === 1 ? (
|
||||
<JoinWorkspaces
|
||||
setTryDiffAccount={() => {
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
finishOnboarding={finishOnboarding}
|
||||
stepChange={stepChange}
|
||||
/>
|
||||
) : step === 2 ? (
|
||||
<UserDetails user={user} />
|
||||
) : (
|
||||
<InviteMembers
|
||||
finishOnboarding={finishOnboarding}
|
||||
stepChange={stepChange}
|
||||
user={user}
|
||||
workspace={workspaces?.[0]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full h-full lg:w-4/5 xl:w-3/4 bg-onboarding-gradient-100 pt-4 px-4 sm:w-4/5 rounded-t-md mx-auto shadow-sm border-x border-t border-custom-border-200 overflow-auto">
|
||||
<div className={`h-full w-full bg-onboarding-gradient-200 rounded-t-md overflow-auto`}>
|
||||
{step === 1 ? (
|
||||
<JoinWorkspaces
|
||||
setTryDiffAccount={() => {
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
finishOnboarding={finishOnboarding}
|
||||
stepChange={stepChange}
|
||||
/>
|
||||
) : step === 2 ? (
|
||||
<UserDetails user={user} />
|
||||
) : (
|
||||
<InviteMembers
|
||||
finishOnboarding={finishOnboarding}
|
||||
stepChange={stepChange}
|
||||
user={user}
|
||||
workspace={workspaces?.[0]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
4
web/public/emoji/project-emoji.svg
Normal file
4
web/public/emoji/project-emoji.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.5554 7.55556C15.5554 11.7284 9.67856 16 7.99989 16C6.32078 16 0.444336 11.7284 0.444336 7.55556C0.444336 3.38267 3.827 0 7.99989 0C12.1728 0 15.5554 3.38267 15.5554 7.55556Z" fill="#CCD6DD"/>
|
||||
<path d="M6.00138 6.5978C7.38982 7.98625 7.95249 9.67469 7.25827 10.3689C6.56405 11.0631 4.8756 10.5009 3.48716 9.11202C2.09827 7.72358 1.5356 6.03514 2.23027 5.34091C2.92493 4.64669 4.61293 5.20936 6.00138 6.5978ZM9.99871 6.5978C8.61027 7.98625 8.0476 9.67469 8.74182 10.3689C9.43605 11.0631 11.1245 10.5009 12.5129 9.11202C13.9018 7.72314 14.4645 6.03514 13.7698 5.34091C13.0756 4.64669 11.3872 5.20891 9.99871 6.5978ZM8.00005 13.7778C6.95693 13.7778 6.41116 13.2622 6.35249 13.2031C6.26856 13.1204 6.22095 13.0077 6.22011 12.8898C6.21928 12.772 6.2653 12.6586 6.34805 12.5747C6.43079 12.4908 6.54349 12.4431 6.66135 12.4423C6.7792 12.4415 6.89256 12.4875 6.97649 12.5702C6.99916 12.5911 7.33471 12.8889 8.00005 12.8889C8.67516 12.8889 9.01027 12.5827 9.02449 12.5694C9.10979 12.4893 9.22304 12.4458 9.34001 12.4482C9.45697 12.4506 9.56834 12.4987 9.65027 12.5822C9.73156 12.6652 9.77686 12.7769 9.77636 12.893C9.77586 13.0092 9.7296 13.1204 9.6476 13.2027C9.58893 13.2622 9.04316 13.7778 8.00005 13.7778Z" fill="#292F33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
web/public/logos/github-dark.svg
Normal file
3
web/public/logos/github-dark.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 0.890625C8.35093 0.890625 7.21312 1.11695 6.15152 1.55668C5.08992 1.99641 4.12533 2.64093 3.31282 3.45344C1.67187 5.09438 0.75 7.31998 0.75 9.64062C0.75 13.5081 3.26125 16.7894 6.735 17.9531C7.1725 18.0231 7.3125 17.7519 7.3125 17.5156V16.0369C4.88875 16.5619 4.3725 14.8644 4.3725 14.8644C3.97 13.8494 3.40125 13.5781 3.40125 13.5781C2.605 13.0356 3.4625 13.0531 3.4625 13.0531C4.3375 13.1144 4.80125 13.9544 4.80125 13.9544C5.5625 15.2844 6.84875 14.8906 7.3475 14.6806C7.42625 14.1119 7.65375 13.7269 7.89875 13.5081C5.95625 13.2894 3.9175 12.5369 3.9175 9.20312C3.9175 8.23187 4.25 7.45312 4.81875 6.83187C4.73125 6.61312 4.425 5.70312 4.90625 4.52188C4.90625 4.52188 5.64125 4.28562 7.3125 5.41438C8.00375 5.22188 8.75625 5.12563 9.5 5.12563C10.2437 5.12563 10.9963 5.22188 11.6875 5.41438C13.3588 4.28562 14.0938 4.52188 14.0938 4.52188C14.575 5.70312 14.2688 6.61312 14.1813 6.83187C14.75 7.45312 15.0825 8.23187 15.0825 9.20312C15.0825 12.5456 13.035 13.2806 11.0838 13.4994C11.3988 13.7706 11.6875 14.3044 11.6875 15.1181V17.5156C11.6875 17.7519 11.8275 18.0319 12.2738 17.9531C15.7475 16.7806 18.25 13.5081 18.25 9.64062C18.25 8.49156 18.0237 7.35374 17.5839 6.29214C17.1442 5.23055 16.4997 4.26595 15.6872 3.45344C14.8747 2.64093 13.9101 1.99641 12.8485 1.55668C11.7869 1.11695 10.6491 0.890625 9.5 0.890625Z" fill="#B0B4BB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
14
web/public/onboarding/user-dark.svg
Normal file
14
web/public/onboarding/user-dark.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg width="30" height="29" viewBox="0 0 30 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_422_19191)">
|
||||
<rect width="30" height="29" rx="14.5" fill="#5B6069"/>
|
||||
<path d="M24.6255 24.1658C24.6255 21.8239 23.4282 19.578 21.6591 17.9221C19.8899 16.2662 17.4904 15.3359 14.9884 15.3359C12.4864 15.3359 10.0869 16.2662 8.31776 17.9221C6.5486 19.578 5.55469 21.8239 5.55469 24.1658" fill="#43484F"/>
|
||||
<path d="M24.6255 24.1658C24.6255 21.8239 23.4282 19.578 21.6591 17.9221C19.8899 16.2662 17.4904 15.3359 14.9884 15.3359C12.4864 15.3359 10.0869 16.2662 8.31776 17.9221C6.5486 19.578 5.55469 21.8239 5.55469 24.1658C6.80126 28.8931 22.7194 29.9478 24.6255 24.1658Z" stroke="#43484F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24.5002 23.2546C24.799 24.4372 23.5063 25.9588 21.7371 26.9349C19.9679 27.911 17.8578 28.3064 15.3558 28.3064C12.8538 28.3064 10.4543 27.758 8.68516 26.7819C6.85637 26.0934 5.00781 24.3616 5.92208 23.1016L15.3558 23.1016L24.5002 23.2546Z" fill="#43484F"/>
|
||||
<path d="M15.0029 14.9375C18.1095 14.9375 20.6279 12.4191 20.6279 9.3125C20.6279 6.2059 18.1095 3.6875 15.0029 3.6875C11.8963 3.6875 9.37793 6.2059 9.37793 9.3125C9.37793 12.4191 11.8963 14.9375 15.0029 14.9375Z" fill="#43484F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_422_19191">
|
||||
<rect width="30" height="29" rx="14.5" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
14
web/public/onboarding/user-light.svg
Normal file
14
web/public/onboarding/user-light.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg width="30" height="29" viewBox="0 0 30 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_11_12473)">
|
||||
<rect width="30" height="29" rx="14.5" fill="#ECF1FF"/>
|
||||
<path d="M24.6255 24.1658C24.6255 21.8239 23.4282 19.578 21.6591 17.9221C19.8899 16.2662 17.4904 15.3359 14.9884 15.3359C12.4864 15.3359 10.0869 16.2662 8.31776 17.9221C6.5486 19.578 5.55469 21.8239 5.55469 24.1658" fill="#C2D0F2"/>
|
||||
<path d="M24.6255 24.1658C24.6255 21.8239 23.4282 19.578 21.6591 17.9221C19.8899 16.2662 17.4904 15.3359 14.9884 15.3359C12.4864 15.3359 10.0869 16.2662 8.31776 17.9221C6.5486 19.578 5.55469 21.8239 5.55469 24.1658C6.80126 28.8931 22.7194 29.9478 24.6255 24.1658Z" stroke="#C2D0F2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24.5002 23.2546C24.799 24.4372 23.5063 25.9588 21.7371 26.9349C19.9679 27.911 17.8578 28.3064 15.3558 28.3064C12.8538 28.3064 10.4543 27.758 8.68516 26.7819C6.85637 26.0934 5.00781 24.3616 5.92208 23.1016L15.3558 23.1016L24.5002 23.2546Z" fill="#C2D0F2"/>
|
||||
<path d="M15.0029 14.9375C18.1095 14.9375 20.6279 12.4191 20.6279 9.3125C20.6279 6.2059 18.1095 3.6875 15.0029 3.6875C11.8963 3.6875 9.37793 6.2059 9.37793 9.3125C9.37793 12.4191 11.8963 14.9375 15.0029 14.9375Z" fill="#CAD9FF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_11_12473">
|
||||
<rect width="30" height="29" rx="14.5" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -127,8 +127,10 @@
|
||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||
|
||||
/* onboarding colors */
|
||||
--gradient-onboarding-primary: linear-gradient(106deg, #F2F6FF 29.8%, #E1EAFF 99.34%);
|
||||
--gradient-onboarding-secondary: linear-gradient(129deg, rgba(255, 255, 255, 0.00) -22.23%, rgba(255, 255, 255, 0.80) 62.98%);
|
||||
--gradient-onboarding-100: linear-gradient(106deg, #F2F6FF 29.8%, #E1EAFF 99.34%);
|
||||
--gradient-onboarding-200: linear-gradient(129deg, rgba(255, 255, 255, 0.00) -22.23%, rgba(255, 255, 255, 0.80) 62.98%);
|
||||
--gradient-onboarding-300: linear-gradient(164deg, #FFF 4.25%, rgba(255, 255, 255, 0.06) 93.5%);
|
||||
--gradient-onboarding-400: linear-gradient(129deg, rgba(255, 255, 255, 0.00) -22.23%, rgba(255, 255, 255, 0.80) 62.98%);
|
||||
|
||||
--color-onboarding-text-100: 23, 23, 23;
|
||||
--color-onboarding-text-200: 58, 58, 58;
|
||||
@ -142,6 +144,9 @@
|
||||
|
||||
--color-onboarding-border-100: 229, 229, 229;
|
||||
--color-onboarding-border-200: 217, 228, 255;
|
||||
--color-onboarding-border-300: 229, 229, 229, 0.5;
|
||||
|
||||
--color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(126, 139, 171, 0.10);
|
||||
|
||||
}
|
||||
|
||||
@ -194,8 +199,9 @@
|
||||
|
||||
|
||||
/* onboarding colors */
|
||||
--gradient-onboarding-primary: linear-gradient(106deg, #18191B 25.17%, #18191B 99.34%);
|
||||
--gradient-onboarding-secondary: linear-gradient(129deg, rgba(47, 49, 53, 0.80) -22.23%, rgba(33, 34, 37, 0.80) 62.98%);
|
||||
--gradient-onboarding-100: linear-gradient(106deg, #18191B 25.17%, #18191B 99.34%);
|
||||
--gradient-onboarding-200: linear-gradient(129deg, rgba(47, 49, 53, 0.80) -22.23%, rgba(33, 34, 37, 0.80) 62.98%);
|
||||
--gradient-onboarding-300: linear-gradient(167deg, rgba(47, 49, 53, 0.45) 19.22%, #212225 98.48%);
|
||||
|
||||
--color-onboarding-text-100: 237, 238, 240;
|
||||
--color-onboarding-text-200: 176, 180, 187;
|
||||
@ -209,6 +215,10 @@
|
||||
|
||||
--color-onboarding-border-100: 54, 58, 64;
|
||||
--color-onboarding-border-200: 54, 58, 64;
|
||||
--color-onboarding-border-300: 34, 35, 38, 0.5;
|
||||
|
||||
--color-onboarding-shadow-sm: 0px 4px 20px 0px rgba(39, 44, 56, 0.10);
|
||||
|
||||
}
|
||||
|
||||
[data-theme="dark-contrast"] {
|
||||
|
Loading…
Reference in New Issue
Block a user