mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: UI changes and revamp components for authentication
This commit is contained in:
parent
2e2e8f730f
commit
2d42d316a0
@ -50,7 +50,7 @@ export const AuthView = observer(() => {
|
|||||||
<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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,9 +79,9 @@ const ForgotPasswordPage: NextPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-screen w-full overflow-hidden">
|
<div className="relative h-screen w-full overflow-hidden">
|
||||||
<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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -159,5 +159,4 @@ const ForgotPasswordPage: NextPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default ForgotPasswordPage;
|
export default ForgotPasswordPage;
|
||||||
|
@ -79,7 +79,7 @@ const ResetPasswordPage: NextPage = () => {
|
|||||||
<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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@ export const AuthBanner: FC<TAuthBanner> = (props) => {
|
|||||||
|
|
||||||
if (!bannerData) return <></>;
|
if (!bannerData) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="relative inline-flex items-center p-2 rounded-md gap-2 border border-custom-primary-100/50 bg-custom-primary-100/10">
|
<div className="relative flex items-center p-2 rounded-md gap-2 border border-custom-primary-100/50 bg-custom-primary-100/10">
|
||||||
<div className="w-4 h-4 flex-shrink-0 relative flex justify-center items-center">
|
<div className="w-4 h-4 flex-shrink-0 relative flex justify-center items-center">
|
||||||
<Info size={16} className="text-custom-primary-100" />
|
<Info size={16} className="text-custom-primary-100" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,7 +38,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
|
|||||||
const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting;
|
const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleFormSubmit} className="mx-auto mt-8 space-y-4 w-5/6 sm:w-96">
|
<form onSubmit={handleFormSubmit} className="mt-8 space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
Email
|
Email
|
||||||
@ -69,13 +69,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||||
type="submit"
|
|
||||||
variant="primary"
|
|
||||||
className="w-full"
|
|
||||||
size="lg"
|
|
||||||
disabled={isButtonDisabled}
|
|
||||||
>
|
|
||||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -98,7 +98,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
className="mt-5 space-y-4"
|
||||||
method="POST"
|
method="POST"
|
||||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
|
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
|
||||||
onSubmit={() => setIsSubmitting(true)}
|
onSubmit={() => setIsSubmitting(true)}
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
AuthPasswordForm,
|
AuthPasswordForm,
|
||||||
OAuthOptions,
|
OAuthOptions,
|
||||||
TermsAndConditions,
|
TermsAndConditions,
|
||||||
UniqueCodeForm,
|
AuthUniqueCodeForm,
|
||||||
} from "@/components/account";
|
} from "@/components/account";
|
||||||
// helpers
|
// helpers
|
||||||
import {
|
import {
|
||||||
@ -82,45 +82,43 @@ export const SignInAuthRoot = observer(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="relative flex flex-col space-y-6">
|
||||||
<div className="relative max-w-lg 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}
|
invitationEmail={email || undefined}
|
||||||
invitationEmail={email || undefined}
|
authMode={EAuthModes.SIGN_IN}
|
||||||
authMode={EAuthModes.SIGN_IN}
|
currentAuthStep={authStep}
|
||||||
currentAuthStep={authStep}
|
handleLoader={setIsLoading}
|
||||||
handleLoader={setIsLoading}
|
/>
|
||||||
|
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
||||||
|
<AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />
|
||||||
|
)}
|
||||||
|
{authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />}
|
||||||
|
{authStep === EAuthSteps.UNIQUE_CODE && (
|
||||||
|
<AuthUniqueCodeForm
|
||||||
|
email={email}
|
||||||
|
handleEmailClear={() => {
|
||||||
|
setEmail("");
|
||||||
|
setAuthStep(EAuthSteps.EMAIL);
|
||||||
|
}}
|
||||||
|
submitButtonText="Continue"
|
||||||
|
mode={authMode}
|
||||||
/>
|
/>
|
||||||
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
)}
|
||||||
<AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />
|
{authStep === EAuthSteps.PASSWORD && (
|
||||||
)}
|
<AuthPasswordForm
|
||||||
{authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />}
|
email={email}
|
||||||
{authStep === EAuthSteps.UNIQUE_CODE && (
|
handleEmailClear={() => {
|
||||||
<UniqueCodeForm
|
setEmail("");
|
||||||
email={email}
|
setAuthStep(EAuthSteps.EMAIL);
|
||||||
handleEmailClear={() => {
|
}}
|
||||||
setEmail("");
|
handleStepChange={(step) => setAuthStep(step)}
|
||||||
setAuthStep(EAuthSteps.EMAIL);
|
mode={authMode}
|
||||||
}}
|
/>
|
||||||
submitButtonText="Continue"
|
)}
|
||||||
mode={authMode}
|
{isOAuthEnabled && <OAuthOptions />}
|
||||||
/>
|
<TermsAndConditions isSignUp={false} />
|
||||||
)}
|
</div>
|
||||||
{authStep === EAuthSteps.PASSWORD && (
|
|
||||||
<AuthPasswordForm
|
|
||||||
email={email}
|
|
||||||
handleEmailClear={() => {
|
|
||||||
setEmail("");
|
|
||||||
setAuthStep(EAuthSteps.EMAIL);
|
|
||||||
}}
|
|
||||||
handleStepChange={(step) => setAuthStep(step)}
|
|
||||||
mode={authMode}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOAuthEnabled && <OAuthOptions />}
|
|
||||||
<TermsAndConditions isSignUp={false} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -91,7 +91,7 @@ export const SignUpAuthRoot: FC = observer(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative max-w-lg px-5 mx-auto flex flex-col space-y-6">
|
<div className="relative 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}
|
||||||
|
@ -93,7 +93,7 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
className="mt-5 space-y-4"
|
||||||
method="POST"
|
method="POST"
|
||||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "magic-sign-in" : "magic-sign-up"}/`}
|
||||||
onSubmit={() => setIsSubmitting(true)}
|
onSubmit={() => setIsSubmitting(true)}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
// react
|
|
||||||
import { useEffect, useState, FC } from "react";
|
|
||||||
// next
|
|
||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
// images
|
|
||||||
import githubLightModeImage from "/public/logos/github-black.png";
|
|
||||||
import githubDarkModeImage from "/public/logos/github-dark.svg";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
handleSignIn: React.Dispatch<string>;
|
|
||||||
clientId: string;
|
|
||||||
type: "sign_in" | "sign_up";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GitHubSignInButton: FC<Props> = (props) => {
|
|
||||||
const { handleSignIn, clientId, type } = props;
|
|
||||||
// states
|
|
||||||
const [loginCallBackURL, setLoginCallBackURL] = useState(undefined);
|
|
||||||
const [gitCode, setGitCode] = useState<null | string>(null);
|
|
||||||
// router
|
|
||||||
const {
|
|
||||||
query: { code },
|
|
||||||
} = useRouter();
|
|
||||||
// theme
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (code && !gitCode) {
|
|
||||||
setGitCode(code.toString());
|
|
||||||
handleSignIn(code.toString());
|
|
||||||
}
|
|
||||||
}, [code, gitCode, handleSignIn]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
|
||||||
setLoginCallBackURL(`${origin}/` as any);
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<Link
|
|
||||||
href={`https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className={`flex h-[42px] w-full items-center justify-center gap-2 rounded border px-2 text-sm font-medium text-custom-text-100 duration-300 hover:bg-onboarding-background-300 ${
|
|
||||||
resolvedTheme === "dark" ? "border-[#43484F] bg-[#2F3135]" : "border-[#D9E4FF]"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={resolvedTheme === "dark" ? githubDarkModeImage : githubLightModeImage}
|
|
||||||
height={18}
|
|
||||||
width={18}
|
|
||||||
alt="GitHub Logo"
|
|
||||||
/>
|
|
||||||
<span className="text-onboarding-text-200">{type === "sign_in" ? "Sign-in" : "Sign-up"} with GitHub</span>
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,60 +0,0 @@
|
|||||||
import { FC, useEffect, useRef, useCallback, useState } from "react";
|
|
||||||
import Script from "next/script";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
handleSignIn: React.Dispatch<any>;
|
|
||||||
clientId: string;
|
|
||||||
type: "sign_in" | "sign_up";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GoogleSignInButton: FC<Props> = (props) => {
|
|
||||||
const { handleSignIn, clientId, type } = props;
|
|
||||||
// refs
|
|
||||||
const googleSignInButton = useRef<HTMLDivElement>(null);
|
|
||||||
// states
|
|
||||||
const [gsiScriptLoaded, setGsiScriptLoaded] = useState(false);
|
|
||||||
|
|
||||||
const loadScript = useCallback(() => {
|
|
||||||
if (!googleSignInButton.current || gsiScriptLoaded) return;
|
|
||||||
|
|
||||||
window?.google?.accounts.id.initialize({
|
|
||||||
client_id: clientId,
|
|
||||||
callback: handleSignIn,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
window?.google?.accounts.id.renderButton(
|
|
||||||
googleSignInButton.current,
|
|
||||||
{
|
|
||||||
type: "standard",
|
|
||||||
theme: "outline",
|
|
||||||
size: "large",
|
|
||||||
logo_alignment: "center",
|
|
||||||
text: type === "sign_in" ? "signin_with" : "signup_with",
|
|
||||||
} as GsiButtonConfiguration // customization attributes
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
window?.google?.accounts.id.prompt(); // also display the One Tap dialog
|
|
||||||
|
|
||||||
setGsiScriptLoaded(true);
|
|
||||||
}, [handleSignIn, gsiScriptLoaded, clientId, type]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (window?.google?.accounts?.id) {
|
|
||||||
loadScript();
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
window?.google?.accounts.id.cancel();
|
|
||||||
};
|
|
||||||
}, [loadScript]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Script src="https://accounts.google.com/gsi/client" async defer onLoad={loadScript} />
|
|
||||||
<div className="!w-full overflow-hidden rounded" id="googleSignInButton" ref={googleSignInButton} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,5 +1,3 @@
|
|||||||
export * from "./github-sign-in";
|
|
||||||
export * from "./google-sign-in";
|
|
||||||
export * from "./oauth-options";
|
export * from "./oauth-options";
|
||||||
export * from "./google-button";
|
export * from "./google-button";
|
||||||
export * from "./github-button";
|
export * from "./github-button";
|
||||||
|
@ -10,12 +10,12 @@ export const OAuthOptions: React.FC = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto mt-4 flex items-center sm:w-96">
|
<div className="mt-4 flex items-center">
|
||||||
<hr className="w-full border-onboarding-border-100" />
|
<hr className="w-full border-onboarding-border-100" />
|
||||||
<p className="mx-3 flex-shrink-0 text-center text-sm text-onboarding-text-400">or</p>
|
<p className="mx-3 flex-shrink-0 text-center text-sm text-onboarding-text-400">or</p>
|
||||||
<hr className="w-full border-onboarding-border-100" />
|
<hr className="w-full border-onboarding-border-100" />
|
||||||
</div>
|
</div>
|
||||||
<div className={`mx-auto mt-7 grid gap-4 overflow-hidden sm:w-96`}>
|
<div className={`mt-7 grid gap-4 overflow-hidden`}>
|
||||||
{instance?.config?.is_google_enabled && (
|
{instance?.config?.is_google_enabled && (
|
||||||
<div className="flex h-[42px] items-center !overflow-hidden">
|
<div className="flex h-[42px] items-center !overflow-hidden">
|
||||||
<GoogleOAuthButton text="SignIn with Google" />
|
<GoogleOAuthButton text="SignIn with Google" />
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from "./signup";
|
|
||||||
export * from "./workspace-dashboard";
|
export * from "./workspace-dashboard";
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { observer } from "mobx-react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
// ui
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
// components
|
|
||||||
import { SignUpAuthRoot } from "@/components/account";
|
|
||||||
import { PageHead } from "@/components/core";
|
|
||||||
// constants
|
|
||||||
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
|
|
||||||
// hooks
|
|
||||||
import { useEventTracker } from "@/hooks/store";
|
|
||||||
// assets
|
|
||||||
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
|
|
||||||
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
|
|
||||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
|
||||||
|
|
||||||
export const SignUpView = observer(() => {
|
|
||||||
// store hooks
|
|
||||||
const { captureEvent } = useEventTracker();
|
|
||||||
// hooks
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
// login redirection hook
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<PageHead title="Sign Up" />
|
|
||||||
<div className="absolute inset-0 z-0">
|
|
||||||
<Image
|
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
|
||||||
className="w-screen min-h-screen object-cover"
|
|
||||||
alt="Plane background pattern"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
|
||||||
Already have an account?{" "}
|
|
||||||
<Link
|
|
||||||
href="/accounts/sign-in"
|
|
||||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
|
|
||||||
className="font-semibold text-custom-primary-100 hover:underline"
|
|
||||||
>
|
|
||||||
Sign In
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-auto h-full">
|
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
|
||||||
<SignUpAuthRoot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
@ -96,25 +96,25 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
<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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
||||||
New to Plane?{" "}
|
New to Plane?{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/accounts/sign-up"
|
href="/"
|
||||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
||||||
className="font-semibold text-custom-primary-100 hover:underline"
|
className="font-semibold text-custom-primary-100 hover:underline"
|
||||||
>
|
>
|
||||||
@ -122,66 +122,64 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<div className="relative flex flex-col space-y-6">
|
||||||
<div className="mx-auto flex flex-col">
|
<div className="text-center space-y-1 py-4">
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
Reset your password
|
||||||
Reset your password
|
</h3>
|
||||||
</h3>
|
<p className="font-medium text-onboarding-text-400">
|
||||||
<p className="font-medium text-onboarding-text-400">
|
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
||||||
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="email"
|
|
||||||
rules={{
|
|
||||||
required: "Email is required",
|
|
||||||
validate: (value) => checkEmailValidity(value) || "Email is invalid",
|
|
||||||
}}
|
|
||||||
render={({ field: { value, onChange, ref } }) => (
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
ref={ref}
|
|
||||||
hasError={Boolean(errors.email)}
|
|
||||||
placeholder="name@company.com"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
disabled={resendTimerCode > 0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{resendTimerCode > 0 && (
|
|
||||||
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
|
|
||||||
<CircleCheck height={12} width={12} className="mt-0.5" />
|
|
||||||
We sent the reset link to your email address
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="primary"
|
|
||||||
className="w-full"
|
|
||||||
size="lg"
|
|
||||||
disabled={!isValid}
|
|
||||||
loading={isSubmitting || resendTimerCode > 0}
|
|
||||||
>
|
|
||||||
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
|
||||||
</Button>
|
|
||||||
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
|
|
||||||
Back to sign in
|
|
||||||
</Link>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<form onSubmit={handleSubmit(handleForgotPassword)} className="mt-5 space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="email"
|
||||||
|
rules={{
|
||||||
|
required: "Email is required",
|
||||||
|
validate: (value) => checkEmailValidity(value) || "Email is invalid",
|
||||||
|
}}
|
||||||
|
render={({ field: { value, onChange, ref } }) => (
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
ref={ref}
|
||||||
|
hasError={Boolean(errors.email)}
|
||||||
|
placeholder="name@company.com"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
disabled={resendTimerCode > 0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{resendTimerCode > 0 && (
|
||||||
|
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
|
||||||
|
<CircleCheck height={12} width={12} className="mt-0.5" />
|
||||||
|
We sent the reset link to your email address
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
disabled={!isValid}
|
||||||
|
loading={isSubmitting || resendTimerCode > 0}
|
||||||
|
>
|
||||||
|
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
||||||
|
</Button>
|
||||||
|
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
|
||||||
|
Back to sign in
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,120 +74,118 @@ const ResetPasswordPage: NextPageWithLayout = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
<PageHead title="Reset Password" />
|
<PageHead title="Reset 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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<div className="relative flex flex-col space-y-6">
|
||||||
<div className="mx-auto flex flex-col">
|
<div className="text-center space-y-1 py-4">
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
Set new password
|
||||||
Set new password
|
</h3>
|
||||||
</h3>
|
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
||||||
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
</div>
|
||||||
|
<form
|
||||||
|
className="mt-5 space-y-4"
|
||||||
|
method="POST"
|
||||||
|
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={resetFormData.email}
|
||||||
|
//hasError={Boolean(errors.email)}
|
||||||
|
placeholder="name@company.com"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form
|
<div className="space-y-1">
|
||||||
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||||
method="POST"
|
Password
|
||||||
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
|
</label>
|
||||||
>
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
<Input
|
||||||
<div className="space-y-1">
|
type={showPassword ? "text" : "password"}
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
name="password"
|
||||||
Email
|
value={resetFormData.password}
|
||||||
</label>
|
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
//hasError={Boolean(errors.password)}
|
||||||
<Input
|
placeholder="Enter password"
|
||||||
id="email"
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
name="email"
|
minLength={8}
|
||||||
type="email"
|
onFocus={() => setIsPasswordInputFocused(true)}
|
||||||
value={resetFormData.email}
|
onBlur={() => setIsPasswordInputFocused(false)}
|
||||||
//hasError={Boolean(errors.email)}
|
autoFocus
|
||||||
placeholder="name@company.com"
|
/>
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
{showPassword ? (
|
||||||
disabled
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<Eye
|
||||||
<div className="space-y-1">
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
onClick={() => setShowPassword(true)}
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="password"
|
|
||||||
value={resetFormData.password}
|
|
||||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
|
||||||
//hasError={Boolean(errors.password)}
|
|
||||||
placeholder="Enter password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
minLength={8}
|
|
||||||
onFocus={() => setIsPasswordInputFocused(true)}
|
|
||||||
onBlur={() => setIsPasswordInputFocused(false)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
/>
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
|
||||||
Confirm password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="confirm_password"
|
|
||||||
value={resetFormData.confirm_password}
|
|
||||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
|
||||||
placeholder="Confirm password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!!resetFormData.confirm_password && resetFormData.password !== resetFormData.confirm_password && (
|
|
||||||
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
|
||||||
Set password
|
</div>
|
||||||
</Button>
|
<div className="space-y-1">
|
||||||
</form>
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||||
</div>
|
Confirm password
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
|
<Input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
name="confirm_password"
|
||||||
|
value={resetFormData.confirm_password}
|
||||||
|
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
||||||
|
placeholder="Confirm password"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
/>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(false)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!!resetFormData.confirm_password && resetFormData.password !== resetFormData.confirm_password && (
|
||||||
|
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||||
|
Set password
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,10 +15,11 @@ import { getPasswordStrength } from "@/helpers/password.helper";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import { UserAuthWrapper } from "@/layouts/auth-layout";
|
|
||||||
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
|
||||||
@ -94,116 +95,114 @@ const SetPasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
<PageHead title="Reset Password" />
|
<PageHead title="Reset 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 object-cover"
|
className="w-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<div className="relative flex flex-col space-y-6">
|
||||||
<div className="mx-auto flex flex-col">
|
<div className="text-center space-y-1 py-4">
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
Secure your account
|
||||||
Secure your account
|
</h3>
|
||||||
</h3>
|
<p className="font-medium text-onboarding-text-400">Setting password helps you login securely</p>
|
||||||
<p className="font-medium text-onboarding-text-400">Setting password helps you login securely</p>
|
|
||||||
</div>
|
|
||||||
<form className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96" onSubmit={(e) => handleSubmit(e)}>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
value={user?.email}
|
|
||||||
//hasError={Boolean(errors.email)}
|
|
||||||
placeholder="name@company.com"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
|
||||||
Set a password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="password"
|
|
||||||
value={passwordFormData.password}
|
|
||||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
|
||||||
//hasError={Boolean(errors.password)}
|
|
||||||
placeholder="Enter password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
minLength={8}
|
|
||||||
onFocus={() => setIsPasswordInputFocused(true)}
|
|
||||||
onBlur={() => setIsPasswordInputFocused(false)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isPasswordInputFocused && <PasswordStrengthMeter password={passwordFormData.password} />}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
|
||||||
Confirm password
|
|
||||||
</label>
|
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="confirm_password"
|
|
||||||
value={passwordFormData.confirm_password}
|
|
||||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
|
||||||
placeholder="Confirm password"
|
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
|
||||||
/>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOff
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(false)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Eye
|
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
|
||||||
onClick={() => setShowPassword(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!!passwordFormData.confirm_password &&
|
|
||||||
passwordFormData.password !== passwordFormData.confirm_password && (
|
|
||||||
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
|
||||||
Continue
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<form className="mt-5 space-y-4" onSubmit={(e) => handleSubmit(e)}>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={user?.email}
|
||||||
|
//hasError={Boolean(errors.email)}
|
||||||
|
placeholder="name@company.com"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||||
|
Set a password
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
|
<Input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
name="password"
|
||||||
|
value={passwordFormData.password}
|
||||||
|
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||||
|
//hasError={Boolean(errors.password)}
|
||||||
|
placeholder="Enter password"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
minLength={8}
|
||||||
|
onFocus={() => setIsPasswordInputFocused(true)}
|
||||||
|
onBlur={() => setIsPasswordInputFocused(false)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(false)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isPasswordInputFocused && <PasswordStrengthMeter password={passwordFormData.password} />}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||||
|
Confirm password
|
||||||
|
</label>
|
||||||
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
|
<Input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
name="confirm_password"
|
||||||
|
value={passwordFormData.confirm_password}
|
||||||
|
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
||||||
|
placeholder="Confirm password"
|
||||||
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
/>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(false)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => setShowPassword(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!!passwordFormData.confirm_password &&
|
||||||
|
passwordFormData.password !== passwordFormData.confirm_password && (
|
||||||
|
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -213,9 +212,9 @@ const SetPasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
SetPasswordPage.getLayout = function getLayout(page: ReactElement) {
|
SetPasswordPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return (
|
return (
|
||||||
<UserAuthWrapper>
|
<AuthenticationWrapper>
|
||||||
<DefaultLayout>{page}</DefaultLayout>
|
<DefaultLayout>{page}</DefaultLayout>
|
||||||
</UserAuthWrapper>
|
</AuthenticationWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,19 +32,19 @@ const SignInPage: NextPageWithLayout = observer(() => {
|
|||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
<PageHead title="Sign In" />
|
<PageHead title="Sign In" />
|
||||||
<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-full h-full object-cover"
|
||||||
alt="Plane background pattern"
|
alt="Plane background pattern"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10">
|
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
|
||||||
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
|
||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
||||||
@ -58,10 +58,8 @@ const SignInPage: NextPageWithLayout = observer(() => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<SignInAuthRoot />
|
||||||
<SignInAuthRoot />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,68 @@
|
|||||||
import { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
// ui
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { SignUpView } from "@/components/page-views";
|
import { SignUpAuthRoot } from "@/components/account";
|
||||||
|
import { PageHead } from "@/components/core";
|
||||||
|
// constants
|
||||||
|
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
|
// hooks
|
||||||
|
import { useEventTracker } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "@/layouts/default-layout";
|
import DefaultLayout from "@/layouts/default-layout";
|
||||||
// type
|
// types
|
||||||
import { NextPageWithLayout } from "@/lib/types";
|
import { NextPageWithLayout } from "@/lib/types";
|
||||||
// wrappers
|
// wrappers
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
|
// assets
|
||||||
|
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
|
||||||
|
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
|
||||||
|
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||||
|
|
||||||
const HomePage: NextPageWithLayout = () => <SignUpView />;
|
const HomePage: NextPageWithLayout = observer(() => {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
// hooks
|
||||||
|
const { captureEvent } = useEventTracker();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-screen h-screen overflow-hidden">
|
||||||
|
<PageHead title="Sign Up" />
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<Image
|
||||||
|
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
alt="Plane background pattern"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 w-screen h-screen overflow-hidden overflow-y-auto flex flex-col">
|
||||||
|
<div className="container mx-auto px-10 lg:px-0 flex-shrink-0 relative flex items-center justify-between pb-4 transition-all">
|
||||||
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
|
||||||
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-center text-sm font-medium text-onboarding-text-300">
|
||||||
|
Already have an account?{" "}
|
||||||
|
<Link
|
||||||
|
href="/accounts/sign-in"
|
||||||
|
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
|
||||||
|
className="font-semibold text-custom-primary-100 hover:underline"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
|
||||||
|
<SignUpAuthRoot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
HomePage.getLayout = function getLayout(page: ReactElement) {
|
HomePage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user