Skip to content

Commit 4d2766b

Browse files
committed
Create an AppContext for holding app-wide data, such as info on the current user.
1 parent 0b5114c commit 4d2766b

File tree

7 files changed

+263
-274
lines changed

7 files changed

+263
-274
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@ Don't expect the code to do anything particularly useful!
1515

1616
## TODO
1717

18-
* Move to using a separate context for user state
1918
* Move authentication code out of App.jsx.

frontend/src/components/App.jsx

Lines changed: 99 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {Component} from "react";
1+
import React, {useEffect, useState} from "react";
22
import {render} from 'react-dom';
33
import ButtonAppBar from './AppBar';
44
import LeadList from './LeadList';
@@ -8,6 +8,8 @@ import Alert from '@mui/material/Alert';
88
import Box from '@mui/material/Box';
99
import Grid from '@mui/material/Grid';
1010
import Cookies from "universal-cookie";
11+
import {stringAvatar} from "../utils";
12+
import {AppContext} from "./AppContext";
1113

1214
const cookies = new Cookies();
1315

@@ -23,198 +25,142 @@ const lightTheme = createTheme({
2325
},
2426
});
2527

26-
class App extends React.Component {
27-
constructor(props) {
28-
super(props);
2928

30-
this.login = this.login.bind(this);
31-
this.clearError = this.clearError.bind(this);
32-
33-
let defaultDark = false;
34-
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
35-
defaultDark = true;
36-
}
37-
38-
let darkMode = JSON.parse(localStorage.getItem('darkMode')) ?? defaultDark;
39-
40-
this.state = {
41-
username: "",
42-
fullname: "Anonymous User",
43-
avatarProps: this.stringAvatar('Anonymous User'),
44-
email: "",
45-
password: "",
46-
error: "",
47-
isAuthenticated: false,
48-
darkMode: darkMode,
49-
};
50-
}
51-
52-
stringToColor(string) {
53-
let hash = 0;
54-
let i;
55-
56-
/* eslint-disable no-bitwise */
57-
for (i = 0; i < string.length; i += 1) {
58-
hash = string.charCodeAt(i) + ((hash << 5) - hash);
59-
}
60-
61-
let color = '#';
62-
63-
for (i = 0; i < 3; i += 1) {
64-
const value = (hash >> (i * 8)) & 0xff;
65-
color += `00${value.toString(16)}`.slice(-2);
66-
}
67-
/* eslint-enable no-bitwise */
68-
69-
return color;
70-
}
29+
// Figure out the default dark mode.
30+
let osDark = false;
31+
let defaultDark = false;
32+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
33+
osDark = true;
34+
}
35+
defaultDark = JSON.parse(localStorage.getItem('darkMode')) ?? osDark;
7136

