Skip to content

Commit a5a70f1

Browse files
Copilotdkhalife
andauthored
Centralize error handling for authentication views using Redux status system (#195)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: dkhalife <[email protected]> Co-authored-by: Dany Khalife <[email protected]>
1 parent 8938f1c commit a5a70f1

File tree

4 files changed

+99
-92
lines changed

4 files changed

+99
-92
lines changed

frontend/src/views/Authorization/ForgotPasswordView.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,24 @@ import {
1212
Input,
1313
FormHelperText,
1414
Button,
15-
Snackbar,
1615
} from '@mui/joy'
1716
import React, { ChangeEvent } from 'react'
17+
import { connect } from 'react-redux'
18+
import { AppDispatch } from '@/store/store'
19+
import { pushStatus } from '@/store/statusSlice'
20+
import { StatusSeverity } from '@/models/status'
1821

19-
type ForgotPasswordViewProps = WithNavigate
22+
type ForgotPasswordViewProps = WithNavigate & {
23+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) => void
24+
}
2025

2126
interface ForgotPasswordViewState {
2227
email: string
2328
emailError: string | null
2429
resetStatusOk: boolean | null
2530
}
2631

27-
export class ForgotPasswordView extends React.Component<
32+
class ForgotPasswordViewImpl extends React.Component<
2833
ForgotPasswordViewProps,
2934
ForgotPasswordViewState
3035
> {
@@ -56,8 +61,10 @@ export class ForgotPasswordView extends React.Component<
5661
try {
5762
await ResetPassword(this.state.email)
5863
this.setState({ resetStatusOk: true })
64+
this.props.pushStatus('Reset email sent, check your email', 'success', 5000)
5965
} catch {
6066
this.setState({ resetStatusOk: false })
67+
this.props.pushStatus('Reset email failed, try again later', 'error', 5000)
6168
}
6269
}
6370

@@ -74,13 +81,6 @@ export class ForgotPasswordView extends React.Component<
7481
this.handleSubmit()
7582
}
7683

77-
private onSnackbarClose = () => {
78-
const { resetStatusOk } = this.state
79-
if (resetStatusOk) {
80-
this.props.navigate(NavigationPaths.Login)
81-
}
82-
}
83-
8484
render(): React.ReactNode {
8585
const { email, emailError, resetStatusOk } = this.state
8686
const { navigate } = this.props
@@ -177,9 +177,9 @@ export class ForgotPasswordView extends React.Component<
177177
<>
178178
<Box mt={-30}>
179179
<Typography>
180-
if there is an account associated with the email you
180+
If there is an account associated with the email you
181181
entered, you will receive an email with instructions on how
182-
to reset your
182+
to reset your password.
183183
</Typography>
184184
</Box>
185185
<Button
@@ -193,18 +193,16 @@ export class ForgotPasswordView extends React.Component<
193193
</Button>
194194
</>
195195
)}
196-
<Snackbar
197-
open={resetStatusOk ? resetStatusOk : resetStatusOk === false}
198-
autoHideDuration={5000}
199-
onClose={this.onSnackbarClose}
200-
>
201-
{resetStatusOk
202-
? 'Reset email sent, check your email'
203-
: 'Reset email failed, try again later'}
204-
</Snackbar>
205196
</Sheet>
206197
</Box>
207198
</Container>
208199
)
209200
}
210201
}
202+
203+
const mapDispatchToProps = (dispatch: AppDispatch) => ({
204+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) =>
205+
dispatch(pushStatus({ message, severity, timeout })),
206+
})
207+
208+
export const ForgotPasswordView = connect(null, mapDispatchToProps)(ForgotPasswordViewImpl)

frontend/src/views/Authorization/LoginView.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,32 @@ import {
77
Input,
88
Button,
99
Divider,
10-
Snackbar,
1110
} from '@mui/joy'
1211
import React, { ChangeEvent } from 'react'
1312
import { doLogin } from '@/utils/auth'
1413
import { setTitle } from '@/utils/dom'
1514
import { NavigationPaths, WithNavigate } from '@/utils/navigation'
15+
import { connect } from 'react-redux'
16+
import { AppDispatch } from '@/store/store'
17+
import { pushStatus } from '@/store/statusSlice'
18+
import { StatusSeverity } from '@/models/status'
1619

17-
type LoginViewProps = WithNavigate
20+
type LoginViewProps = WithNavigate & {
21+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) => void
22+
}
1823

1924
interface LoginViewState {
2025
email: string
2126
password: string
22-
error: string | null
2327
}
2428

