163 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import Head from "next/head";
 | |
| import Link from "next/link";
 | |
| import React from "react";
 | |
| import { getCsrfToken } from "next-auth/client";
 | |
| import debounce from "lodash.debounce";
 | |
| 
 | |
| export default function Page({ csrfToken }) {
 | |
|   const [loading, setLoading] = React.useState(false);
 | |
|   const [error, setError] = React.useState(null);
 | |
|   const [success, setSuccess] = React.useState(false);
 | |
|   const [email, setEmail] = React.useState("");
 | |
| 
 | |
|   const handleChange = (e) => {
 | |
|     setEmail(e.target.value);
 | |
|   };
 | |
| 
 | |
|   const submitForgotPasswordRequest = async ({ email }) => {
 | |
|     try {
 | |
|       const res = await fetch("/api/auth/forgot-password", {
 | |
|         method: "POST",
 | |
|         body: JSON.stringify({ email: email }),
 | |
|         headers: {
 | |
|           "Content-Type": "application/json",
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       const json = await res.json();
 | |
|       if (!res.ok) {
 | |
|         setError(json);
 | |
|       } else {
 | |
|         setSuccess(true);
 | |
|       }
 | |
| 
 | |
|       return json;
 | |
|     } catch (reason) {
 | |
|       setError({ message: "An unexpected error occurred. Try again." });
 | |
|     } finally {
 | |
|       setLoading(false);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const debouncedHandleSubmitPasswordRequest = debounce(submitForgotPasswordRequest, 250);
 | |
| 
 | |
|   const handleSubmit = async (e) => {
 | |
|     e.preventDefault();
 | |
| 
 | |
|     if (!email) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (loading) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     setLoading(true);
 | |
|     setError(null);
 | |
|     setSuccess(false);
 | |
| 
 | |
|     await debouncedHandleSubmitPasswordRequest({ email });
 | |
|   };
 | |
| 
 | |
|   const Success = () => {
 | |
|     return (
 | |
|       <div className="space-y-6">
 | |
|         <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Done</h2>
 | |
|         <p>Check your email. We sent you a link to reset your password.</p>
 | |
|         {error && <p className="text-red-600">{error.message}</p>}
 | |
|       </div>
 | |
|     );
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
 | |
|       <Head>
 | |
|         <title>Forgot Password</title>
 | |
|         <link rel="icon" href="/favicon.ico" />
 | |
|       </Head>
 | |
| 
 | |
|       <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
 | |
|         <div className="bg-white py-8 px-4 mx-2 shadow rounded-lg sm:px-10 space-y-6">
 | |
|           {success && <Success />}
 | |
|           {!success && (
 | |
|             <>
 | |
|               <div className="space-y-6">
 | |
|                 <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Forgot Password</h2>
 | |
|                 <p>
 | |
|                   Enter the email address associated with your account and we will send you a link to reset
 | |
|                   your password.
 | |
|                 </p>
 | |
|                 {error && <p className="text-red-600">{error.message}</p>}
 | |
|               </div>
 | |
|               <form className="space-y-6" onSubmit={handleSubmit} action="#">
 | |
|                 <input name="csrfToken" type="hidden" defaultValue={csrfToken} hidden />
 | |
|                 <div>
 | |
|                   <label htmlFor="email" className="block text-sm font-medium text-gray-700">
 | |
|                     Email address
 | |
|                   </label>
 | |
|                   <div className="mt-1">
 | |
|                     <input
 | |
|                       onChange={handleChange}
 | |
|                       id="email"
 | |
|                       name="email"
 | |
|                       type="email"
 | |
|                       autoComplete="email"
 | |
|                       placeholder="john.doe@example.com"
 | |
|                       required
 | |
|                       className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-black focus:border-black sm:text-sm"
 | |
|                     />
 | |
|                   </div>
 | |
|                 </div>
 | |
| 
 | |
|                 <div>
 | |
|                   <button
 | |
|                     type="submit"
 | |
|                     disabled={loading}
 | |
|                     className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-black hover:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ${
 | |
|                       loading ? "cursor-not-allowed" : ""
 | |
|                     }`}>
 | |
|                     {loading && (
 | |
|                       <svg
 | |
|                         className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
 | |
|                         xmlns="http://www.w3.org/2000/svg"
 | |
|                         fill="none"
 | |
|                         viewBox="0 0 24 24">
 | |
|                         <circle
 | |
|                           className="opacity-25"
 | |
|                           cx="12"
 | |
|                           cy="12"
 | |
|                           r="10"
 | |
|                           stroke="currentColor"
 | |
|                           strokeWidth="4"></circle>
 | |
|                         <path
 | |
|                           className="opacity-75"
 | |
|                           fill="currentColor"
 | |
|                           d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
 | |
|                       </svg>
 | |
|                     )}
 | |
|                     Request Password Reset
 | |
|                   </button>
 | |
|                 </div>
 | |
|                 <div className="space-y-2">
 | |
|                   <Link href="/auth/login">
 | |
|                     <button
 | |
|                       type="button"
 | |
|                       className="w-full flex justify-center py-2 px-4 text-sm font-medium text-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
 | |
|                       Login
 | |
|                     </button>
 | |
|                   </Link>
 | |
|                 </div>
 | |
|               </form>
 | |
|             </>
 | |
|           )}
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   );
 | |
| }
 | |
| 
 | |
| Page.getInitialProps = async ({ req }) => {
 | |
|   return {
 | |
|     csrfToken: await getCsrfToken({ req }),
 | |
|   };
 | |
| };
 | 
