Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,68 +1,36 @@
import React from "react";
import { FC } from "react";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
// services
import userService from "services/user.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
// types
type Props = {
onSubmit: (formValues: any) => void;
};

export const EmailResetPasswordForm: React.FC<Props> = (props) => {
export interface EmailForgotPasswordFormValues {
email: string;
}

export interface IEmailForgotPasswordForm {
onSubmit: (formValues: any) => Promise<void>;
}

export const EmailForgotPasswordForm: FC<IEmailForgotPasswordForm> = (props) => {
const { onSubmit } = props;
// toast
const { setToastAlert } = useToast();
// router
const router = useRouter();
// form data
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({
} = useForm<EmailForgotPasswordFormValues>({
defaultValues: {
email: "",
},
mode: "onChange",
reValidateMode: "onChange",
});

const forgotPassword = async (formData: any) => {
const payload = {
email: formData.email,
};

await userService
.forgotPassword(payload)
.then(() =>
setToastAlert({
type: "success",
title: "Success!",
message: "Password reset link has been sent to your email address.",
})
)
.catch((err) => {
if (err.status === 400)
setToastAlert({
type: "error",
title: "Error!",
message: "Please check the Email ID entered.",
});
else
setToastAlert({
type: "error",
title: "Error!",
message: "Something went wrong. Please try again.",
});
});
};

return (
<form className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto" onSubmit={handleSubmit(forgotPassword)}>
<form className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-1">
<Input
id="email"
Expand Down
19 changes: 18 additions & 1 deletion web/components/account/email-password-form.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from "react";
import { useForm } from "react-hook-form";
import { useRouter } from "next/router";
// ui
import { Input, PrimaryButton } from "components/ui";
import Link from "next/link";
// types
export interface EmailPasswordFormValues {
email: string;
Expand All @@ -15,6 +17,8 @@ export interface IEmailPasswordForm {

export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
const { onSubmit } = props;
// router
const router = useRouter();
// form info
const {
register,
Expand Down Expand Up @@ -66,7 +70,11 @@ export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
/>
</div>
<div className="text-right text-xs">
<button type="button" onClick={() => {}} className="text-custom-text-200 hover:text-custom-primary-100">
<button
type="button"
onClick={() => router.push("/accounts/forgot-password")}
className="text-custom-text-200 hover:text-custom-primary-100"
>
Forgot your password?
</button>
</div>
Expand All @@ -80,6 +88,15 @@ export const EmailPasswordForm: React.FC<IEmailPasswordForm> = (props) => {
{isSubmitting ? "Signing in..." : "Sign in"}
</PrimaryButton>
</div>
<div className="text-xs">
<button
type="button"
onClick={() => router.push("/accounts/sign-up")}
className="text-custom-text-200 hover:text-custom-primary-100"
>
{"Don't have an account? Sign Up"}
</button>
</div>
</form>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion web/components/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from "./email-code-form";
export * from "./email-password-form";
export * from "./email-reset-password-form";
export * from "./email-forgot-password-form";
export * from "./github-login-button";
export * from "./google-login";
export * from "./email-signup-form";
1 change: 1 addition & 0 deletions web/components/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./product-updates-modal";
52 changes: 52 additions & 0 deletions web/components/headers/workspace-dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useState } from "react";
import { LayoutGrid, Zap } from "lucide-react";
import Image from "next/image";
import { useTheme } from "next-themes";
// images
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
// components
import { ProductUpdatesModal } from "components/common";

export const WorkspaceDashboardHeader = () => {
const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
// theme
const { resolvedTheme } = useTheme();

return (
<>
<ProductUpdatesModal isOpen={isProductUpdatesModalOpen} setIsOpen={setIsProductUpdatesModalOpen} />
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 pl-3">
<LayoutGrid size={14} strokeWidth={2} />
Dashboard
</div>
<div className="flex items-center gap-3 px-3">
<button
onClick={() => setIsProductUpdatesModalOpen(true)}
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded"
>
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
{"What's New?"}
</button>
<a
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded"
href="https://github.com/makeplane/plane"
target="_blank"
rel="noopener noreferrer"
>
<Image
src={resolvedTheme === "dark" ? githubWhiteImage : githubBlackImage}
height={16}
width={16}
alt="GitHub Logo"
/>
Star us on GitHub
</a>
</div>
</div>
</>
);
};
1 change: 0 additions & 1 deletion web/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export * from "./spinner";
export * from "./tooltip";
export * from "./toggle-switch";
export * from "./markdown-to-component";
export * from "./product-updates-modal";
export * from "./integration-and-import-export-banner";
export * from "./range-datepicker";
export * from "./circular-progress";
Expand Down
1 change: 1 addition & 0 deletions web/components/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./user-greetings";
49 changes: 49 additions & 0 deletions web/components/user/user-greetings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { FC } from "react";
import { IUser } from "types";

export interface IUserGreetingsView {
user: IUser;
}

export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
const { user } = props;

const currentTime = new Date();

const hour = new Intl.DateTimeFormat("en-US", {
hour12: false,
hour: "numeric",
}).format(currentTime);

const date = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
}).format(currentTime);

const weekDay = new Intl.DateTimeFormat("en-US", {
weekday: "long",
}).format(currentTime);

const timeString = new Intl.DateTimeFormat("en-US", {
timeZone: user?.user_timezone,
hour12: false, // Use 24-hour format
hour: "2-digit",
minute: "2-digit",
}).format(currentTime);

const greeting = parseInt(hour, 10) < 12 ? "morning" : parseInt(hour, 10) < 18 ? "afternoon" : "evening";

return (
<div>
<h3 className="text-2xl font-semibold">
Good {greeting}, {user?.first_name} {user?.last_name}
</h3>
<h6 className="text-custom-text-400 font-medium flex items-center gap-2">
<div>{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}</div>
<div>
{weekDay}, {date} {timeString}
</div>
</h6>
</div>
);
};
1 change: 1 addition & 0 deletions web/components/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./modal";
export * from "./select-filters";
export * from "./single-view-item";
export * from "./signin";
export * from "./workspace-dashboard";
44 changes: 22 additions & 22 deletions web/components/views/signin.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import { useRouter } from "next/router";
// hooks
import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
Expand All @@ -13,25 +14,37 @@ import {
GithubLoginButton,
EmailCodeForm,
EmailPasswordForm,
EmailResetPasswordForm,
EmailPasswordFormValues,
} from "components/account";
// ui
import { Spinner } from "@plane/ui";
// images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
import { IUserSettings } from "types";

const appConfigService = new AppConfigService();
const authService = new AuthService();

export const SignInView = observer(() => {
const { user: userStore } = useMobxStore();
// router
const router = useRouter();
// toast
const { setToastAlert } = useToast();
// fetch app config
const { data } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
// fetch user info
const { data: user, error } = useSWR("USER_INFO", () => userStore.fetchCurrentUser());
// computed
const enableEmailPassword =
data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github);

const handleLoginRedirection = () =>
userStore.fetchCurrentUserSettings().then((userSettings: IUserSettings) => {
const workspaceSlug =
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
router.push(`/${workspaceSlug}`);
});

const handleGoogleSignIn = async ({ clientId, credential }: any) => {
try {
Expand All @@ -42,9 +55,8 @@ export const SignInView = observer(() => {
clientId,
};
const response = await authService.socialAuth(socialAuthPayload);
if (response && response?.user) {
mutateUser();
handleTheme(response?.user);
if (response) {
handleLoginRedirection();
}
} else {
throw Error("Cant find credentials");
Expand All @@ -67,9 +79,8 @@ export const SignInView = observer(() => {
clientId: data.github,
};
const response = await authService.socialAuth(socialAuthPayload);
if (response && response?.user) {
mutateUser();
handleTheme(response?.user);
if (response) {
handleLoginRedirection();
}
} else {
throw Error("Cant find credentials");
Expand All @@ -87,17 +98,8 @@ export const SignInView = observer(() => {
await authService
.emailLogin(formData)
.then((response) => {
try {
if (response) {
mutateUser();
handleTheme(response?.user);
}
} catch (err: any) {
setToastAlert({
type: "error",
title: "Error!",
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
});
if (response) {
handleLoginRedirection();
}
})
.catch((err) =>
Expand All @@ -112,8 +114,7 @@ export const SignInView = observer(() => {
const handleEmailCodeSignIn = async (response: any) => {
try {
if (response) {
mutateUser();
handleTheme(response?.user);
handleLoginRedirection();
}
} catch (err: any) {
setToastAlert({
Expand Down Expand Up @@ -147,9 +148,8 @@ export const SignInView = observer(() => {
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
Sign in to Plane
</h1>
<EmailResetPasswordForm />
<>
{data?.email_password_login && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
{data?.magic_login && (
<div className="flex flex-col divide-y divide-custom-border-200">
<div className="pb-7">
Expand Down
Loading