Browse Source

+ user profile, fix snack render bug, add private routes

master
david 2 years ago
parent
commit
0c0141c5b7
  1. 58
      package-lock.json
  2. 1
      package.json
  3. 32
      src/App.js
  4. 44
      src/Components/Appbar.js
  5. 78
      src/Components/AuthForm.js
  6. 0
      src/Components/Home.js
  7. 7
      src/Components/PrivateRoute.js
  8. 53
      src/Components/UserProfile.js
  9. 10
      src/Contexts/AuthContext.js
  10. 5
      src/Contexts/SnackbarContext.js
  11. 19
      src/index.js

58
package-lock.json generated

@ -19,6 +19,7 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.3" "web-vitals": "^2.1.3"
} }
@ -8523,6 +8524,14 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/history": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"dependencies": {
"@babel/runtime": "^7.7.6"
}
},
"node_modules/hoist-non-react-statics": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -13726,6 +13735,30 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"dependencies": {
"history": "^5.2.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"dependencies": {
"history": "^5.2.0",
"react-router": "6.2.1"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
@ -22735,6 +22768,14 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}, },
"history": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
"requires": {
"@babel/runtime": "^7.7.6"
}
},
"hoist-non-react-statics": { "hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -26381,6 +26422,23 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
}, },
"react-router": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz",
"integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==",
"requires": {
"history": "^5.2.0"
}
},
"react-router-dom": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz",
"integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==",
"requires": {
"history": "^5.2.0",
"react-router": "6.2.1"
}
},
"react-scripts": { "react-scripts": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",

1
package.json

@ -14,6 +14,7 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.3" "web-vitals": "^2.1.3"
}, },

32
src/App.js

