Add reset password page

pull/3813/head
Kamran Ahmed 2 years ago
parent 725aa2b092
commit 3072e42b0a
  1. 98
      src/components/AuthenticationFlow/ResetPasswordForm.tsx
  2. 9
      src/components/Authenticator/authenticator.ts
  3. 135
      src/components/Profile/ResetPasswordForm.tsx
  4. 29
      src/pages/reset-password.astro

@ -0,0 +1,98 @@
import { useEffect, useState } from 'preact/hooks';
import Spinner from '../Spinner';
import { httpPost } from '../../lib/http';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/constants';
export default function ResetPasswordForm() {
const [code, setCode] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (!code) {
window.location.href = '/login';
} else {
setCode(code);
}
}, []);
const handleSubmit = async (e: Event) => {
e.preventDefault();
setIsLoading(true);
if (password !== passwordConfirm) {
setIsLoading(false);
setError('Passwords do not match.');
return;
}
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-reset-forgotten-password`,
{
newPassword: password,
confirmPassword: passwordConfirm,
code,
}
);
if (error?.message) {
setIsLoading(false);
setError(error.message);
return;
}
if (!response?.token) {
setIsLoading(false);
setError('Something went wrong. Please try again later.');
return;
}
const token = response.token;
Cookies.set(TOKEN_COOKIE_NAME, token);
window.location.href = '/';
};
return (
<form className="mx-auto w-full" onSubmit={handleSubmit}>
<input
type="password"
className="mb-2 mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
minLength={6}
placeholder="New Password"
value={password}
onInput={(e) => setPassword((e.target as HTMLInputElement).value)}
/>
<input
type="password"
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
minLength={6}
placeholder="Confirm New Password"
value={passwordConfirm}
onInput={(e) =>
setPasswordConfirm((e.target as HTMLInputElement).value)
}
/>
{error && (
<p className="mt-2 rounded-lg bg-red-100 p-2 text-red-700">{error}</p>
)}
<button
type="submit"
disabled={isLoading}
className="mt-2 inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
>
{isLoading ? 'Please wait...' : 'Reset Password'}
</button>
</form>
);
}

@ -47,7 +47,14 @@ function handleGuest() {
// Prepares the UI for the user who is logged out // Prepares the UI for the user who is logged out
function handleAuthenticated() { function handleAuthenticated() {
const guestRoutes = ['/login', '/signup']; const guestRoutes = [
'/login',
'/signup',
'/verify-account',
'/verification-pending',
'/reset-password',
'/forgot-password',
];
showHideGuestElements('hide'); showHideGuestElements('hide');
showHideAuthElements('show'); showHideAuthElements('show');

@ -1,135 +0,0 @@
import { useState } from 'preact/hooks';
import Spinner from '../Spinner';
export default function ResetPasswordForm() {
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState<{
type: 'error' | 'success' | 'info';
message: string;
}>();
const handleSubmit = async (e: Event) => {
e.preventDefault();
setIsLoading(true);
if (password !== passwordConfirm) {
setIsLoading(false);
return setMessage({
type: 'error',
message: 'Passwords do not match.',
});
}
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const res = await fetch(
'http://localhost:8080/v1-reset-forgotten-password',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
newPassword: password,
confirmPassword: passwordConfirm,
code,
}),
}
);
const data = await res.json();
if (!res.ok) {
setIsLoading(false);
setMessage({
type: 'error',
message: data.message,
});
return;
}
setIsLoading(false);
setPassword('');
setPasswordConfirm('');
setMessage({
type: 'success',
message: 'Your password has been reset.',
});
// TODO: Redirect to login page after 2 seconds
setTimeout(() => {
window.location.href = '/login';
}, 2000);
};
return (
<form className="mx-auto max-w-md" onSubmit={handleSubmit}>
<h2 className="mb-6 text-3xl font-bold sm:text-center sm:text-4xl">
Reset your password
</h2>
<label
for="new-password"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
New Password
</label>
<input
type="password"
name="new-password"
id="new-password"
className="mt-2 mb-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
minLength={6}
placeholder="Enter a new password"
value={password}
onInput={(e) => setPassword((e.target as HTMLInputElement).value)}
/>
<label
for="new-password-confirm"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Confirm New Password
</label>
<input
type="password"
name="new-password-confirm"
id="new-password-confirm"
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
minLength={6}
placeholder="Confirm your new password"
value={passwordConfirm}
onInput={(e) =>
setPasswordConfirm((e.target as HTMLInputElement).value)
}
/>
{message && (
<div
className={`mt-2 rounded-lg p-2 ${
message.type === 'error'
? 'bg-red-100 text-red-700'
: message.type === 'success'
? 'bg-green-100 text-green-700'
: 'bg-blue-100 text-blue-700'
}`}
>
{message.message}
</div>
)}
<button
disabled={isLoading}
type="submit"
className="mt-5 inline-flex h-10 w-full items-center justify-center rounded-lg border border-slate-300 bg-black p-2 px-4 text-sm font-medium text-white outline-none transition duration-150 ease-in-out focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
>
{isLoading ? <Spinner className="text-white" /> : 'Reset my password'}
</button>
</form>
);
}

@ -1,22 +1,23 @@
--- ---
import ResetPasswordForm from '../components/Profile/ResetPasswordForm'; import ResetPasswordForm from '../components/AuthenticationFlow/ResetPasswordForm';
import SettingLayout from '../layouts/SettingLayout.astro'; import SettingLayout from '../layouts/SettingLayout.astro';
--- ---
<SettingLayout title='Reset Password'> <SettingLayout title='Reset Password'>
<div class='container py-16'> <div class='container'>
<div
class='mx-auto flex flex-col items-start justify-start pb-28 pt-10 sm:max-w-[400px] sm:items-center sm:justify-center sm:pt-20'
>
<div class='mb-2 text-left sm:mb-5 sm:text-center'>
<h1 class='mb-2 text-3xl font-semibold sm:mb-5 sm:text-5xl'>
Reset Password
</h1>
<p class='mb-3 text-base leading-6 text-gray-600'>
Enter and confirm your new password below.
</p>
</div>
<ResetPasswordForm client:load /> <ResetPasswordForm client:load />
</div> </div>
</div>
</SettingLayout> </SettingLayout>
<script>
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../lib/constants';
const token = Cookies.get(TOKEN_COOKIE_NAME);
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (!code || token) {
window.location.href = '/settings/profile';
}
</script>

Loading…
Cancel
Save