chore: updated intance and auth wrapper logic

This commit is contained in:
gurusainath 2024-05-01 14:54:35 +05:30
parent 86196c38bc
commit dd7c500bfe
18 changed files with 211 additions and 63 deletions

View File

@ -212,3 +212,4 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
</form> </form>
); );
}); });

View File

@ -91,7 +91,7 @@ export const SignUpAuthRoot: FC = observer(() => {
); );
return ( return (
<div className="relative max-w-lg mx-auto flex flex-col space-y-6"> <div className="relative max-w-lg px-5 border border-red-500 mx-auto flex flex-col space-y-6">
<AuthHeader <AuthHeader
workspaceSlug={workspaceSlug?.toString() || undefined} workspaceSlug={workspaceSlug?.toString() || undefined}
invitationId={invitation_id?.toString() || undefined} invitationId={invitation_id?.toString() || undefined}

View File

@ -1,3 +1,10 @@
export enum EPageTypes {
"PUBLIC" = "PUBLIC",
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
"ONBOARDING" = "ONBOARDING",
"AUTHENTICATED" = "AUTHENTICATED",
}
export enum EAuthModes { export enum EAuthModes {
SIGN_IN = "SIGN_IN", SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP", SIGN_UP = "SIGN_UP",

View File

@ -4,7 +4,9 @@ import { observer } from "mobx-react-lite";
import { CommandPalette } from "@/components/command-palette"; import { CommandPalette } from "@/components/command-palette";
import { SidebarHamburgerToggle } from "@/components/core/sidebar"; import { SidebarHamburgerToggle } from "@/components/core/sidebar";
// layouts // layouts
import { UserAuthWrapper, WorkspaceAuthWrapper, ProjectAuthWrapper } from "@/layouts/auth-layout"; import { WorkspaceAuthWrapper, ProjectAuthWrapper } from "@/layouts/auth-layout";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
import { AppSidebar } from "./sidebar"; import { AppSidebar } from "./sidebar";
export interface IAppLayout { export interface IAppLayout {
@ -18,31 +20,29 @@ export const AppLayout: FC<IAppLayout> = observer((props) => {
const { children, header, withProjectWrapper = false, mobileHeader } = props; const { children, header, withProjectWrapper = false, mobileHeader } = props;
return ( return (
<> <AuthenticationWrapper>
<UserAuthWrapper> <CommandPalette />
<CommandPalette /> <WorkspaceAuthWrapper>
<WorkspaceAuthWrapper> <div className="relative flex h-screen w-full overflow-hidden">
<div className="relative flex h-screen w-full overflow-hidden"> <AppSidebar />
<AppSidebar /> <main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100"> <div className="z-[15]">
<div className="z-[15]"> <div className="z-10 flex w-full items-center border-b border-custom-border-200">
<div className="z-10 flex w-full items-center border-b border-custom-border-200"> <div className="block bg-custom-sidebar-background-100 py-4 pl-5 md:hidden">
<div className="block bg-custom-sidebar-background-100 py-4 pl-5 md:hidden"> <SidebarHamburgerToggle />
<SidebarHamburgerToggle />
</div>
<div className="w-full">{header}</div>
</div> </div>
{mobileHeader && mobileHeader} <div className="w-full">{header}</div>
</div> </div>
<div className="h-full w-full overflow-hidden"> {mobileHeader && mobileHeader}
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll"> </div>
{withProjectWrapper ? <ProjectAuthWrapper>{children}</ProjectAuthWrapper> : <>{children}</>} <div className="h-full w-full overflow-hidden">
</div> <div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
{withProjectWrapper ? <ProjectAuthWrapper>{children}</ProjectAuthWrapper> : <>{children}</>}
</div> </div>
</main> </div>
</div> </main>
</WorkspaceAuthWrapper> </div>
</UserAuthWrapper> </WorkspaceAuthWrapper>
</> </AuthenticationWrapper>
); );
}); });

View File

@ -57,5 +57,3 @@ export const UserAuthWrapper: FC<IUserAuthWrapper> = observer((props) => {
return <>{children}</>; return <>{children}</>;
}); });

View File

@ -25,7 +25,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
// next themes // next themes
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme } = useTheme();
// store hooks // store hooks
const { membership, signOut, data: currentUser } = useUser(); const { membership, signOut, data: currentUser } = useUser();
const { fetchProjects } = useProject(); const { fetchProjects } = useProject();

View File

@ -13,8 +13,8 @@ import { SWR_CONFIG } from "@/constants/swr-config";
import { resolveGeneralTheme } from "@/helpers/theme.helper"; import { resolveGeneralTheme } from "@/helpers/theme.helper";
// hooks // hooks
import { useInstance, useWorkspace, useUser } from "@/hooks/store"; import { useInstance, useWorkspace, useUser } from "@/hooks/store";
// layouts // wrappers
import InstanceLayout from "@/lib/wrappers/instance-wrapper"; import { InstanceWrapper } from "@/lib/wrappers";
// dynamic imports // dynamic imports
const StoreWrapper = dynamic(() => import("@/lib/wrappers/store-wrapper"), { ssr: false }); const StoreWrapper = dynamic(() => import("@/lib/wrappers/store-wrapper"), { ssr: false });
const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false }); const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false });
@ -45,7 +45,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
<> <>
{/* TODO: Need to handle custom themes for toast */} {/* TODO: Need to handle custom themes for toast */}
<Toast theme={resolveGeneralTheme(resolvedTheme)} /> <Toast theme={resolveGeneralTheme(resolvedTheme)} />
<InstanceLayout> <InstanceWrapper>
<StoreWrapper> <StoreWrapper>
<CrispWrapper user={currentUser}> <CrispWrapper user={currentUser}>
<PostHogProvider <PostHogProvider
@ -60,7 +60,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
</PostHogProvider> </PostHogProvider>
</CrispWrapper> </CrispWrapper>
</StoreWrapper> </StoreWrapper>
</InstanceLayout> </InstanceWrapper>
</> </>
); );
}); });