@ -1,22 +1,24 @@
import { Container, Button } from '@mui/material'; import { Container } from '@mui/material';
import { AuthForm } from './Components/AuthForm'; import { AuthForm } from './Components/AuthForm';
import { useAuth } from './Contexts/AuthContext'; import { Appbar } from './Components/Appbar';
function App() { import { Routes, Route } from 'react-router-dom';
const { user, signOut } = useAuth(); import { PrivateRoute } from './Components/PrivateRoute';
console.log(user);
const handleSignOut = (e) => {
e.preventDefault();
signOut();
};
function App() {
return ( return (
<> <>
<Container maxWidth="xs"> <Container maxWidth="xs" height="100vh">
{!user ? ( <Routes>
<AuthForm /> <Route
) : ( path="/"
<Button onClick={(e) => handleSignOut(e)}>wewlad signout</Button> element={
)} <PrivateRoute>
<Appbar />
</PrivateRoute>
}
/>
<Route path="/auth" element={<AuthForm />} />
</Routes>
</Container> </Container>
</> </>
); );

44
src/Components/Appbar.js

@ -0,0 +1,44 @@
import { useState } from 'react';
import { Box, AppBar, Typography, Toolbar, IconButton } from '@mui/material';
import AccountCircle from '@mui/icons-material/AccountCircle';
import { UserProfile } from './UserProfile';
export const Appbar = () => {
const [showDrawer, setShowDrawer] = useState(false);
const toggleDrawer = () => (event) => {
if (
event.type === 'keydown' &&
(event.key === 'Tab' || event.key === 'Shift')
) {
return;
}
setShowDrawer((showDrawer) => !showDrawer);
};
return (
<Box>
<AppBar>
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
supachat
</Typography>
<div>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={toggleDrawer()}
color="inherit"
>
<AccountCircle />
</IconButton>
<UserProfile toggleDrawer={toggleDrawer} showDrawer={showDrawer} />
</div>
</Toolbar>
</AppBar>
</Box>
);
};

78
src/Components/AuthForm.js

@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { import {
Box, Box,
Tabs, Tabs,
@ -12,6 +12,7 @@ import {
import LoadingButton from '@mui/lab/LoadingButton'; import LoadingButton from '@mui/lab/LoadingButton';
import { useSnackbar } from '../Contexts/SnackbarContext'; import { useSnackbar } from '../Contexts/SnackbarContext';
import { useAuth } from '../Contexts/AuthContext'; import { useAuth } from '../Contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
export function AuthForm({ setSession }) { export function AuthForm({ setSession }) {
const [tabindex, setTabIndex] = useState(0); const [tabindex, setTabIndex] = useState(0);
@ -78,46 +79,69 @@ export function AuthForm({ setSession }) {
<AuthInputs tabIndex={0} setSession={setSession} /> <AuthInputs tabIndex={0} setSession={setSession} />
</TabPanel> </TabPanel>
<TabPanel value={tabindex} index={1}> <TabPanel value={tabindex} index={1}>
<AuthInputs tabIndex={1} /> <AuthInputs tabIndex={1} setTabIndex={setTabIndex} />
</TabPanel> </TabPanel>
</Box> </Box>
</Box> </Box>
); );
} }
const AuthInputs = ({ tabIndex }) => { const AuthInputs = ({ tabIndex, setTabIndex }) => {
const initial = { email: '', password: '', confirmPassword: '' }; const initial = { email: '', password: '', confirmPassword: '' };
const [credentials, setCredentials] = useState(initial); const [credentials, setCredentials] = useState(initial);
const [loading, setLoading] = useState(false);
const { showSnackBar } = useSnackbar(); const { showSnackBar } = useSnackbar();
const { signIn, signUp } = useAuth(); const { signIn, signUp, user } = useAuth();
const navigate = useNavigate();
const handleValueChanged = ({ target: { name, value } }) => { const handleValueChanged = ({ target: { name, value } }) => {
setCredentials({ ...credentials, [name]: value }); setCredentials({ ...credentials, [name]: value });
}; };
const handleSubmit = async (e) => { useEffect(() => {
e.preventDefault(); if (user) navigate('/');
}, [user, navigate]);
if (credentials.email === '' || credentials.password === '')
return showSnackBar('error', 'missing inputs');
if (tabIndex === 1 && credentials.password !== credentials.confirmPassword)
return showSnackBar('error', `passwords don't match`);
const { user, error } = const handleSubmit = async (e) => {
tabIndex === 0 try {
? await signIn(credentials.email, credentials.password) e.preventDefault();
: await signUp(credentials.email, credentials.password); setLoading(true);
if (error) return showSnackBar('error', error.message); if (credentials.email === '' || credentials.password === '') {
setLoading(false);
if (user) { return showSnackBar('error', 'missing inputs');
tabIndex === 0 }
? showSnackBar('success', `logged in!`)
: showSnackBar( if (
'info', tabIndex === 1 &&
`verification email has been sent to ${credentials.email} pls check this before signing in` credentials.password !== credentials.confirmPassword
); ) {
setLoading(false);
return showSnackBar('error', `passwords don't match`);
}
const { user, error } =
tabIndex === 0
? await signIn(credentials.email, credentials.password)
: await signUp(credentials.email, credentials.password);
if (error) {
setLoading(false);
return showSnackBar('error', error.message);
}
if (user) {
tabIndex === 0
? showSnackBar('success', `logged in!`)
: showSnackBar(
'info',
`verification email sent to ${credentials.email}`
);
}
tabIndex === 1 && setTabIndex(0);
navigate('/');
} catch (error) {
console.log(error);
} }
}; };
@ -165,7 +189,7 @@ const AuthInputs = ({ tabIndex }) => {
<LoadingButton <LoadingButton
fullWidth fullWidth
variant="outlined" variant="outlined"
loading={false} loading={loading}
onClick={(e) => handleSubmit(e)} onClick={(e) => handleSubmit(e)}
type="submit" type="submit"
> >

0
src/Components/Home.js

7
src/Components/PrivateRoute.js

@ -0,0 +1,7 @@
import { Navigate } from 'react-router-dom';
import { useAuth } from '../Contexts/AuthContext';
export const PrivateRoute = ({ children }) => {
const { user } = useAuth();
return user ? children : <Navigate to="/auth" replace />;
};

53
src/Components/UserProfile.js

@ -0,0 +1,53 @@
import {
Drawer,
Button,
IconButton,
Toolbar,
Box,
TextField,
Divider,
Stack,
} from '@mui/material';
import { useAuth } from '../Contexts/AuthContext';
import { useSnackbar } from '../Contexts/SnackbarContext';
import CloseIcon from '@mui/icons-material/Close';
export const UserProfile = ({ showDrawer, toggleDrawer }) => {
const { user, signOut } = useAuth();
const { showSnackBar } = useSnackbar();
const handleSignOut = (e) => {
e.preventDefault();
signOut();
showSnackBar('info', 'user logged out');
};
return (
<Drawer
PaperProps={{
sx: {
// width: 1
width: 350,
},
}}
anchor="right"
open={showDrawer}
onClose={toggleDrawer()}
>
<Toolbar sx={{ flexDirection: 'row' }}>
<Box sx={{ flexGrow: 1 }}>user profile</Box>
<IconButton onClick={toggleDrawer()}>
<CloseIcon />
</IconButton>
</Toolbar>
<Divider />
<Stack m={2} spacing={1}>
<TextField fullWidth label="email" value={user.email} />
<Button fullWidth onClick={(e) => handleSignOut(e)}>
signout
</Button>
</Stack>
</Drawer>
);
};

10
src/Contexts/AuthContext.js

@ -5,16 +5,18 @@ const AuthContext = createContext();
const AuthProvider = ({ children }) => { const AuthProvider = ({ children }) => {
const [user, setUser] = useState(); const [user, setUser] = useState();
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => { useEffect(() => {
const session = supabase.auth.session(); const session = supabase.auth.session();
setUser(session?.user ?? null); setUser(session?.user ?? null);
const { data: listener } = supabase.auth.onAuthStateChange( const { data: listener } = supabase.auth.onAuthStateChange(
async (event, session) => { (event, session) => {
setUser(session?.user ?? null); setUser(session?.user ?? null);
} }
); );
setAuthLoading(false);
return () => { return () => {
listener?.unsubscribe(); listener?.unsubscribe();
@ -52,7 +54,11 @@ const AuthProvider = ({ children }) => {
const value = { signUp, signIn, signOut, user }; const value = { signUp, signIn, signOut, user };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; return (
<AuthContext.Provider value={value}>
{!authLoading && children}
</AuthContext.Provider>
);
}; };
const useAuth = () => { const useAuth = () => {

5
src/Contexts/SnackbarContext.js

@ -18,7 +18,10 @@ const SnackbarProvider = ({ children }) => {
const handleSnackClose = (event, reason) => { const handleSnackClose = (event, reason) => {
if (reason === 'clickaway') return; if (reason === 'clickaway') return;
setSnack(initialSnack); setSnack({
...snack,
show: false,
});
}; };
return ( return (

19
src/index.js

@ -5,6 +5,7 @@ import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material'; import { CssBaseline } from '@mui/material';
import { SnackbarProvider } from './Contexts/SnackbarContext'; import { SnackbarProvider } from './Contexts/SnackbarContext';
import { AuthProvider } from './Contexts/AuthContext'; import { AuthProvider } from './Contexts/AuthContext';
import { BrowserRouter } from 'react-router-dom';
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
@ -14,14 +15,16 @@ const theme = createTheme({
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<ThemeProvider theme={theme}> <BrowserRouter>
<AuthProvider> <ThemeProvider theme={theme}>
<SnackbarProvider> <CssBaseline />
<CssBaseline /> <AuthProvider>
<App /> <SnackbarProvider>
</SnackbarProvider> <App />
</AuthProvider> </SnackbarProvider>
</ThemeProvider> </AuthProvider>
</ThemeProvider>
</BrowserRouter>
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById('root')
); );

Loading…
Cancel
Save