diff --git a/src/App.tsx b/src/App.tsx
index 12004605..dddb9f81 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,14 +2,18 @@ import * as Sentry from '@sentry/react';
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom';
import ErrorFallback from './components/ErrorFallback';
+import ForgotPassword from './components/ForgotPassword';
import MagicLinkSuccessContent from './components/MagicLinkSuccessContent';
import MobileAuth from './components/MobileAuth';
import Redirection from './components/Redirection';
+import ResetPassword from './components/ResetPassword';
import SignIn from './components/SignIn';
import SignUp from './components/SignUp';
import {
+ FORGOT_PASSWORD_PATH,
HOME_PATH,
MOBILE_AUTH_PATH,
+ RESET_PASSWORD_PATH,
SIGN_IN_MAGIC_LINK_SUCCESS_PATH,
SIGN_IN_PATH,
SIGN_UP_PATH,
@@ -26,6 +30,8 @@ const App = () => (
element={}
/>
} />
+ } />
+ } />
} />
} />
diff --git a/src/components/ForgotPassword.tsx b/src/components/ForgotPassword.tsx
new file mode 100644
index 00000000..fd445bb6
--- /dev/null
+++ b/src/components/ForgotPassword.tsx
@@ -0,0 +1,64 @@
+import { useState } from 'react';
+import { Link } from 'react-router-dom';
+
+import { Button, GraaspLogo } from '@graasp/ui';
+
+import { Stack, useTheme } from '@mui/material';
+import FormControl from '@mui/material/FormControl';
+import Typography from '@mui/material/Typography';
+
+import { useAuthTranslation } from '../config/i18n';
+import { SIGN_IN_PATH } from '../config/paths';
+import { AUTH } from '../langs/constants';
+import EmailInput from './EmailInput';
+import FullscreenContainer from './FullscreenContainer';
+
+const ForgotPassword = () => {
+ const { t } = useAuthTranslation();
+ const theme = useTheme();
+
+ // enable validation after first click
+ const [shouldValidate, setShouldValidate] = useState(false);
+ const [email, setEmail] = useState('');
+
+ const resetPassword = () => {
+ setShouldValidate(true);
+ };
+
+ const handleKeypress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ resetPassword();
+ }
+ };
+
+ return (
+
+ {
+
+
+
+
+ {t(AUTH.FORGOT_PASSWORD_TITLE)}
+
+
+
+
+
+
+
+
+ {t(AUTH.FORGOT_PASSWORD_BACK_BUTTON)}
+
+ }
+
+ );
+};
+
+export default ForgotPassword;
diff --git a/src/components/ResetPassword.tsx b/src/components/ResetPassword.tsx
new file mode 100644
index 00000000..0bdd96c0
--- /dev/null
+++ b/src/components/ResetPassword.tsx
@@ -0,0 +1,123 @@
+import { useState } from 'react';
+import { Link } from 'react-router-dom';
+
+import { Button, GraaspLogo } from '@graasp/ui';
+
+import { Stack, useTheme } from '@mui/material';
+import FormControl from '@mui/material/FormControl';
+import Typography from '@mui/material/Typography';
+
+import { useAuthTranslation } from '../config/i18n';
+import { SIGN_IN_PATH } from '../config/paths';
+import { AUTH } from '../langs/constants';
+import { passwordValidator } from '../utils/validation';
+import FullscreenContainer from './FullscreenContainer';
+import StyledTextField from './StyledTextField';
+
+const ResetPassword = () => {
+ const { t } = useAuthTranslation();
+ const theme = useTheme();
+
+ // enable validation after first click
+ const [passwordError, setPasswordError] = useState(null);
+ const [confirmPasswordError, setConfirmPasswordError] = useState<
+ string | null
+ >(null);
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+
+ const resetPassword = () => {
+ const checkingPassword = passwordValidator(password);
+ const checkingConfirmPassword = passwordValidator(confirmPassword);
+ if (checkingPassword || checkingConfirmPassword) {
+ if (checkingPassword) {
+ setPasswordError(checkingPassword);
+ }
+ if (checkingConfirmPassword) {
+ setConfirmPasswordError(checkingConfirmPassword);
+ }
+ } else {
+ setPasswordError(null);
+ setConfirmPasswordError(null);
+ // const token = await executeCaptcha(
+ // isMobile
+ // ? RecaptchaAction.SignInWithPasswordMobile
+ // : RecaptchaAction.SignInWithPassword,
+ // );
+ // const result = await (isMobile
+ // ? mobileSignInWithPassword({
+ // email: lowercaseEmail,
+ // password,
+ // captcha: token,
+ // challenge,
+ // })
+ // : signInWithPassword({
+ // email: lowercaseEmail,
+ // password,
+ // captcha: token,
+ // url: redirect.url,
+ // }));
+ // // successful redirect
+ // if (result && result.resource) {
+ // window.location.href = result.resource;
+ // }
+ }
+ };
+
+ const handleKeypress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ resetPassword();
+ }
+ };
+
+ return (
+
+ {
+
+
+
+
+ {t(AUTH.RESET_PASSWORD_TITLE)}
+
+
+
+
+ {
+ setPassword(e.target.value);
+ }}
+ type="password"
+ onKeyDown={handleKeypress}
+ />
+ {
+ setConfirmPassword(e.target.value);
+ }}
+ type="password"
+ onKeyDown={handleKeypress}
+ />
+
+
+
+ {t(AUTH.FORGOT_PASSWORD_BACK_BUTTON)}
+
+ }
+
+ );
+};
+
+export default ResetPassword;
diff --git a/src/components/SignInPasswordForm.tsx b/src/components/SignInPasswordForm.tsx
index 3c2d343e..57bf67ca 100644
--- a/src/components/SignInPasswordForm.tsx
+++ b/src/components/SignInPasswordForm.tsx
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
import { RecaptchaAction } from '@graasp/sdk';
import { Alert, LoadingButton } from '@mui/lab';
import { useAuthTranslation } from '../config/i18n';
+import { FORGOT_PASSWORD_PATH } from '../config/paths';
import { mutations } from '../config/queryClient';
import {
EMAIL_SIGN_IN_FIELD_ID,
@@ -131,6 +133,7 @@ const SignInPasswordForm = () => {
>
{t(SIGN_IN_BUTTON)}
+ {t(AUTH.FORGOT_PASSWORD_LINK)}
{(signInWithPasswordSuccess || mobileSignInWithPasswordSuccess) && (
{t(AUTH.PASSWORD_SUCCESS_ALERT)}
diff --git a/src/config/paths.ts b/src/config/paths.ts
index ae04b62a..ca846f73 100644
--- a/src/config/paths.ts
+++ b/src/config/paths.ts
@@ -3,3 +3,5 @@ export const SIGN_IN_MAGIC_LINK_SUCCESS_PATH = '/signin/success';
export const SIGN_UP_PATH = '/signup';
export const HOME_PATH = '/';
export const MOBILE_AUTH_PATH = '/auth';
+export const FORGOT_PASSWORD_PATH = '/password/forgot';
+export const RESET_PASSWORD_PATH = '/password/reset';
diff --git a/src/langs/constants.ts b/src/langs/constants.ts
index b6313a20..3d42db4c 100644
--- a/src/langs/constants.ts
+++ b/src/langs/constants.ts
@@ -41,4 +41,10 @@ export const AUTH = {
EMPTY_EMAIL_ERROR: 'EMPTY_EMAIL_ERROR',
PASSWORD_EMPTY_ERROR: 'PASSWORD_EMPTY_ERROR',
PASSWORD_SUCCESS_ALERT: 'PASSWORD_SUCCESS_ALERT',
+ FORGOT_PASSWORD_LINK: 'FORGOT_PASSWORD_LINK',
+ FORGOT_PASSWORD_TITLE: 'FORGOT_PASSWORD_TITLE',
+ FORGOT_PASSWORD_BUTTON: 'FORGOT_PASSWORD_BUTTON',
+ FORGOT_PASSWORD_BACK_BUTTON: 'FORGOT_PASSWORD_BACK_BUTTON',
+ RESET_PASSWORD_TITLE: 'RESET_PASSWORD_TITLE',
+ CONFIRM_PASSWORD_FIELD_LABEL: 'CONFIRM_PASSWORD_FIELD_LABEL',
};
diff --git a/src/langs/en.json b/src/langs/en.json
index a16183a2..855ca059 100644
--- a/src/langs/en.json
+++ b/src/langs/en.json
@@ -39,5 +39,11 @@
"INVALID_EMAIL_ERROR": "This does not look like a valid email address",
"EMPTY_EMAIL_ERROR": "An email address is required, this field can not be empty",
"PASSWORD_EMPTY_ERROR": "The password can not be empty",
- "PASSWORD_SUCCESS_ALERT": "You successfully signed in. You will be redirected soon."
+ "PASSWORD_SUCCESS_ALERT": "You successfully signed in. You will be redirected soon.",
+ "FORGOT_PASSWORD_LINK": "Forgot your password? Click here to reset",
+ "FORGOT_PASSWORD_TITLE": "Request new password",
+ "FORGOT_PASSWORD_BUTTON": "Reset",
+ "FORGOT_PASSWORD_BACK_BUTTON": "Back to sign in",
+ "RESET_PASSWORD_TITLE": "Reset your password",
+ "CONFIRM_PASSWORD_FIELD_LABEL": "Confirm password"
}