View File

@ -1,20 +1,125 @@
import { FC, ReactNode } from "react"; import { FC, ReactNode } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks // hooks
import { useUser } from "@/hooks/store"; import { useUser, useWorkspace } from "@/hooks/store";
type TPageType = "public" | "onboarding" | "private"; type TPageType = EPageTypes;
type TAuthenticationWrapper = { type TAuthenticationWrapper = {
children: ReactNode; children: ReactNode;
pageType: TPageType; pageType?: TPageType;
};
const isValidURL = (url: string): boolean => {
const disallowedSchemes = /^(https?|ftp):\/\//i;
return !disallowedSchemes.test(url);
}; };
export const AuthenticationWrapper: FC<TAuthenticationWrapper> = (props) => { export const AuthenticationWrapper: FC<TAuthenticationWrapper> = (props) => {
const { children, pageType } = props; const router = useRouter();
const { next_path } = router.query;
// props
const { children, pageType = EPageTypes.AUTHENTICATED } = props;
// hooks // hooks
const { data: currentUser } = useUser(); const {
isLoading: isUserLoading,
data: currentUser,
currentUserSettings: { isLoading: currentUserSettingsLoader, data: currentUserSettings, fetchCurrentUserSettings },
profile: { isLoading: currentUserProfileLoader, data: currentUserProfile, fetchUserProfile },
fetchCurrentUser,
} = useUser();
const { loader: workspaceLoader, workspaces, fetchWorkspaces } = useWorkspace();
console.log("currentUser", currentUser); useSWR(
"USER_PROFILE_SETTINGS_INFORMATION",
async () => {
await fetchCurrentUser();
if (currentUser) {
fetchCurrentUserSettings();
fetchUserProfile();
fetchWorkspaces();
}
},
{ revalidateOnFocus: false, shouldRetryOnError: false }
);
return <div key={pageType}>{children}</div>; const getWorkspaceRedirectionUrl = (): string => {
let redirectionRoute = "/profile";
// validating the next_path from the router query
if (next_path && isValidURL(next_path.toString())) {
redirectionRoute = next_path.toString();
return redirectionRoute;
}
// validate the last and fallback workspace_slug
const currentWorkspaceSlug =
currentUserSettings?.workspace?.last_workspace_slug || currentUserSettings?.workspace?.fallback_workspace_slug;
// validate the current workspace_slug is available in the user's workspace list
const isCurrentWorkspaceValid = Object.values(workspaces || {}).findIndex(
(workspace) => workspace.slug === currentWorkspaceSlug
);
if (isCurrentWorkspaceValid >= 0) redirectionRoute = `/${currentWorkspaceSlug}`;
return redirectionRoute;
};
if (isUserLoading || currentUserSettingsLoader || currentUserProfileLoader || workspaceLoader)
return (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
</div>
);
if (pageType === EPageTypes.PUBLIC) return <>{children}</>;
if (pageType === EPageTypes.NON_AUTHENTICATED) {
if (!currentUser) return <>{children}</>;
else {
if (currentUserProfile?.is_onboarded) {
const currentRedirectRoute = getWorkspaceRedirectionUrl();
router.push(currentRedirectRoute);
return;
} else {
router.push("/onboarding");
return;
}
}
}
if (pageType === EPageTypes.ONBOARDING) {
if (!currentUser) {
router.push("/accounts/sign-in");
return;
} else {
if (currentUser && currentUserProfile?.is_onboarded) {
const currentRedirectRoute = getWorkspaceRedirectionUrl();
router.push(currentRedirectRoute);
return;
} else return <>{children}</>;
}
}
if (pageType === EPageTypes.AUTHENTICATED) {
if (currentUser) {
if (currentUserProfile?.is_onboarded) {
return <>{children}</>;
} else {
const currentRedirectRoute = getWorkspaceRedirectionUrl();
router.push(currentRedirectRoute);
return;
}
} else {
router.push("/accounts/sign-in");
return;
}
}
return <>{children}</>;
}; };

