Skip to content

Commit

Permalink
Merge pull request #420 from Monstarrrr/feat/0_forgotpassword
Browse files Browse the repository at this point in the history
forgot password reset
  • Loading branch information
seporterfield authored Dec 24, 2024
2 parents d70626c + 325ec68 commit 2ce6a0c
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 0 deletions.
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'

0 comments on commit 2ce6a0c

Please sign in to comment.