Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forgot password reset #420

Merged
merged 2 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/rebutify/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
DJOSER = {
"ACTIVATION_URL": "activate?uid={uid}&token={token}",
"ACTIVATION_NEW_EMAIL_URL": "activate_new_email/{uid}/{token}",
"PASSWORD_RESET_CONFIRM_URL": "confirmresetpassword?uid={uid}&token={token}",
"SEND_ACTIVATION_EMAIL": True,
"SEND_CONFIRMATION_EMAIL": True,
"EMAIL": {
Expand Down
12 changes: 12 additions & 0 deletions frontend/app/(routes)/(auth)/confirmresetpassword/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client'

import { ConfirmResetPassword } from '@/components'
import { Suspense } from 'react'

export default function ConfirmResetPasswordPage() {
return (
<Suspense fallback={<div>Loading page...</div>}>
<ConfirmResetPassword />
</Suspense>
)
}
80 changes: 80 additions & 0 deletions frontend/app/(routes)/(auth)/forgot/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client'
import { FormEvent, useState } from 'react'
import { Form, Button } from '@/components'
import { TextInput } from '@/types'
import { formDataToObj, ServerErrorMessage } from '@/helpers'
import { SectionStyle } from '@/styles'
import { AxiosResponse } from 'axios'
import { requestPasswordReset } from '@/api/auth'

const forgotPasswordInputs: TextInput[] = [
{
id: 'email',
placeholder: 'Email',
type: 'email',
value: '',
},
]

const submitButtonLabel = 'Send Reset Link'

export default function Forgot() {
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState<string | null>(null)
const [apiErrors, setApiErrors] = useState<AxiosResponse | null>(null)

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
setLoading(true)
setApiErrors(null)

const formData = formDataToObj(event)

try {
// Replace with your actual API call
await requestPasswordReset({ email: formData.email })
setLoading(false)
setSuccess('Password reset instructions have been sent to your email')
} catch (error: any) {
setLoading(false)
console.log(`# forgot password error:`, error)
setApiErrors(
error.response ?? {
data: {
detail: ServerErrorMessage,
},
status: 500,
},
)
}
}

return (
<>
<h1 style={{ marginBottom: '12px' }}>Forgot Password</h1>
<SectionStyle>
<Form
id='forgot-password-form'
inputsFields={forgotPasswordInputs}
inputsErrors={apiErrors}
onSubmit={handleSubmit}
loading={loading}
success={success}
setSuccess={setSuccess}
>
<Button
size='max'
success={success}
loading={loading}
label={submitButtonLabel}
/>
</Form>
</SectionStyle>
<div style={{ marginTop: '8px' }}>
<a href='/login' style={{ color: 'grey' }}>
Back to login
</a>
</div>
</>
)
}
5 changes: 5 additions & 0 deletions frontend/app/(routes)/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ export default function Login() {
<Button size='max' loading={loading} label={submitButtonLabel} />
</Form>
</SectionStyle>
<div style={{ marginTop: '8px' }}>
<a href='/forgot' style={{ color: 'grey' }}>
Forgot password
</a>
</div>
</>
)
}
1 change: 1 addition & 0 deletions frontend/app/_api/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { fetchUserInfo } from '@/api/auth/fetchUserInfo'
export { refreshTheToken } from '@/api/auth/refreshTheToken'
export { deleteSelfAccount } from '@/api/auth/deleteSelfAccount'
export { editPassword } from '@/api/auth/editPassword'
export { requestPasswordReset } from '@/api/auth/requestPasswordReset'
20 changes: 20 additions & 0 deletions frontend/app/_api/auth/requestPasswordReset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import api from '@/api/api'

export const requestPasswordReset = async (formData: { email: string }) => {
try {
const response = await api.post(
'/auth/users/reset_password/',
{ email: formData.email },
{
requiresAuth: false,
},
)
return response.data
} catch (error: any) {
console.error(
`/auth/users/reset_password request failed: `,
error.response?.data ?? error,
)
throw error
}
}
88 changes: 88 additions & 0 deletions frontend/app/_components/confirmresetpassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client'
import { FormEvent, useState } from 'react'
import { Form, Button } from '@/components'
import { TextInput } from '@/types'
import { formDataToObj } from '@/helpers'
import { useSearchParams } from 'next/navigation'
import { useRouter } from 'next/navigation'
import { SectionStyle } from '@/styles'
import api from '@/api/api'

const resetPasswordInputs: TextInput[] = [
{
id: 'new_password',
placeholder: 'New Password',
type: 'password',
value: '',
},
{
id: 're_new_password',
placeholder: 'Confirm New Password',
type: 'password',
value: '',
},
]

export default function ConfirmResetPassword() {
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState<string | null>(
'Password successfully reset',
)
const [error, setError] = useState<string | null>(null)
const router = useRouter()

const searchParams = useSearchParams()
const token = searchParams.get('token')
const uid = searchParams.get('uid')

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
setLoading(true)
setError(null)

const formData = formDataToObj(event)

try {
const { status } = await api.post('/auth/users/reset_password_confirm/', {
uid: uid,
token: token,
new_password: formData.new_password,
re_new_password: formData.re_new_password,
})

if (status >= 200 && status < 300) {
// Success - redirect to login
router.push('/login?reset=success')
}
} catch (error: any) {
setLoading(false)
setError(
error.response?.data?.detail ||
'Password reset failed. The link may have expired. Please try requesting a new reset link.',
)
}
}

return (
<>
<h1 style={{ marginBottom: '12px' }}>Reset Password</h1>
<SectionStyle>
{error ? (
<div style={{ color: 'red' }}>{error}</div>
) : (
<Form
id='reset-password-form'
inputsFields={resetPasswordInputs}
onSubmit={handleSubmit}
loading={loading}
inputsErrors={null}
success={success}
setSuccess={() => setSuccess}
>
<Button size='max' loading={loading} label='Reset Password' />
</Form>
)}
</SectionStyle>
</>
)
}
1 change: 1 addition & 0 deletions frontend/app/_components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as Icon } from '@/components/icon'
export { default as Comments } from '@/components/comments'
export { default as LoginBlocker } from '@/components/loginBlocker'
export { default as Follow } from '@/components/follow'
export { default as ConfirmResetPassword } from '@/components/confirmresetpassword'
Loading