View File

@ -0,0 +1,2 @@
export * from "./instance-wrapper";
export * from "./authentication-wrapper";

View File

@ -12,7 +12,7 @@ type TInstanceWrapper = {
children: ReactNode; children: ReactNode;
}; };
const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => { export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
const { children } = props; const { children } = props;
// store // store
const { isLoading, instance, error, fetchInstanceInfo } = useInstance(); const { isLoading, instance, error, fetchInstanceInfo } = useInstance();
@ -42,5 +42,3 @@ const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
return <>{children}</>; return <>{children}</>;
}); });
export default InstanceWrapper;

View File

@ -13,6 +13,7 @@ import { PageHead } from "@/components/core";
// constants // constants
import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
// helpers // helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { checkEmailValidity } from "@/helpers/string.helper"; import { checkEmailValidity } from "@/helpers/string.helper";
// hooks // hooks
@ -23,6 +24,8 @@ import useTimer from "@/hooks/use-timer";
import DefaultLayout from "@/layouts/default-layout"; import DefaultLayout from "@/layouts/default-layout";
// lib // lib
import { NextPageWithLayout } from "@/lib/types"; import { NextPageWithLayout } from "@/lib/types";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
// images // images
@ -47,8 +50,8 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
const { email } = router.query; const { email } = router.query;
// store hooks // store hooks
const { captureEvent } = useEventTracker(); const { captureEvent } = useEventTracker();
// hooks // hooks
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
// timer // timer
const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0); const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0);
const { isRedirecting, handleRedirection } = useAuthRedirection(); const { isRedirecting, handleRedirection } = useAuthRedirection();
@ -108,7 +111,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
<div className="relative"> <div className="relative">
<PageHead title="Forgot Password" /> <PageHead title="Forgot Password" />
<div className="absolute inset-0 z-0"> <div className="absolute inset-0 z-0">
<Image <Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern} src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-screen min-h-screen object-cover" className="w-screen min-h-screen object-cover"
alt="Plane background pattern" alt="Plane background pattern"
@ -199,7 +202,11 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
}; };
ForgotPasswordPage.getLayout = function getLayout(page: ReactElement) { ForgotPasswordPage.getLayout = function getLayout(page: ReactElement) {
return <DefaultLayout>{page}</DefaultLayout>; return (
<DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>{page}</AuthenticationWrapper>
</DefaultLayout>
);
}; };
export default ForgotPasswordPage; export default ForgotPasswordPage;