25-
export class LoginView extends React.Component<LoginViewProps, LoginViewState> {
29+
class LoginViewImpl extends React.Component<LoginViewProps, LoginViewState> {
2630
constructor(props: LoginViewProps) {
2731
super(props)
2832

2933
this.state = {
3034
email: '',
3135
password: '',
32-
error: null,
3336
}
3437
}
3538

@@ -44,7 +47,7 @@ export class LoginView extends React.Component<LoginViewProps, LoginViewState> {
4447
const { email, password } = this.state
4548
await doLogin(email, password, this.props.navigate)
4649
} catch (error) {
47-
this.setState({ error: (error as Error).message })
50+
this.props.pushStatus((error as Error).message, 'error', 5000)
4851
}
4952
}
5053

@@ -56,12 +59,7 @@ export class LoginView extends React.Component<LoginViewProps, LoginViewState> {
5659
this.setState({ password: e.target.value })
5760
}
5861

59-
private onSnackbarClose = () => {
60-
this.setState({ error: null })
61-
}
62-
6362
render(): React.ReactNode {
64-
const { error } = this.state
6563
const { navigate } = this.props
6664

6765
return (
@@ -159,14 +157,14 @@ export class LoginView extends React.Component<LoginViewProps, LoginViewState> {
159157
</Button>
160158
</Sheet>
161159
</Box>
162-
<Snackbar
163-
open={error !== null}
164-
onClose={this.onSnackbarClose}
165-
autoHideDuration={3000}
166-
>
167-
{error}
168-
</Snackbar>
169160
</Container>
170161
)
171162
}
172163
}
164+
165+
const mapDispatchToProps = (dispatch: AppDispatch) => ({
166+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) =>
167+
dispatch(pushStatus({ message, severity, timeout })),
168+
})
169+
170+
export const LoginView = connect(null, mapDispatchToProps)(LoginViewImpl)

frontend/src/views/Authorization/Signup.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@ import {
1313
Button,
1414
Sheet,
1515
Divider,
16-
Snackbar,
1716
} from '@mui/joy'
1817
import React, { ChangeEvent } from 'react'
18+
import { connect } from 'react-redux'
19+
import { AppDispatch } from '@/store/store'
20+
import { pushStatus } from '@/store/statusSlice'
21+
import { StatusSeverity } from '@/models/status'
1922

20-
type SignupViewProps = WithNavigate
23+
type SignupViewProps = WithNavigate & {
24+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) => void
25+
}
2126

2227
interface SignupViewState {
2328
password: string
@@ -26,14 +31,15 @@ interface SignupViewState {
2631
passwordError: string | null
2732
emailError: string | null
2833
displayNameError: string | null
29-
error: string | null
3034
accountCreated: boolean
3135
}
3236

33-
export class SignupView extends React.Component<
37+
class SignupViewImpl extends React.Component<
3438
SignupViewProps,
3539
SignupViewState
3640
> {
41+
private navigationTimeout?: NodeJS.Timeout
42+
3743
constructor(props: SignupViewProps) {
3844
super(props)
3945

@@ -44,7 +50,6 @@ export class SignupView extends React.Component<
4450
passwordError: null,
4551
emailError: null,
4652
displayNameError: null,
47-
error: null,
4853
accountCreated: false,
4954
}
5055
}
@@ -53,6 +58,12 @@ export class SignupView extends React.Component<
5358
setTitle('Sign Up')
5459
}
5560

61+
componentWillUnmount(): void {
62+
if (this.navigationTimeout) {
63+
clearTimeout(this.navigationTimeout)
64+
}
65+
}
66+
5667
private handleSignUpValidation = () => {
5768
// Reset errors before validation
5869
const newState: SignupViewState = {
@@ -105,10 +116,10 @@ export class SignupView extends React.Component<
105116
this.setState({
106117
accountCreated: true,
107118
})
119+
this.props.pushStatus('Please check your email to verify your account.', 'success', 3000)
120+
this.navigationTimeout = setTimeout(() => this.props.navigate(NavigationPaths.Login), 3000)
108121
} catch (error) {
109-
this.setState({
110-
error: (error as Error).message,
111-
})
122+
this.props.pushStatus((error as Error).message, 'error', 5000)
112123
}
113124
}
114125

@@ -135,14 +146,12 @@ export class SignupView extends React.Component<
135146

136147
render(): React.ReactNode {
137148
const {
138-
accountCreated,
139149
password,
140150
displayName,
141151
email,
142152
passwordError,
143153
displayNameError,
144154
emailError,
145-
error,
146155
} = this.state
147156
const { navigate } = this.props
148157

@@ -233,23 +242,14 @@ export class SignupView extends React.Component<
233242
</Button>
234243
</Sheet>
235244
</Box>
236-
<Snackbar
237-
open={error !== null}
238-
onClose={() => this.setState({ error: null })}
239-
autoHideDuration={5000}
240-
>
241-
{error}
242-
</Snackbar>
243-
<Snackbar
244-
color='success'
245-
variant='solid'
246-
open={accountCreated}
247-
onClose={() => navigate(NavigationPaths.Login)}
248-
autoHideDuration={3000}
249-
>
250-
Please check your email to verify your account.
251-
</Snackbar>
252245
</Container>
253246
)
254247
}
255248
}
249+
250+
const mapDispatchToProps = (dispatch: AppDispatch) => ({
251+
pushStatus: (message: string, severity: StatusSeverity, timeout?: number) =>
252+
dispatch(pushStatus({ message, severity, timeout })),
253+
})
254+
255+
export const SignupView = connect(null, mapDispatchToProps)(SignupViewImpl)

0 commit comments

Comments
 (0)