Skip to content

Commit e2c6b51

Browse files
authored
Merge pull request #112 from AndrewHUNGNguyen/#52-Add-Forgot-Password-For-Sign-In
Changes to reset password
2 parents 5d75541 + a6f4584 commit e2c6b51

3 files changed

Lines changed: 366 additions & 59 deletions

File tree

app/login/page.tsx

Lines changed: 143 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)