View File

@ -10,6 +10,7 @@ import { Button, Input, Spinner } from "@plane/ui";
import { PasswordStrengthMeter } from "@/components/account"; import { PasswordStrengthMeter } from "@/components/account";
import { PageHead } from "@/components/core"; import { PageHead } from "@/components/core";
// helpers // helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper"; import { getPasswordStrength } from "@/helpers/password.helper";
// hooks // hooks
@ -18,6 +19,8 @@ import useAuthRedirection from "@/hooks/use-auth-redirection";
import DefaultLayout from "@/layouts/default-layout"; import DefaultLayout from "@/layouts/default-layout";
// lib // lib
import { NextPageWithLayout } from "@/lib/types"; import { NextPageWithLayout } from "@/lib/types";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
// images // images
@ -212,7 +215,11 @@ const ResetPasswordPage: NextPageWithLayout = () => {
}; };
ResetPasswordPage.getLayout = function getLayout(page: ReactElement) { ResetPasswordPage.getLayout = function getLayout(page: ReactElement) {
return <DefaultLayout>{page}</DefaultLayout>; return (
<DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>{page}</AuthenticationWrapper>
</DefaultLayout>
);
}; };
export default ResetPasswordPage; export default ResetPasswordPage;

View File

@ -10,6 +10,8 @@ import { SignInAuthRoot } from "@/components/account";
import { PageHead } from "@/components/core"; import { PageHead } from "@/components/core";
// constants // constants
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks // hooks
import { useEventTracker, useInstance, useUser } from "@/hooks/store"; import { useEventTracker, useInstance, useUser } from "@/hooks/store";
import useAuthRedirection from "@/hooks/use-auth-redirection"; import useAuthRedirection from "@/hooks/use-auth-redirection";
@ -17,6 +19,8 @@ import useAuthRedirection from "@/hooks/use-auth-redirection";
import DefaultLayout from "@/layouts/default-layout"; import DefaultLayout from "@/layouts/default-layout";
// types // types
import { NextPageWithLayout } from "@/lib/types"; import { NextPageWithLayout } from "@/lib/types";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
// assets // assets
import PlaneBackgroundPatternDark from "public/onboarding/background-pattern-dark.svg"; import PlaneBackgroundPatternDark from "public/onboarding/background-pattern-dark.svg";
import PlaneBackgroundPattern from "public/onboarding/background-pattern.svg"; import PlaneBackgroundPattern from "public/onboarding/background-pattern.svg";
@ -83,7 +87,11 @@ const SignInPage: NextPageWithLayout = observer(() => {
}); });
SignInPage.getLayout = function getLayout(page: React.ReactElement) { SignInPage.getLayout = function getLayout(page: React.ReactElement) {
return <DefaultLayout>{page}</DefaultLayout>; return (
<DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>{page}</AuthenticationWrapper>
</DefaultLayout>
);
}; };
export default SignInPage; export default SignInPage;

View File

@ -1,15 +1,23 @@
import { ReactElement } from "react"; import { ReactElement } from "react";
// layouts
import { SignUpView } from "@/components/page-views";
import DefaultLayout from "@/layouts/default-layout";
// components // components
import { SignUpView } from "@/components/page-views";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// layouts
import DefaultLayout from "@/layouts/default-layout";
// type // type
import { NextPageWithLayout } from "@/lib/types"; import { NextPageWithLayout } from "@/lib/types";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
const HomePage: NextPageWithLayout = () => <SignUpView />; const HomePage: NextPageWithLayout = () => <SignUpView />;
HomePage.getLayout = function getLayout(page: ReactElement) { HomePage.getLayout = function getLayout(page: ReactElement) {
return <DefaultLayout>{page}</DefaultLayout>; return (
<DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>{page}</AuthenticationWrapper>
</DefaultLayout>
);
}; };
export default HomePage; export default HomePage;

