1
- import React from "react" ;
1
+ import React , { useEffect , useState } from "react" ;
2
2
import { useNavigate } from "react-router-dom" ;
3
- import Cookies from "js-cookie" ;
4
3
5
4
import Button from "@mui/material/Button" ;
6
5
import TextField from "@mui/material/TextField" ;
@@ -12,104 +11,188 @@ import { Box } from "@mui/material";
12
11
13
12
import RootPage from "../root" ;
14
13
import "./reset_password.css" ; // Import CSS file for additional styling
15
- import { backend_post , expire_time } from "../../utils" ;
16
- import { TokenResponse } from "../../types/common" ;
14
+ import { useAxiosRequest } from "../../utils" ;
15
+ import { Empty } from "../../types/common" ;
16
+
17
+ interface ChangePasswordRequestParams {
18
+ token : string ;
19
+ password : string ;
20
+ }
21
+ interface ValidateTokenRequestParams {
22
+ token : string ;
23
+ }
24
+ interface ValidateTokenRequestResponse {
25
+ detail ?: string ;
26
+ status ?: string ;
27
+ }
17
28
18
29
const ResetPassword = ( ) => {
19
30
const navigate = useNavigate ( ) ;
31
+ const [ newPassword , setNewPassword ] = useState ( "" ) ;
32
+ const [ confirmPassword , setConfirmPassword ] = useState ( "" ) ;
33
+ const [ passwordError , setPasswordError ] = useState ( "" ) ;
34
+ const [ loading , setLoading ] = useState ( true ) ;
35
+
36
+ // API Request
37
+ const ChangePassword = useAxiosRequest <
38
+ ChangePasswordRequestParams ,
39
+ Empty
40
+ > ( ) ;
41
+ const ValidateToken = useAxiosRequest <
42
+ ValidateTokenRequestParams ,
43
+ ValidateTokenRequestResponse
44
+ > ( ) ;
45
+ const validateRequest = ValidateToken . sendRequest ;
20
46
21
- const handleTokenResponse = ( data : TokenResponse ) => {
22
- const expire_date = new Date ( new Date ( ) . getTime ( ) + expire_time * 1000 ) ;
23
- Cookies . set ( "token_access" , data [ "access" ] , { expires : expire_date } ) ;
24
- Cookies . set ( "token_refresh" , data [ "refresh" ] , { expires : expire_date } ) ;
25
- Cookies . set ( "token_spawned" , ( Date . now ( ) / 1000 ) . toString ( ) , {
26
- expires : expire_date
47
+ // Get the token from query
48
+ const queryParams = new URLSearchParams ( location . search ) ;
49
+ let token = queryParams . get ( "token" ) ;
50
+ if ( token == null ) token = "" ;
51
+
52
+ useEffect ( ( ) => {
53
+ if ( token === "" ) {
54
+ navigate ( "/login" ) ;
55
+ navigate ( 0 ) ;
56
+ }
57
+ const tokenParam : ValidateTokenRequestParams = {
58
+ token : token
59
+ } ;
60
+ validateRequest ( {
61
+ method : "POST" ,
62
+ route : "password_reset/validate_token" ,
63
+ data : tokenParam ,
64
+ useJWT : false
27
65
} ) ;
28
- navigate ( 0 ) ;
29
- navigate ( "/home" , { replace : true } ) ;
30
- } ;
66
+ } , [ navigate , token , validateRequest ] ) ;
31
67
32
- const handleSubmit = ( event : React . FormEvent < HTMLFormElement > ) => {
68
+ useEffect ( ( ) => {
69
+ if ( ValidateToken . error != null ) {
70
+ navigate ( "/login" ) ;
71
+ navigate ( 0 ) ;
72
+ }
73
+ } , [ navigate , ValidateToken . error ] ) ;
74
+
75
+ useEffect ( ( ) => {
76
+ if ( ValidateToken . loading ) {
77
+ setLoading ( false ) ;
78
+ }
79
+ } , [ ValidateToken . loading ] ) ;
80
+
81
+ const handleSubmit = async ( event : React . FormEvent ) => {
33
82
event . preventDefault ( ) ;
34
- const data = new FormData ( event . currentTarget ) ;
35
- console . log ( {
36
- username : data . get ( "username" ) ,
37
- password : data . get ( "password" )
38
- } ) ;
39
83
40
- backend_post (
41
- "token/" ,
42
- JSON . stringify ( {
43
- username : data . get ( "username" ) ,
44
- password : data . get ( "password" )
84
+ // Reset error state
85
+ setPasswordError ( "" ) ;
86
+
87
+ // Basic client-side validations
88
+ if ( newPassword !== confirmPassword ) {
89
+ setPasswordError ( "Passwords do not match." ) ;
90
+ return ;
91
+ }
92
+
93
+ if ( newPassword . length < 8 ) {
94
+ setPasswordError ( "Password must be at least 8 characters long." ) ;
95
+ return ;
96
+ }
97
+
98
+ // Basic common password check (for demonstration; consider using a list)
99
+ const commonPasswords = [ "password" , "12345678" , "qwerty" ] ;
100
+ if ( commonPasswords . includes ( newPassword . toLowerCase ( ) ) ) {
101
+ setPasswordError ( "Password is too common." ) ;
102
+ return ;
103
+ }
104
+
105
+ // Check if the password is entirely numeric
106
+ if ( / ^ \d + $ / . test ( newPassword ) ) {
107
+ setPasswordError ( "Password cannot be entirely numeric." ) ;
108
+ return ;
109
+ }
110
+ const requestParams : ChangePasswordRequestParams = {
111
+ token : token ,
112
+ password : newPassword
113
+ } ;
114
+
115
+ ChangePassword . sendRequest ( {
116
+ method : "POST" ,
117
+ route : "/password_reset/confirm/" ,
118
+ data : requestParams ,
119
+ useJWT : false
120
+ } )
121
+ . then ( ( ) => {
122
+ navigate ( "/login" ) ;
123
+ navigate ( 0 ) ;
45
124
} )
46
- )
47
- . then ( ( resp ) => resp . json ( ) )
48
- . then ( ( data ) => handleTokenResponse ( data ) )
49
- . catch ( ( error ) => console . log ( error ) ) ;
125
+ . catch ( ( error ) => {
126
+ console . error ( "Password reset request failed:" , error ) ;
127
+ setPasswordError ( "Failed to reset password. Please try again." ) ;
128
+ } ) ;
50
129
} ;
51
130
52
131
return (
53
132
< RootPage >
54
133
< Container component = "main" maxWidth = "xs" >
55
- < Box
56
- sx = { {
57
- marginTop : 8 ,
58
- display : "flex" ,
59
- flexDirection : "column" ,
60
- alignItems : "center"
61
- } }
62
- >
63
- < Avatar sx = { { m : 1 , bgcolor : "secondary.main" } } >
64
- < LockOutlinedIcon />
65
- </ Avatar >
66
- < Typography component = "h1" variant = "h5" >
67
- Change Password
68
- </ Typography >
134
+ { loading || ValidateToken . loading ? (
135
+ < div > Loading...</ div > // Added loading state feedback
136
+ ) : (
69
137
< Box
70
- component = "form"
71
- onSubmit = { handleSubmit }
72
- noValidate
73
- sx = { { mt : 1 } }
74
- color = { "primary" }
138
+ sx = { {
139
+ marginTop : 8 ,
140
+ display : "flex" ,
141
+ flexDirection : "column" ,
142
+ alignItems : "center"
143
+ } }
75
144
>
76
- < TextField
77
- margin = "normal"
78
- required
79
- fullWidth
80
- name = "old_password"
81
- label = "Previous Password"
82
- type = "password"
83
- id = "old_password"
84
- />
85
- < TextField
86
- margin = "normal"
87
- required
88
- fullWidth
89
- name = "new_password"
90
- label = "New Password"
91
- type = "password"
92
- id = "new_password"
93
- />
94
- < TextField
95
- margin = "normal"
96
- required
97
- fullWidth
98
- name = "confirm_password"
99
- label = "Confirm Password"
100
- type = "password"
101
- id = "confirm_password"
102
- />
103
- < Button
104
- type = "submit"
105
- fullWidth
106
- variant = "contained"
107
- sx = { { mt : 3 , mb : 2 } }
108
- >
145
+ < Avatar sx = { { m : 1 , bgcolor : "secondary.main" } } >
146
+ < LockOutlinedIcon />
147
+ </ Avatar >
148
+ < Typography component = "h1" variant = "h5" >
109
149
Change Password
110
- </ Button >
150
+ </ Typography >
151
+ < Box
152
+ component = "form"
153
+ onSubmit = { handleSubmit }
154
+ noValidate
155
+ sx = { { mt : 1 } }
156
+ color = { "primary" }
157
+ >
158
+ < TextField
159
+ margin = "normal"
160
+ required
161
+ fullWidth
162
+ name = "new_password"
163
+ label = "New Password"
164
+ type = "password"
165
+ id = "new_password"
166
+ onChange = { ( e ) => setNewPassword ( e . target . value ) }
167
+ />
168
+ < TextField
169
+ margin = "normal"
170
+ required
171
+ fullWidth
172
+ name = "confirm_password"
173
+ label = "Confirm Password"
174
+ type = "password"
175
+ id = "confirm_password"
176
+ onChange = { ( e ) =>
177
+ setConfirmPassword ( e . target . value )
178
+ }
179
+ />
180
+ < Button
181
+ type = "submit"
182
+ fullWidth
183
+ variant = "contained"
184
+ sx = { { mt : 3 , mb : 2 } }
185
+ >
186
+ Change Password
187
+ </ Button >
188
+ { passwordError && (
189
+ < Typography color = "error" textAlign = "center" >
190
+ { passwordError }
191
+ </ Typography >
192
+ ) }
193
+ </ Box >
111
194
</ Box >
112
- </ Box >
195
+ ) }
113
196
</ Container >
114
197
</ RootPage >
115
198
) ;
0 commit comments