@@ -18,6 +18,8 @@ export default function Login() {
1818 const [ signingUp , setSigningUp ] = useState ( false )
1919 const [ signupSuccess , setSignupSuccess ] = useState ( false )
2020 const [ showEmailForm , setShowEmailForm ] = useState ( false )
21+ const [ resettingPassword , setResettingPassword ] = useState ( false )
22+ const [ passwordResetSuccess , setPasswordResetSuccess ] = useState ( false )
2123 const router = useRouter ( )
2224
2325 useEffect ( ( ) => {
@@ -211,11 +213,47 @@ export default function Login() {
211213 const handleBackClick = ( ) => {
212214 setError ( null )
213215 setShowEmailForm ( false )
216+ setPasswordResetSuccess ( false )
214217 }
215218
216219 const handleContinueEmailClick = ( ) => {
217220 setError ( null )
218221 setShowEmailForm ( true )
222+ setPasswordResetSuccess ( false )
223+ }
224+
225+ const handleForgotPassword = async ( e : React . MouseEvent < HTMLButtonElement > ) => {
226+ e . preventDefault ( )
227+ setError ( null )
228+
229+ if ( ! email . trim ( ) ) {
230+ setError ( "Please enter your email" )
231+ return
232+ }
233+
234+ try {
235+ setResettingPassword ( true )
236+
237+ // Preserve `redirect` so after password reset + sign in we land back where the user started.
238+ const searchParams = new URLSearchParams ( window . location . search )
239+ const redirectParam = searchParams . get ( "redirect" )
240+
241+ const redirectTo = redirectParam
242+ ? `${ window . location . origin } /reset-password?redirect=${ encodeURIComponent ( redirectParam ) } `
243+ : `${ window . location . origin } /reset-password`
244+
245+ const { error : resetError } = await supabaseClient . auth . resetPasswordForEmail ( email , {
246+ redirectTo
247+ } )
248+
249+ if ( resetError ) throw resetError
250+
251+ setPasswordResetSuccess ( true )
252+ } catch ( err : any ) {
253+ setError ( err ?. message || "Failed to send password reset email" )
254+ } finally {
255+ setResettingPassword ( false )
256+ }
219257 }
220258
221259 return (
@@ -240,67 +278,92 @@ export default function Login() {
240278 { error && < ErrorMessage > { error } </ ErrorMessage > }
241279
242280 { showEmailForm ? (
243- < EmailForm
244- onSubmit = { ( e ) => {
245- e . preventDefault ( )
246- handleEmailSignIn ( e )
247- } }
248- >
249- < InputGroup >
250- < TextInput
251- type = "email"
252- variant = "secondary"
253- size = "default"
254- value = { email }
255- onChange = { ( e ) => setEmail ( e . target . value ) }
256- placeholder = "Email"
257- required
258- disabled = { signingIn || signingUp }
259- />
260- </ InputGroup >
261- < InputGroup >
262- < TextInput
263- type = "password"
264- variant = "secondary"
265- size = "default"
266- value = { password }
267- onChange = { ( e ) => setPassword ( e . target . value ) }
268- placeholder = "Password"
269- required
270- minLength = { 6 }
271- disabled = { signingIn || signingUp }
281+ passwordResetSuccess ? (
282+ < SuccessContainer >
283+ < SuccessMessage
284+ title = "Check your email"
285+ message = "We've sent you a password reset link. Follow the instructions to choose a new password."
272286 />
273- < PasswordHelpText > Password must be at least 6 characters</ PasswordHelpText >
274- </ InputGroup >
275- < ButtonWrapper >
276- < Button
277- type = "submit"
278- size = "default"
279- variant = "primary"
280- disabled = { signingIn || signingUp || ! email || ! password }
281- >
282- { signingIn ? "Signing in..." : "Sign In" }
283- </ Button >
284- < Button
285- type = "button"
286- size = "default"
287- variant = "secondary"
288- disabled = { signingIn || signingUp || ! email || ! password }
289- onClick = { ( e ) => {
290- if ( e ) {
291- e . preventDefault ( )
292- e . stopPropagation ( )
287+ < BackButton type = "button" onClick = { handleBackClick } >
288+ ← Back to sign in options
289+ </ BackButton >
290+ </ SuccessContainer >
291+ ) : (
292+ < EmailForm
293+ onSubmit = { ( e ) => {
294+ e . preventDefault ( )
295+ handleEmailSignIn ( e )
296+ } }
297+ >
298+ < InputGroup >
299+ < TextInput
300+ type = "email"
301+ variant = "secondary"
302+ size = "default"
303+ value = { email }
304+ onChange = { ( e ) => setEmail ( e . target . value ) }
305+ placeholder = "Email"
306+ required
307+ disabled = { signingIn || signingUp || resettingPassword }
308+ />
309+ </ InputGroup >
310+ < InputGroup >
311+ < TextInput
312+ type = "password"
313+ variant = "secondary"
314+ size = "default"
315+ value = { password }
316+ onChange = { ( e ) => setPassword ( e . target . value ) }
317+ placeholder = "Password"
318+ required
319+ minLength = { 6 }
320+ disabled = { signingIn || signingUp || resettingPassword }
321+ />
322+ < PasswordHelpText > Password must be at least 6 characters</ PasswordHelpText >
323+ </ InputGroup >
324+ < ButtonWrapper >
325+ < Button
326+ type = "submit"
327+ size = "default"
328+ variant = "primary"
329+ disabled = {
330+ signingIn || signingUp || resettingPassword || ! email || ! password
331+ }
332+ >
333+ { signingIn ? "Signing in..." : "Sign In" }
334+ </ Button >
335+ < Button
336+ type = "button"
337+ size = "default"
338+ variant = "secondary"
339+ disabled = {
340+ signingIn || signingUp || resettingPassword || ! email || ! password
293341 }
294- handleEmailSignUp ( e ! )
295- } }
342+ onClick = { ( e ) => {
343+ if ( e ) {
344+ e . preventDefault ( )
345+ e . stopPropagation ( )
346+ }
347+ handleEmailSignUp ( e ! )
348+ } }
349+ >
350+ { signingUp ? "Signing up..." : "Sign Up" }
351+ </ Button >
352+ </ ButtonWrapper >
353+
354+ < ForgotPasswordButton
355+ type = "button"
356+ onClick = { handleForgotPassword }
357+ disabled = { resettingPassword || signingIn || signingUp || ! email }
296358 >
297- { signingUp ? "Signing up..." : "Sign Up" }
298- </ Button >
299- </ ButtonWrapper >
300- < BackButton type = "button" onClick = { handleBackClick } >
301- ← Back to sign in options
302- </ BackButton >
303- </ EmailForm >
359+ Forgot password?
360+ </ ForgotPasswordButton >
361+
362+ < BackButton type = "button" onClick = { handleBackClick } >
363+ ← Back to sign in options
364+ </ BackButton >
365+ </ EmailForm >
366+ )
304367 ) : (
305368 < ButtonContainer >
306369 < Button onClick = { handleLogin ( "google" ) } size = "default" variant = "primary" >
@@ -431,3 +494,25 @@ const BackButton = styled.button`
431494 color: rgba(255, 255, 255, 0.9);
432495 }
433496`
497+
498+ const ForgotPasswordButton = styled . button `
499+ background: none;
500+ border: none;
501+ color: rgba(255, 255, 255, 0.6);
502+ cursor: pointer;
503+ padding: 0;
504+ margin-top: -0.5rem;
505+ font-size: 0.875rem;
506+ transition: color 0.2s ease;
507+ font-family: inherit;
508+ text-align: right;
509+
510+ &:hover:not(:disabled) {
511+ color: rgba(255, 255, 255, 0.9);
512+ }
513+
514+ &:disabled {
515+ opacity: 0.5;
516+ cursor: not-allowed;
517+ }
518+ `
0 commit comments