diff --git a/client/packages/lowcoder/src/api/userApi.ts b/client/packages/lowcoder/src/api/userApi.ts index 40324bb86..5b81e7135 100644 --- a/client/packages/lowcoder/src/api/userApi.ts +++ b/client/packages/lowcoder/src/api/userApi.ts @@ -43,6 +43,12 @@ export interface ApiKeyPayload { description?: string; } +export interface ResetLostPasswordPayload { + token: string; + userEmail: string; + newPassword: string; +} + export interface FetchApiKeysResponse extends ApiResponse { data: { id: string; @@ -69,6 +75,8 @@ class UserApi extends Api { static markUserStatusURL = "/users/mark-status"; static userDetailURL = (id: string) => `/users/userDetail/${id}`; static resetPasswordURL = `/users/reset-password`; + static forgotPasswordURL = `/users/lost-password`; + static resetLostPasswordURL = `/users/reset-lost-password`; static fetchApiKeysURL = `/auth/api-keys`; static createApiKeyURL = `/auth/api-key`; static deleteApiKeyURL = (id: string) => `/auth/api-key/${id}`; @@ -138,6 +146,15 @@ class UserApi extends Api { return Api.post(UserApi.resetPasswordURL, { userId: userId }); } + static forgotPassword(userEmail: string): AxiosPromise { + return Api.post(UserApi.forgotPasswordURL, { userEmail }); + } + + static resetLostPassword(request: ResetLostPasswordPayload): AxiosPromise { + console.log(request); + return Api.post(UserApi.resetLostPasswordURL, request); + } + static createApiKey({ name, description = '' diff --git a/client/packages/lowcoder/src/app.tsx b/client/packages/lowcoder/src/app.tsx index ceccbe714..97cc75374 100644 --- a/client/packages/lowcoder/src/app.tsx +++ b/client/packages/lowcoder/src/app.tsx @@ -24,6 +24,8 @@ import { TRASH_URL, USER_AUTH_URL, ADMIN_APP_URL, + ORG_AUTH_FORGOT_PASSWORD_URL, + ORG_AUTH_RESET_PASSWORD_URL, } from "constants/routesURL"; import React from "react"; @@ -173,6 +175,8 @@ class AppIndex extends React.Component { + + diff --git a/client/packages/lowcoder/src/constants/authConstants.ts b/client/packages/lowcoder/src/constants/authConstants.ts index 6c19a0bf8..965fee788 100644 --- a/client/packages/lowcoder/src/constants/authConstants.ts +++ b/client/packages/lowcoder/src/constants/authConstants.ts @@ -1,10 +1,14 @@ import { AUTH_BIND_URL, + AUTH_FORGOT_PASSWORD_URL, AUTH_LOGIN_URL, AUTH_REGISTER_URL, + AUTH_RESET_PASSWORD_URL, OAUTH_REDIRECT, + ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_LOGIN_URL, ORG_AUTH_REGISTER_URL, + ORG_AUTH_RESET_PASSWORD_URL, } from "constants/routesURL"; import { InviteInfo } from "api/inviteApi"; import Login, { ThirdPartyBindCard } from "pages/userAuth/login"; @@ -18,6 +22,8 @@ import { KeyCloakLoginIcon, EmailLoginIcon } from "assets/icons"; +import ForgotPassword from "pages/userAuth/forgotPassword"; +import ResetPassword from "pages/userAuth/resetPassword"; export type AuthInviteInfo = InviteInfo & { invitationId: string }; export type AuthLocationState = { inviteInfo?: AuthInviteInfo; thirdPartyAuthError?: boolean }; @@ -85,9 +91,13 @@ export const AuthRoutes: Array<{ path: string; component: React.ComponentType diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 262b4a5ae..163737f1a 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2810,8 +2810,12 @@ export const en = { "userAuth": { "registerByEmail": "Sign Up", "email": "Email:", - "inputEmail": "Please Enter Your Email", - "inputValidEmail": "Please Enter a Valid Email", + "inputEmail": "Please enter your email", + "inputValidEmail": "Please enter a valid email", + "forgotPassword": "Forgot Password", + "forgotPasswordInfo": "Enter your email and we'll send you a link to reset your password.", + "forgotPasswordSuccess": "Please check your email for reset password link.", + "forgotPasswordError": "Something went wrong. Please try again.", "register": "Sign Up", "userLogin": "Sign In", "login": "Sign In", @@ -2836,6 +2840,7 @@ export const en = { "resetPasswordDesc": "Reset User {name}'s Password. A New Password Will Be Generated After Reset.", "resetSuccess": "Reset Succeeded", "resetSuccessDesc": "Password Reset Succeeded. The New Password is: {password}", + "resetLostPasswordSuccess": "Password Reset Succeeded. Please login again.", "copyPassword": "Copy Password", "poweredByLowcoder": "Powered by: Lowcoder.cloud" }, diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx index 1383e5731..144de5640 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx @@ -335,8 +335,8 @@ export function HomeLayout(props: HomeLayoutProps) { const resList: HomeRes[] = displayElements .filter((e) => searchValue - ? e.name.toLocaleLowerCase().includes(searchValue) || - e.createBy.toLocaleLowerCase().includes(searchValue) + ? e.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase()) || + e.createBy.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase()) : true ) .filter((e) => { diff --git a/client/packages/lowcoder/src/pages/userAuth/forgotPassword.tsx b/client/packages/lowcoder/src/pages/userAuth/forgotPassword.tsx new file mode 100644 index 000000000..e8c66ea2f --- /dev/null +++ b/client/packages/lowcoder/src/pages/userAuth/forgotPassword.tsx @@ -0,0 +1,101 @@ +import React, { useContext, useState, useMemo } from "react"; +import { + AuthContainer, + ConfirmButton, + FormWrapperMobile, + StyledRouteLinkLogin, +} from "pages/userAuth/authComponents"; +import { FormInput, PasswordInput, messageInstance } from "lowcoder-design"; +import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL"; +import UserApi from "api/userApi"; +import { useRedirectUrl } from "util/hooks"; +import { checkEmailValid } from "util/stringUtils"; +import styled from "styled-components"; +import { requiresUnAuth } from "./authHOC"; +import { useLocation } from "react-router-dom"; +import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants"; +import { trans } from "i18n"; +import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils"; +import { useParams } from "react-router-dom"; +import { Divider } from "antd"; +import { validateResponse } from "api/apiUtils"; + +const StyledFormInput = styled(FormInput)` + margin-bottom: 16px; +`; + +const RegisterContent = styled(FormWrapperMobile)` + display: flex; + flex-direction: column; + margin-bottom: 0px; +`; + +function ForgotPassword() { + const [account, setAccount] = useState(""); + const [loading, setLoading] = useState(false); + const location = useLocation(); + + const orgId = useParams().orgId; + + const onSubmit = () => { + setLoading(true); + UserApi.forgotPassword(account) + .then((resp) => { + // TODO: need proper response from BE + // if (validateResponse(resp)) { + // messageInstance.success(trans("userAuth.forgotPasswordSuccess")); + // } + if (resp.status === 200) { + messageInstance.success(trans("userAuth.forgotPasswordSuccess")); + } + }) + .catch((e) => { + messageInstance.error(trans("userAuth.forgotPasswordError")); + }) + .finally(() => { + setLoading(false); + }) + } + + const forgotPasswordHeading = trans("userAuth.forgotPassword") + const subHeading = trans("userAuth.poweredByLowcoder"); + + return ( + +

{trans("userAuth.forgotPasswordInfo")}

+ + setAccount(valid ? value : "")} + placeholder={trans("userAuth.inputEmail")} + checkRule={{ + check: checkEmailValid, + errorMsg: trans("userAuth.inputValidEmail"), + }} + /> + + {trans("button.submit")} + + + + {trans("userAuth.userLogin")} + +
+ ); +} + +export default requiresUnAuth(ForgotPassword); diff --git a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx index a993bf9f9..e43e3b94f 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLogin.tsx @@ -15,14 +15,19 @@ import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants"; import { trans } from "i18n"; import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils"; import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; -import { AUTH_REGISTER_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL"; -import { useLocation, useParams } from "react-router-dom"; +import { AUTH_FORGOT_PASSWORD_URL, AUTH_REGISTER_URL, ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL"; +import { Link, useLocation, useParams } from "react-router-dom"; import { Divider } from "antd"; +import Flex from "antd/es/flex"; const AccountLoginWrapper = styled(FormWrapperMobile)` display: flex; flex-direction: column; - margin-bottom: 40px; + margin-bottom: 0px; + + .form-input.password-input { + margin-bottom: 0px; + } `; type FormLoginProps = { @@ -70,10 +75,21 @@ export default function FormLogin(props: FormLoginProps) { }} /> setPassword(value)} valueCheck={() => [true, ""]} /> + + + {`${trans("userAuth.forgotPassword")}?`} + + {trans("userAuth.login")} diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx index 6dd833548..88e6cadd7 100644 --- a/client/packages/lowcoder/src/pages/userAuth/register.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx @@ -33,7 +33,7 @@ const StyledPasswordInput = styled(PasswordInput)` const RegisterContent = styled(FormWrapperMobile)` display: flex; flex-direction: column; - margin-bottom: 40px; + margin-bottom: 0px; `; function UserRegister() { diff --git a/client/packages/lowcoder/src/pages/userAuth/resetPassword.tsx b/client/packages/lowcoder/src/pages/userAuth/resetPassword.tsx new file mode 100644 index 000000000..9f8d7e619 --- /dev/null +++ b/client/packages/lowcoder/src/pages/userAuth/resetPassword.tsx @@ -0,0 +1,118 @@ +import React, { useContext, useState, useMemo } from "react"; +import { + AuthContainer, + ConfirmButton, + FormWrapperMobile, + StyledRouteLinkLogin, +} from "pages/userAuth/authComponents"; +import { FormInput, PasswordInput, messageInstance } from "lowcoder-design"; +import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL"; +import UserApi from "api/userApi"; +import { checkEmailValid } from "util/stringUtils"; +import styled from "styled-components"; +import { requiresUnAuth } from "./authHOC"; +import { useLocation } from "react-router-dom"; +import { trans } from "i18n"; +import { checkPassWithMsg } from "pages/userAuth/authUtils"; +import { useParams } from "react-router-dom"; +import { Divider } from "antd"; +import { validateResponse } from "api/apiUtils"; + +const StyledFormInput = styled(FormInput)` + margin-bottom: 16px; +`; + +const StyledPasswordInput = styled(PasswordInput)` + margin-bottom: 16px; +`; + +const RegisterContent = styled(FormWrapperMobile)` + display: flex; + flex-direction: column; + margin-bottom: 0px; +`; + +function ResetPassword() { + const [submitBtnDisable, setSubmitBtnDisable] = useState(false); + const [loading, setLoading] = useState(false); + const [account, setAccount] = useState(""); + const [password, setPassword] = useState(""); + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + + const orgId = useParams().orgId; + const token = queryParams.get('token') ?? ''; + + const onSubmit = () => { + setLoading(true); + UserApi.resetLostPassword({ + token, + userEmail: account, + newPassword: password, + }) + .then((resp) => { + // TODO: need proper response from BE + // if (validateResponse(resp)) { + // messageInstance.success(trans("userAuth.resetLostPasswordSuccess")); + // } + if (resp.status === 200) { + messageInstance.success(trans("userAuth.resetLostPasswordSuccess")); + } + }) + .catch((e) => { + messageInstance.error(trans("userAuth.forgotPasswordError")); + }) + .finally(() => { + setLoading(false); + }) + } + + const registerHeading = trans("userAuth.resetPassword") + const registerSubHeading = trans("userAuth.poweredByLowcoder"); + + return ( + + + setAccount(valid ? value : "")} + placeholder={trans("userAuth.inputEmail")} + checkRule={{ + check: checkEmailValid, + errorMsg: trans("userAuth.inputValidEmail"), + }} + /> + setPassword(valid ? value : "")} + doubleCheck + /> + + {trans("button.submit")} + + + + {trans("userAuth.userLogin")} + + + ); +} + +export default requiresUnAuth(ResetPassword); \ No newline at end of file