fix: auth redirection issue fixes when user is logged in (#2499)

* fix: auth redirection issues

* fix: redirect flickering fix

* chore: sign in page ui improvement and redirection fix (#2501)

* style: sign in page ui improvement

* chore: sign up redirection added and ui improvement

* chore: redirection validation and create workspace form fix (#2504)

* chore: sign in redirection validation

* fix: create workspace form input fix

* chore: code refactor

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
This commit is contained in:
sriram veeraghanta 2023-10-20 17:10:17 +05:30 committed by GitHub
parent d78b4dccf3
commit 9f1fd2327a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 99 additions and 52 deletions

View File

@ -14,7 +14,7 @@ export const LayersIcon: React.FC<ISvgIcons> = ({
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
{...rest} {...rest}
> >
<g clip-path="url(#clip0_7258_81938)"> <g clipPath="url(#clip0_7258_81938)">
<path <path
d="M16.5953 6.69606L16.6072 5.17376L6.85812 8.92381L6.85812 19.4238L9.00319 18.6961" d="M16.5953 6.69606L16.6072 5.17376L6.85812 8.92381L6.85812 19.4238L9.00319 18.6961"
strokeLinecap="round" strokeLinecap="round"

View File

@ -203,7 +203,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
variant="primary" variant="primary"
type="submit" type="submit"
className="w-full" className="w-full"
size="md" size="xl"
onClick={handleSubmit(handleSignin)} onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty} disabled={!isValid && isDirty}
loading={isLoading} loading={isLoading}

View File

@ -51,7 +51,7 @@ export const EmailForgotPasswordForm: FC<IEmailForgotPasswordForm> = (props) =>
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.email)} hasError={Boolean(errors.email)}
placeholder="Enter registered email address.." placeholder="Enter registered email address.."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
)} )}
/> />

View File

@ -56,7 +56,7 @@ export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.email)} hasError={Boolean(errors.email)}
placeholder="Enter your email address..." placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
)} )}
/> />
@ -77,7 +77,7 @@ export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
onChange={onChange} onChange={onChange}
hasError={Boolean(errors.password)} hasError={Boolean(errors.password)}
placeholder="Enter your password..." placeholder="Enter your password..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
)} )}
/> />
@ -93,8 +93,10 @@ export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
</div> </div>
<div> <div>
<Button <Button
variant="primary"
type="submit" type="submit"
className="w-full text-center h-[46px]" className="w-full"
size="xl"
disabled={!isValid && isDirty} disabled={!isValid && isDirty}
loading={isSubmitting} loading={isSubmitting}
> >

View File