View File

@ -3,22 +3,25 @@ import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { Boxes, Check, Share2, Star, User2, X } from "lucide-react"; import { Boxes, Check, Share2, Star, User2, X } from "lucide-react";
// hooks
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
// components
import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space";
// constants
import { WORKSPACE_INVITATION } from "@/constants/fetch-keys"; import { WORKSPACE_INVITATION } from "@/constants/fetch-keys";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useUser } from "@/hooks/store"; import { useUser } from "@/hooks/store";
// services
// layouts // layouts
import DefaultLayout from "@/layouts/default-layout"; import DefaultLayout from "@/layouts/default-layout";
// ui
// icons
// types // types
import { NextPageWithLayout } from "@/lib/types"; import { NextPageWithLayout } from "@/lib/types";
import { WorkspaceService } from "@/services/workspace.service"; // wrappers
// constants import { AuthenticationWrapper } from "@/lib/wrappers";
// services // services
import { WorkspaceService } from "@/services/workspace.service";
// service initialization
const workspaceService = new WorkspaceService(); const workspaceService = new WorkspaceService();
const WorkspaceInvitationPage: NextPageWithLayout = observer(() => { const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
@ -124,7 +127,11 @@ const WorkspaceInvitationPage: NextPageWithLayout = observer(() => {
}); });
WorkspaceInvitationPage.getLayout = function getLayout(page: ReactElement) { WorkspaceInvitationPage.getLayout = function getLayout(page: ReactElement) {
return <DefaultLayout>{page}</DefaultLayout>; return (
<DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.PUBLIC}>{page}</AuthenticationWrapper>
</DefaultLayout>
);
}; };
export default WorkspaceInvitationPage; export default WorkspaceInvitationPage;

View File

@ -1,6 +1,8 @@
import { action, makeObservable, observable, runInAction } from "mobx"; import { action, makeObservable, observable, runInAction } from "mobx";
// types // types
import { IUser, IUserAccount } from "@plane/types"; import { IUser, IUserAccount } from "@plane/types";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
@ -9,7 +11,6 @@ import { RootStore } from "@/store/root.store";
import { IAccountStore, AccountStore } from "@/store/user/account.store"; import { IAccountStore, AccountStore } from "@/store/user/account.store";
import { ProfileStore, IProfileStore } from "@/store/user/profile.store"; import { ProfileStore, IProfileStore } from "@/store/user/profile.store";
import { IUserMembershipStore, UserMembershipStore } from "@/store/user/user-membership.store"; import { IUserMembershipStore, UserMembershipStore } from "@/store/user/user-membership.store";
import { API_BASE_URL } from "@/helpers/common.helper";
import { IUserSettingsStore, UserSettingsStore } from "./user-setting.store"; import { IUserSettingsStore, UserSettingsStore } from "./user-setting.store";
export interface IUserStore { export interface IUserStore {

View File

@ -88,7 +88,6 @@ export class ProfileStore implements IProfileStore {
return userProfile; return userProfile;
} catch (error) { } catch (error) {
console.log("Failed to fetch profile details");
runInAction(() => { runInAction(() => {
this.isLoading = true; this.isLoading = true;
this.error = { this.error = {

View File

@ -2,7 +2,7 @@ import { action, makeObservable, observable, runInAction } from "mobx";
// services // services
import { UserService } from "services/user.service"; import { UserService } from "services/user.service";
// types // types
import { IUserSettings, TUserProfile } from "@plane/types"; import { IUserSettings } from "@plane/types";
type TError = { type TError = {
status: string; status: string;