forked from github/plane
style: auth screens (#478)
* style: sign in page * style: github and google sign * style: sign with code and password * style: not a member and not authorized for project setting * style: join project icon * chore: comment removed
This commit is contained in:
parent
68150a9d2b
commit
b96d40f106
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// ui
|
// ui
|
||||||
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||||
import { Input, SecondaryButton } from "components/ui";
|
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// services
|
// services
|
||||||
import authenticationService from "services/authentication.service";
|
import authenticationService from "services/authentication.service";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -90,7 +90,7 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className="mt-5 space-y-5">
|
<form className="space-y-5 py-5 px-5">
|
||||||
{(codeSent || codeResent) && (
|
{(codeSent || codeResent) && (
|
||||||
<div className="rounded-md bg-green-50 p-4">
|
<div className="rounded-md bg-green-50 p-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@ -121,7 +121,7 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
|
|||||||
) || "Email ID is not valid",
|
) || "Email ID is not valid",
|
||||||
}}
|
}}
|
||||||
error={errors.email}
|
error={errors.email}
|
||||||
placeholder="Enter your Email ID"
|
placeholder="Enter you email Id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -169,18 +169,20 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
{codeSent ? (
|
{codeSent ? (
|
||||||
<SecondaryButton
|
<PrimaryButton
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full text-center"
|
className="w-full text-center"
|
||||||
|
size="md"
|
||||||
onClick={handleSubmit(handleSignin)}
|
onClick={handleSubmit(handleSignin)}
|
||||||
loading={isSubmitting || (!isValid && isDirty)}
|
loading={isSubmitting || (!isValid && isDirty)}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Signing in..." : "Sign in"}
|
{isSubmitting ? "Signing in..." : "Sign in"}
|
||||||
</SecondaryButton>
|
</PrimaryButton>
|
||||||
) : (
|
) : (
|
||||||
<SecondaryButton
|
<PrimaryButton
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full text-center"
|
className="w-full text-center"
|
||||||
|
size="md"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleSubmit(onSubmit)().then(() => {
|
handleSubmit(onSubmit)().then(() => {
|
||||||
setResendCodeTimer(30);
|
setResendCodeTimer(30);
|
||||||
@ -189,7 +191,7 @@ export const EmailCodeForm = ({ onSuccess }: any) => {
|
|||||||
loading={isSubmitting || (!isValid && isDirty)}
|
loading={isSubmitting || (!isValid && isDirty)}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Sending code..." : "Send code"}
|
{isSubmitting ? "Sending code..." : "Send code"}
|
||||||
</SecondaryButton>
|
</PrimaryButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -60,7 +60,7 @@ export const EmailPasswordForm = ({ onSuccess }: any) => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form className="mt-5" onSubmit={handleSubmit(onSubmit)}>
|
<form className="mt-5 py-5 px-5" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
|
@ -19,28 +19,6 @@ export const EmailSignInForm: FC<EmailSignInFormProps> = (props) => {
|
|||||||
) : (
|
) : (
|
||||||
<EmailPasswordForm onSuccess={handleSuccess} />
|
<EmailPasswordForm onSuccess={handleSuccess} />
|
||||||
)}
|
)}
|
||||||
<div className="mt-6">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="absolute inset-0 flex items-center">
|
|
||||||
<div className="w-full border-t border-gray-300" />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex justify-center text-sm">
|
|
||||||
<span className="bg-white px-2 text-gray-500">or</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <div className="mt-6 flex w-full flex-col items-stretch gap-y-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex w-full items-center rounded border border-gray-300 px-3 py-2 text-sm duration-300 hover:bg-gray-100"
|
|
||||||
onClick={() => setUseCode((prev) => !prev)}
|
|
||||||
>
|
|
||||||
<KeyIcon className="h-[25px] w-[25px]" />
|
|
||||||
<span className="w-full text-center font-medium">
|
|
||||||
{useCode ? "Continue with Password" : "Continue with Code"}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import Link from "next/link";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// images
|
// images
|
||||||
import githubImage from "/public/logos/github.png";
|
import githubImage from "/public/logos/github-black.png";
|
||||||
|
|
||||||
const { NEXT_PUBLIC_GITHUB_ID } = process.env;
|
const { NEXT_PUBLIC_GITHUB_ID } = process.env;
|
||||||
|
|
||||||
@ -33,19 +33,15 @@ export const GithubLoginButton: FC<GithubLoginButtonProps> = (props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="px-1 w-full">
|
||||||
<Link
|
<Link
|
||||||
href={`https://github.com/login/oauth/authorize?client_id=${NEXT_PUBLIC_GITHUB_ID}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
|
href={`https://github.com/login/oauth/authorize?client_id=${NEXT_PUBLIC_GITHUB_ID}&redirect_uri=${loginCallBackURL}&scope=read:user,user:email`}
|
||||||
>
|
>
|
||||||
<button className="flex w-full items-center rounded bg-black px-3 py-2 text-sm text-white opacity-90 duration-300 hover:opacity-100">
|
<button className="flex w-full items-center justify-center gap-3 rounded-md border border-gray-200 p-2 text-sm font-medium text-gray-600 duration-300 hover:bg-gray-50">
|
||||||
<Image
|
<Image src={githubImage} height={22} width={22} color="#000" alt="GitHub Logo" />
|
||||||
src={githubImage}
|
<span>Sign In with Github</span>
|
||||||
height={25}
|
|
||||||
width={25}
|
|
||||||
className="flex-shrink-0"
|
|
||||||
alt="GitHub Logo"
|
|
||||||
/>
|
|
||||||
<span className="w-full text-center font-medium">Continue with GitHub</span>
|
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
|
|||||||
theme: "outline",
|
theme: "outline",
|
||||||
size: "large",
|
size: "large",
|
||||||
logo_alignment: "center",
|
logo_alignment: "center",
|
||||||
width: document.getElementById("googleSignInButton")?.offsetWidth,
|
width: "410",
|
||||||
text: "continue_with",
|
text: "continue_with",
|
||||||
} as GsiButtonConfiguration // customization attributes
|
} as GsiButtonConfiguration // customization attributes
|
||||||
);
|
);
|
||||||
@ -47,7 +47,7 @@ export const GoogleLoginButton: FC<IGoogleLoginButton> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Script src="https://accounts.google.com/gsi/client" async defer onLoad={loadScript} />
|
<Script src="https://accounts.google.com/gsi/client" async defer onLoad={loadScript} />
|
||||||
<div className="w-full" id="googleSignInButton" ref={googleSignInButton} />
|
<div className="h-12" id="googleSignInButton" ref={googleSignInButton} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
// next
|
// next
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/default-layout";
|
import DefaultLayout from "layouts/default-layout";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// icons
|
// img
|
||||||
import { LockIcon } from "components/icons";
|
import ProjectSettingImg from "public/project-setting.svg";
|
||||||
|
|
||||||
type TNotAuthorizedViewProps = {
|
type TNotAuthorizedViewProps = {
|
||||||
actionButton?: React.ReactNode;
|
actionButton?: React.ReactNode;
|
||||||
@ -27,25 +28,27 @@ export const NotAuthorizedView: React.FC<TNotAuthorizedViewProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
|
||||||
<LockIcon className="h-16 w-16 text-gray-400" />
|
<div className="h-44 w-72">
|
||||||
|
<Image src={ProjectSettingImg} height="176" width="288" alt="ProjectSettingImg" />
|
||||||
|
</div>
|
||||||
<h1 className="text-xl font-medium text-gray-900">
|
<h1 className="text-xl font-medium text-gray-900">
|
||||||
Oops! You are not authorized to view this page
|
Oops! You are not authorized to view this page
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="w-full md:w-1/3">
|
<div className="w-full text-base text-gray-500 max-w-md ">
|
||||||
{user ? (
|
{user ? (
|
||||||
<p className="text-base font-light">
|
<p className="">
|
||||||
You have signed in as <span className="font-medium">{user.email}</span>.{" "}
|
You have signed in as {user.email}.{" "}
|
||||||
<Link href={`/signin?next=${currentPath}`}>
|
<Link href={`/signin?next=${currentPath}`}>
|
||||||
<a className="font-medium">Sign in</a>
|
<a className="text-gray-900 font-medium">Sign in</a>
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
with different account that has access to this page.
|
with different account that has access to this page.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-base font-light">
|
<p className="">
|
||||||
You need to{" "}
|
You need to{" "}
|
||||||
<Link href={`/signin?next=${currentPath}`}>
|
<Link href={`/signin?next=${currentPath}`}>
|
||||||
<a className="font-medium">Sign in</a>
|
<a className="text-gray-900 font-medium">Sign in</a>
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
with an account that has access to this page.
|
with an account that has access to this page.
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
// next
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { SecondaryButton } from "components/ui";
|
import { PrimaryButton } from "components/ui";
|
||||||
|
// icon
|
||||||
|
import { AssignmentClipboardIcon } from "components/icons";
|
||||||
|
// img
|
||||||
|
import JoinProjectImg from "public/join-project.svg";
|
||||||
|
|
||||||
export interface JoinProjectProps {
|
export interface JoinProjectProps {
|
||||||
isJoiningProject: boolean;
|
isJoiningProject: boolean;
|
||||||
@ -9,18 +15,27 @@ export interface JoinProjectProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const JoinProject: FC<JoinProjectProps> = ({ isJoiningProject, handleJoin }) => (
|
export const JoinProject: FC<JoinProjectProps> = ({ isJoiningProject, handleJoin }) => (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-y-5 text-center">
|
||||||
<div className="space-y-4 text-center">
|
<div className="h-44 w-72">
|
||||||
<h1 className="text-2xl font-bold">You are not a member of this project</h1>
|
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-xl font-medium text-gray-900">You are not a member of this project</h1>
|
||||||
|
|
||||||
|
<div className="w-full max-w-md text-base text-gray-500 ">
|
||||||
<p className="mx-auto w-full text-sm md:w-3/4">
|
<p className="mx-auto w-full text-sm md:w-3/4">
|
||||||
You are not a member of this project, but you can join this project by clicking the button
|
You are not a member of this project, but you can join this project by clicking the button
|
||||||
below.
|
below.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
|
||||||
<SecondaryButton loading={isJoiningProject} onClick={handleJoin}>
|
|
||||||
{isJoiningProject ? "Joining..." : "Click to join"}
|
|
||||||
</SecondaryButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<PrimaryButton
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
loading={isJoiningProject}
|
||||||
|
onClick={handleJoin}
|
||||||
|
>
|
||||||
|
<AssignmentClipboardIcon height={16} width={16} color="white" />
|
||||||
|
{isJoiningProject ? "Joining..." : "Click to join"}
|
||||||
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,9 @@ import projectService from "services/project.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// ui
|
// ui
|
||||||
import { SecondaryButton, Spinner } from "components/ui";
|
import { PrimaryButton, Spinner } from "components/ui";
|
||||||
|
// icon
|
||||||
|
import { LayerDiagonalIcon } from "components/icons";
|
||||||
// components
|
// components
|
||||||
import { NotAuthorizedView } from "components/core";
|
import { NotAuthorizedView } from "components/core";
|
||||||
import { CommandPalette } from "components/command-palette";
|
import { CommandPalette } from "components/command-palette";
|
||||||
@ -103,13 +105,17 @@ const AppLayout: FC<AppLayoutProps> = ({
|
|||||||
actionButton={
|
actionButton={
|
||||||
(memberType?.isViewer || memberType?.isGuest) && projectId ? (
|
(memberType?.isViewer || memberType?.isGuest) && projectId ? (
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
|
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
|
||||||
<SecondaryButton>Go to Issues</SecondaryButton>
|
<PrimaryButton className="flex items-center gap-1">
|
||||||
|
<LayerDiagonalIcon height={16} width={16} color="white" /> Go to Issues
|
||||||
|
</PrimaryButton>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
(memberType?.isViewer || memberType?.isGuest) &&
|
(memberType?.isViewer || memberType?.isGuest) &&
|
||||||
workspaceSlug && (
|
workspaceSlug && (
|
||||||
<Link href={`/${workspaceSlug}`}>
|
<Link href={`/${workspaceSlug}`}>
|
||||||
<SecondaryButton>Go to workspace</SecondaryButton>
|
<PrimaryButton className="flex items-center gap-1">
|
||||||
|
<LayerDiagonalIcon height={16} width={16} color="white" /> Go to workspace
|
||||||
|
</PrimaryButton>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
// ui
|
// ui
|
||||||
import { Spinner } from "components/ui";
|
import { Spinner } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import Logo from "public/logo-with-text.png";
|
import Logo from "public/logo.png";
|
||||||
// types
|
// types
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
@ -105,31 +105,28 @@ const SignInPage: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
<div className="flex h-screen w-full items-center justify-center overflow-auto bg-gray-50">
|
<div className="flex h-screen w-full items-center justify-center overflow-auto bg-gray-50">
|
||||||
<div className="flex min-h-full w-full flex-col justify-center py-12 px-6 lg:px-8">
|
<div className="flex min-h-full w-full flex-col justify-center py-12 px-6 lg:px-8">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
<div className="flex flex-col gap-10 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<div className="text-center">
|
<div className="flex flex-col items-center justify-center gap-10">
|
||||||
<Image src={Logo} height={40} width={179} alt="Plane Web Logo" />
|
<Image src={Logo} height={80} width={80} alt="Plane Web Logo" />
|
||||||
</div>
|
<h2 className="text-center text-xl font-medium text-black">
|
||||||
<h2 className="mt-3 text-center text-3xl font-bold text-gray-900">
|
Sign In to your Plane Account
|
||||||
Sign in to your account
|
|
||||||
</h2>
|
</h2>
|
||||||
<div className="mt-16 bg-white py-8 px-4 sm:rounded-lg sm:px-10">
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col rounded-[10px] bg-white shadow-md">
|
||||||
{parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0") ? (
|
{parseInt(process.env.NEXT_PUBLIC_ENABLE_OAUTH || "0") ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
|
||||||
<EmailSignInForm handleSuccess={onSignInSuccess} />
|
<EmailSignInForm handleSuccess={onSignInSuccess} />
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
<div className="flex flex-col gap-3 py-5 px-5 border-t items-center justify-center border-gray-300 ">
|
||||||
<GoogleLoginButton handleSignIn={handleGoogleSignIn} />
|
<GoogleLoginButton handleSignIn={handleGoogleSignIn} />
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<GithubLoginButton handleSignIn={handleGithubSignIn} />
|
<GithubLoginButton handleSignIn={handleGithubSignIn} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
|
||||||
<EmailPasswordForm onSuccess={onSignInSuccess} />
|
<EmailPasswordForm onSuccess={onSignInSuccess} />
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
52
apps/app/public/join-project.svg
Normal file
52
apps/app/public/join-project.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 27 KiB |
44
apps/app/public/project-setting.svg
Normal file
44
apps/app/public/project-setting.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 33 KiB |
Loading…
Reference in New Issue
Block a user