@ -122,6 +122,7 @@ export const EmailSignUpForm: React.FC<Props> = (props) => {
variant="primary" variant="primary"
type="submit" type="submit"
className="w-full" className="w-full"
size="xl"
disabled={!isValid && isDirty} disabled={!isValid && isDirty}
loading={isSubmitting} loading={isSubmitting}
> >

View File

@ -1,3 +1,4 @@
import { useState, useEffect } from "react";
import useSWR from "swr"; import useSWR from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Image from "next/image"; import Image from "next/image";
@ -20,14 +21,15 @@ import {
import { Loader, Spinner } from "@plane/ui"; import { Loader, Spinner } from "@plane/ui";
// images // images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
// types
import { IUserSettings } from "types"; import { IUserSettings } from "types";
import { useState } from "react";
const appConfigService = new AppConfigService(); const appConfigService = new AppConfigService();
const authService = new AuthService(); const authService = new AuthService();
export const SignInView = observer(() => { export const SignInView = observer(() => {
const { user: userStore } = useMobxStore(); const { user: userStore } = useMobxStore();
const { fetchCurrentUserSettings } = userStore;
// router // router
const router = useRouter(); const router = useRouter();
// states // states
@ -35,25 +37,46 @@ export const SignInView = observer(() => {
// toast // toast
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// fetch app config // fetch app config
const { data, error } = useSWR("APP_CONFIG", () => appConfigService.envConfig()); const { data, error: appConfigError } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
// fetch user info
useSWR("USER_INFO", () => userStore.fetchCurrentUser());
// computed // computed
const enableEmailPassword = const enableEmailPassword =
data && data &&
(data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github)); (data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github));
const handleLoginRedirection = () => useEffect(() => {
userStore fetchCurrentUserSettings().then((settings) => {
.fetchCurrentUserSettings() setLoading(true);
.then((userSettings: IUserSettings) => { router.push(
const workspaceSlug = `/${
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; settings.workspace.last_workspace_slug
router.push(`/${workspaceSlug}`); ? settings.workspace.last_workspace_slug
}) : settings.workspace.fallback_workspace_slug
.catch(() => { }`
setLoading(false); );
}); });
}, [fetchCurrentUserSettings, router]);
const handleLoginRedirection = () => {
userStore.fetchCurrentUser().then((user) => {
const isOnboard = user.onboarding_step.profile_complete;
if (isOnboard) {
userStore
.fetchCurrentUserSettings()
.then((userSettings: IUserSettings) => {
const workspaceSlug =
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
if (workspaceSlug) router.push(`/${workspaceSlug}`);
else if (userSettings.workspace.invites > 0) router.push("/invitations");
else router.push("/create-workspace");
})
.catch(() => {
setLoading(false);
});
} else {
router.push("/onboarding");
}
});
};
const handleGoogleSignIn = async ({ clientId, credential }: any) => { const handleGoogleSignIn = async ({ clientId, credential }: any) => {
try { try {
@ -114,7 +137,13 @@ export const SignInView = observer(() => {
return authService return authService
.emailLogin(formData) .emailLogin(formData)
.then(() => { .then(() => {
handleLoginRedirection(); userStore.fetchCurrentUser().then((user) => {
const isOnboard = user.onboarding_step.profile_complete;
if (isOnboard) handleLoginRedirection();
else {
router.push("/onboarding");
}
});
}) })
.catch((err) => { .catch((err) => {
setLoading(false); setLoading(false);
@ -166,7 +195,7 @@ export const SignInView = observer(() => {
Sign in to Plane Sign in to Plane
</h1> </h1>
{!data && !error ? ( {!data && !appConfigError ? (
<div className="pt-10 w-ful"> <div className="pt-10 w-ful">
<Loader className="space-y-4 w-full pb-4"> <Loader className="space-y-4 w-full pb-4">
<Loader.Item height="46px" width="360px" /> <Loader.Item height="46px" width="360px" />

View File

@ -1,6 +1,7 @@
import { useState, useEffect, Fragment, FC, ChangeEvent } from "react"; import { useState, useEffect, Fragment, FC, ChangeEvent } from "react";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// icons // icons
import { X } from "lucide-react"; import { X } from "lucide-react";
// hooks // hooks
@ -8,9 +9,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
// ui // ui
import { Button, CustomSelect, CustomSearchSelect, Input, TextArea } from "@plane/ui"; import { Button, CustomSelect, Input, TextArea } from "@plane/ui";
import { Avatar } from "components/ui";
// components // components
import { WorkspaceMemberSelect } from "components/workspace";
import { ImagePickerPopover } from "components/core"; import { ImagePickerPopover } from "components/core";
import EmojiIconPicker from "components/emoji-icon-picker"; import EmojiIconPicker from "components/emoji-icon-picker";
// helpers // helpers
@ -19,8 +20,6 @@ import { getRandomEmoji, renderEmoji } from "helpers/emoji.helper";
import { IWorkspaceMember } from "types"; import { IWorkspaceMember } from "types";
// constants // constants
import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project"; import { NETWORK_CHOICES, PROJECT_UNSPLASH_COVERS } from "constants/project";
import { WorkspaceMemberSelect } from "components/workspace";
import { observer } from "mobx-react-lite";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;

View File

@ -136,13 +136,17 @@ export const CreateWorkspaceForm: FC<Props> = ({
message: "Workspace name should not exceed 80 characters", message: "Workspace name should not exceed 80 characters",
}, },
}} }}
render={({ field: { value, ref } }) => ( render={({ field: { value, ref, onChange } }) => (
<Input <Input
id="workspaceName" id="workspaceName"
name="name" name="name"
type="text" type="text"
value={value} value={value}
onChange={(e) => setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"))} onChange={(e) => {
onChange(e.target.value);
setValue("name", e.target.value);
setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"));
}}
ref={ref} ref={ref}
hasError={Boolean(errors.name)} hasError={Boolean(errors.name)}
placeholder="Enter workspace name..." placeholder="Enter workspace name..."
@ -166,7 +170,7 @@ export const CreateWorkspaceForm: FC<Props> = ({
id="workspaceUrl" id="workspaceUrl"
name="slug" name="slug"
type="text" type="text"
value={value} value={value.toLocaleLowerCase().trim().replace(/ /g, "-")}
onChange={(e) => onChange={(e) =>
/^[a-zA-Z0-9_-]+$/.test(e.target.value) ? setInvalidSlug(false) : setInvalidSlug(true) /^[a-zA-Z0-9_-]+$/.test(e.target.value) ? setInvalidSlug(false) : setInvalidSlug(true)
} }

View File

@ -1,5 +1,7 @@
import useUser from "hooks/use-user";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
declare global { declare global {
interface Window { interface Window {
@ -8,16 +10,14 @@ declare global {
} }
} }
const Crisp = () => { const Crisp = observer(() => {
const { user } = useUser(); const { user: userStore } = useMobxStore();
const { currentUser } = userStore;
const validateCurrentUser = useCallback(() => { const validateCurrentUser = useCallback(() => {
const currentUser = user ? user : null; if (currentUser) return currentUser.email;
if (currentUser && currentUser.email) return currentUser.email;
return null; return null;
}, [user]); }, [currentUser]);
useEffect(() => { useEffect(() => {
if (typeof window && validateCurrentUser()) { if (typeof window && validateCurrentUser()) {
@ -40,5 +40,5 @@ const Crisp = () => {
}, [validateCurrentUser]); }, [validateCurrentUser]);
return <></>; return <></>;
}; });
export default Crisp; export default Crisp;

View File

@ -1,12 +1,12 @@
import { FC, ReactNode } from "react";
type Props = { type Props = {
children: React.ReactNode; children: ReactNode;
gradient?: boolean; gradient?: boolean;
}; };
const DefaultLayout: React.FC<Props> = ({ children, gradient = false }) => ( const DefaultLayout: FC<Props> = ({ children, gradient = false }) => (
<div className={`h-screen w-full overflow-hidden ${gradient ? "" : "bg-custom-background-100"}`}> <div className={`h-screen w-full overflow-hidden ${gradient ? "" : "bg-custom-background-100"}`}>{children}</div>
{children}
</div>
); );
export default DefaultLayout; export default DefaultLayout;

View File

@ -8,6 +8,10 @@ import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
const MobxStoreInit = observer(() => { const MobxStoreInit = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query;
// store
const { const {
theme: themeStore, theme: themeStore,
user: userStore, user: userStore,
@ -23,14 +27,11 @@ const MobxStoreInit = observer(() => {
const [dom, setDom] = useState<any>(); const [dom, setDom] = useState<any>();
// theme // theme
const { setTheme } = useTheme(); const { setTheme } = useTheme();
// router
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query;
// const dom = useMemo(() => window && window.document?.querySelector<HTMLElement>("[data-theme='custom']"), [document]);
/**
* Sidebar collapsed fetching from local storage
*/
useEffect(() => { useEffect(() => {
// sidebar collapsed toggle
const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed"); const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false; const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (localValue && themeStore?.sidebarCollapsed === undefined) { if (localValue && themeStore?.sidebarCollapsed === undefined) {
@ -38,6 +39,9 @@ const MobxStoreInit = observer(() => {
} }
}, [themeStore, userStore, setTheme]); }, [themeStore, userStore, setTheme]);
/**
* Setting up the theme of the user by fetching it from local storage
*/
useEffect(() => { useEffect(() => {
if (!userStore.currentUser) return; if (!userStore.currentUser) return;
if (window) { if (window) {
@ -45,11 +49,13 @@ const MobxStoreInit = observer(() => {
} }
setTheme(userStore.currentUser?.theme?.theme || "system"); setTheme(userStore.currentUser?.theme?.theme || "system");
if (userStore.currentUser?.theme?.theme === "custom" && dom) { if (userStore.currentUser?.theme?.theme === "custom" && dom) {
console.log("userStore.currentUser?.theme?.theme", userStore.currentUser?.theme);
applyTheme(userStore.currentUser?.theme?.palette, false); applyTheme(userStore.currentUser?.theme?.palette, false);
} else unsetCustomCssVariables(); } else unsetCustomCssVariables();
}, [userStore.currentUser, setTheme, dom]); }, [userStore.currentUser, setTheme, dom]);
/**
* Setting router info to the respective stores.
*/
useEffect(() => { useEffect(() => {
if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString()); if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
if (projectId) projectStore.setProjectId(projectId.toString()); if (projectId) projectStore.setProjectId(projectId.toString());

View File

@ -50,7 +50,7 @@ const SignUp: NextPage = () => {
}); });
if (response) await mutateUser(); if (response) await mutateUser();
router.push("/"); router.push("/onboarding");
}) })
.catch((err) => .catch((err) =>
setToastAlert({ setToastAlert({

View File

@ -11,6 +11,7 @@ import { IWorkspaceMember, IProjectMember } from "types";
export interface IUserStore { export interface IUserStore {
loader: boolean; loader: boolean;
isUserLoggedIn: boolean | null;
currentUser: IUser | null; currentUser: IUser | null;
currentUserSettings: IUserSettings | null; currentUserSettings: IUserSettings | null;
@ -38,6 +39,7 @@ export interface IUserStore {
class UserStore implements IUserStore { class UserStore implements IUserStore {
loader: boolean = false; loader: boolean = false;
isUserLoggedIn: boolean | null = null;
currentUser: IUser | null = null; currentUser: IUser | null = null;
currentUserSettings: IUserSettings | null = null; currentUserSettings: IUserSettings | null = null;
@ -85,10 +87,14 @@ class UserStore implements IUserStore {
if (response) { if (response) {
runInAction(() => { runInAction(() => {
this.currentUser = response; this.currentUser = response;
this.isUserLoggedIn = true;
}); });
} }
return response; return response;
} catch (error) { } catch (error) {
runInAction(() => {
this.isUserLoggedIn = false;
});
throw error; throw error;
} }
}; };