72-
stringAvatar(name) {
73-
let children = '';
74-
if (!name.includes(' ')) {
75-
children = name[0].toUpperCase();
76-
} else {
77-
children = name.split(' ')[0][0].toUpperCase() + name.split(' ')[1][0].toUpperCase();
78-
}
7937

80-
return {
81-
sx: {
82-
bgcolor: this.stringToColor(name),
83-
},
84-
children: children,
85-
};
86-
}
38+
export function App() {
39+
const anonUser = {
40+
username: '',
41+
fullname: 'Anonymous User',
42+
avatarProps: stringAvatar('Anonymous User'),
43+
email: '',
44+
password: '',
45+
isAuthenticated: false
46+
};
8747

88-
componentDidMount = () => {
89-
this.getSession();
90-
}
48+
const [user, setUser] = useState(anonUser);
49+
const [darkMode, setDarkMode] = useState(defaultDark);
50+
const [error, setError] = useState("");
9151

92-
getSession = () => {
52+
// Retrieve any session details, or clear them if necessary
53+
const getSession = () => {
9354
fetch("/api/session/", {
9455
credentials: "same-origin",
9556
})
96-
.then((res) => res.json())
97-
.then((data) => {
98-
console.log(data);
99-
if (data.isAuthenticated) {
100-
this.setState({
101-
isAuthenticated: true,
102-
username: data.username,
103-
fullname: data.fullname,
104-
avatarProps: this.stringAvatar(data.fullname),
105-
email: data.email,
106-
error: ''
107-
});
108-
} else {
109-
this.setState({
110-
isAuthenticated: false,
111-
username: '',
112-
fullname: 'Anonymous User',
113-
avatarProps: this.stringAvatar('Anonymous User'),
114-
email: '',
115-
error: ''
116-
});
117-
}
118-
})
119-
.catch((err) => {
120-
console.log(err);
121-
});
57+
.then((res) => res.json())
58+
.then((data) => {
59+
console.log(data);
60+
if (data.isAuthenticated) {
61+
setUser({
62+
username: data.username,
63+
fullname: data.fullname,
64+
avatarProps: stringAvatar(data.fullname),
65+
email: data.email,
66+
password: '',
67+
isAuthenticated: true
68+
});
69+
setError('');
70+
} else {
71+
setUser(anonUser);
72+
setError('');
73+
}
74+
})
75+
.catch((err) => {
76+
console.log(err);
77+
});
12278
}
12379

124-
isResponseOk(response) {
80+
// Did we get a good response from our request?
81+
const isResponseOk = (response) => {
12582
if (response.status >= 200 && response.status <= 299) {
12683
return response.json();
12784
} else {
12885
throw Error(response.statusText);
12986
}
13087
}
13188

132-
clearError() {
133-
this.setState({error: ''});
89+
// Clear any previous errors
90+
const clearError = () => {
91+
setError('');
13492
}
13593

136-
login(username, password, cbSuccess) {
94+
// Attempt to login
95+
const login = (username, password, cbSuccess) => {
13796
fetch("/api/login/", {
138-
method: "POST",
139-
headers: {
140-
"Content-Type": "application/json",
141-
"X-CSRFToken": cookies.get("csrftoken"),
142-
},
143-
credentials: "same-origin",
144-
body: JSON.stringify({username: username, password: password}),
145-
})
146-
.then(this.isResponseOk)
147-
.then((data) => {
148-
this.getSession();
149-
cbSuccess();
97+
method: "POST", headers: {
98+
"Content-Type": "application/json", "X-CSRFToken": cookies.get("csrftoken"),
99+
}, credentials: "same-origin", body: JSON.stringify({username: username, password: password}),
150100
})
151-
.catch((err) => {
152-
console.log(err);
153-
this.setState({error: "Incorrect username or password."});
154-
});
101+
.then(isResponseOk)
102+
.then(() => {
103+
getSession();
104+
cbSuccess();
105+
})
106+
.catch((err) => {
107+
console.log(err);
108+
setError('Incorrect username or password.');
109+
});
155110
}
156111

157-
logout = () => {
112+
// Clear the session out
113+
const logout = () => {
158114
fetch("/api/logout", {
159115
credentials: "same-origin",
160116
})
161-
.then(this.isResponseOk)
162-
.then((data) => {
163-
console.log(data);
164-
this.setState({
165-
isAuthenticated: false,
166-
username: '',
167-
fullname: 'Anonymous User',
168-
avatarProps: this.stringAvatar('Anonymous User'),
169-
email: '',
170-
error: ''
117+
.then(isResponseOk)
118+
.then((data) => {
119+
console.log(data);
120+
setUser(anonUser);
121+
setError('');
122+
})
123+
.catch((err) => {
124+
console.log(err);
171125
});
172-
})
173-
.catch((err) => {
174-
console.log(err);
175-
});
176126
};
177127

178-
179-
changeTheme = () => {
180-
const darkMode = this.state.darkMode;
181-
this.setState({darkMode: !darkMode});
128+
// Toggle the theme
129+
const changeTheme = () => {
182130
localStorage.setItem('darkMode', JSON.stringify(!darkMode));
131+
setDarkMode(!darkMode);
183132
}
184133

185-
render() {
186-
return (
187-
<ThemeProvider theme={this.state.darkMode ? darkTheme : lightTheme}>
134+
// See if we can get session data
135+
useEffect(() => {
136+
getSession()
137+
}, []);
138+
139+
return (<AppContext.Provider value={{user, darkMode, error}}>
140+
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
188141
<CssBaseline/>
189-
<ButtonAppBar appState={this.state} handleLogin={this.login} handleLogout={this.logout} clearError={this.clearError} onChangeTheme={this.changeTheme} />
142+
<ButtonAppBar appState={user} handleLogin={login} handleLogout={logout} clearError={clearError}
143+
onChangeTheme={changeTheme}/>
190144
<Box align="center" sx={{flexGrow: 1}} style={{marginLeft: 20, marginRight: 20}}>
191145
<Grid container align="left" maxWidth="xl" spacing={1} wrap="wrap">
192-
{!this.state.isAuthenticated ?
146+
{!user.isAuthenticated ? <Grid item xs={12}>
147+
<h1>Login</h1>
148+
<Alert severity="error">Please login to see the lead list.</Alert>
149+
</Grid> : <>
193150
<Grid item xs={12}>
194-
<h1>Login</h1>
195-
<Alert severity="error">Please login to see the lead list.</Alert>
151+
<h1>Lead List</h1>
196152
</Grid>
197-
:
198-
<>
199-
<Grid item xs={12}>
200-
<h1>Lead List</h1>
201-
</Grid>
202-
<Grid item xs={12}>
203-
<LeadList/>
204-
</Grid>
205-
</>
206-
}
153+
<Grid item xs={12}>
154+
<LeadList/>
155+
</Grid>
156+
</>}
207157
</Grid>
208158
</Box>
209159
</ThemeProvider>
210-
)
211-
};
160+
</AppContext.Provider>)
212161
}
213162

214163
const container = document.getElementById("app");
215-
render(
216-
<React.StrictMode>
217-
<App/>
218-
</React.StrictMode>,
219-
container
220-
);
164+
render(<React.StrictMode>
165+
<App/>
166+
</React.StrictMode>, container);

0 commit comments

Comments
 (0)