Skip to content

Commit

Permalink
Updated guest auth and navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
TaylorBeck committed Sep 3, 2024
1 parent 628690c commit 71568d3
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 117 deletions.
12 changes: 9 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { Suspense, lazy } from 'react';
import { Provider } from 'react-redux';
import { store, persistor } from './redux/store';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';
import ChickenDetailsPage from './pages/ChickenDetailsPage';
import FarmDetailsPage from './pages/FarmDetailsPage';
import FarmsPage from './pages/FarmsPage';
import OrdersPage from './pages/OrdersPage';
import GuestAccess from './pages/GuestAccess';

// Lazy load components
const SignUp = lazy(() => import('./pages/SignUp'));
Expand Down Expand Up @@ -51,7 +52,7 @@ function App() {
loading={null}
persistor={persistor}
>
<BrowserRouter>
<Router>
<Suspense fallback={<Loading />}>
<Routes>
<Route
Expand Down Expand Up @@ -114,13 +115,18 @@ function App() {
/>
</Route>

<Route
path="/guest/:guestToken"
element={<GuestAccess />}
/>

<Route
path="*"
element={<NotFound />}
/>
</Routes>
</Suspense>
</BrowserRouter>
</Router>
</PersistGate>
</Provider>
);
Expand Down
58 changes: 51 additions & 7 deletions src/api/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from './firebaseConfig';
import { signInWithEmailAndPassword, signInAnonymously } from 'firebase/auth';
import { auth, db } from './firebaseConfig';
import { ref, get } from 'firebase/database';

const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api';

Expand Down Expand Up @@ -27,13 +28,12 @@ export const createUser = async userData => {
export const loginUser = async credentials => {
const { email, password } = credentials;
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
// Authenticate with Firebase
const userCredential = await signInWithEmailAndPassword(auth, email, password);
// Get the Firebase ID token
const idToken = await userCredential.user.getIdToken();

// Send the ID token to our backend for verification and to get user data
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: {
Expand All @@ -53,3 +53,47 @@ export const loginUser = async credentials => {
throw error;
}
};

export const authenticateGuest = async guestToken => {
try {
// Get a reference to the 'farms' node in the Firebase Realtime Database
const farmsRef = ref(db, 'farms');
// Fetch all farms data
const farmsSnapshot = await get(farmsRef);

if (farmsSnapshot.exists()) {
const farms = farmsSnapshot.val();

// Iterate through all farms
for (const [farmId, farmData] of Object.entries(farms)) {
// Check if the farm has guest access enabled
if (farmData.guestAccess) {
// Iterate through all guest access entries for this farm
for (const [guestId, guestData] of Object.entries(farmData.guestAccess)) {
// Check if the provided token matches a guest token
if (guestData.token === guestToken) {
// Check if the token has expired
const expiresAt = new Date(guestData.expiresAt);
if (expiresAt > new Date()) {
// Return the farmId and guestToken if the token is valid
return { farmId, guestToken };
} else {
// Throw an error if the token has expired
throw new Error('Guest token has expired');
}
}
}
}
}
} else {
console.log('No farms data found');
}

// If we've gone through all farms and haven't found a match, the token is invalid
console.log('No matching guest token found');
throw new Error('Invalid guest token');
} catch (error) {
console.error('Error authenticating guest:', error);
throw error;
}
};
127 changes: 57 additions & 70 deletions src/components/layout/MainLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
import { styled, createTheme, ThemeProvider } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useDispatch, useSelector } from 'react-redux';
import { logout } from '../../redux/slices/authSlice';
import { logout, clearGuestAccess } from '../../redux/slices/authSlice';
import { persistor } from '../../redux/store';
import { auth } from '../../api/firebaseConfig';
import { Breadcrumbs, Link } from '@mui/material';
Expand Down Expand Up @@ -53,6 +53,7 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

// Custom drawer widths for open and closed states
const drawerWidth = 200;
const closedDrawerWidth = 59;

Expand Down Expand Up @@ -80,6 +81,7 @@ const theme = createTheme({
}
});

// Custom styled components for responsive drawer and app bar
const StyledAppBar = styled(AppBar, {
shouldForwardProp: prop => prop !== 'open'
})(({ theme, open }) => ({
Expand Down Expand Up @@ -167,12 +169,20 @@ export default function Dashboard({ children, title }) {
const navigate = useNavigate();
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user);
const isGuest = useSelector(state => state.auth.isGuest);
const isAuthenticated = useSelector(state => state.auth.isAuthenticated);
const [open, setOpen] = useState(true);
const [anchorEl, setAnchorEl] = useState(null);
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const [notificationsOpen, setNotificationsOpen] = useState(false);
const location = useLocation();

// Filter menu items for guest users
const visibleMenuItems = isGuest
? menuItems.filter(item => item.text === 'Farms')
: menuItems;

// Mapping for breadcrumb navigation
const breadcrumbNameMap = {
'/dashboard': 'Dashboard',
'/farms': 'Farms',
Expand Down Expand Up @@ -210,16 +220,21 @@ export default function Dashboard({ children, title }) {
const popoverOpen = Boolean(anchorEl);

const handleLogout = async () => {
await auth.signOut(); // Sign out from Firebase
dispatch(logout()); // Dispatch the logout action
persistor.purge(); // Clear persisted store data
if (isGuest) {
dispatch(clearGuestAccess());
} else {
await auth.signOut(); // Sign out from Firebase
dispatch(logout()); // Dispatch the logout action
persistor.purge(); // Clear persisted store data
}
navigate('/sign-in'); // Navigate to sign-in page
};

const toggleNotifications = () => {
setNotificationsOpen(!notificationsOpen);
};

// Mock notifications data
const notifications = [
{
id: 1,
Expand Down Expand Up @@ -259,6 +274,7 @@ export default function Dashboard({ children, title }) {
}
];

// Helper function to get appropriate icon for notification type
const getNotificationIcon = type => {
switch (type) {
case 'warning':
Expand All @@ -276,8 +292,10 @@ export default function Dashboard({ children, title }) {

return (
<ThemeProvider theme={theme}>
{/* Main layout structure */}
<Box sx={{ display: 'flex' }}>
<CssBaseline />
{/* Responsive app bar */}
<StyledAppBar
position="fixed"
open={open}
Expand Down Expand Up @@ -320,6 +338,7 @@ export default function Dashboard({ children, title }) {
</IconButton>
</Toolbar>
</StyledAppBar>
{/* Responsive side drawer */}
<StyledDrawer
variant="permanent"
open={open}
Expand Down Expand Up @@ -353,7 +372,7 @@ export default function Dashboard({ children, title }) {
</Toolbar>
<Divider />
<List component="nav">
{menuItems.map(item => (
{visibleMenuItems.map(item => (
<ListItem
button={'true'}
key={item.text}
Expand Down Expand Up @@ -386,78 +405,43 @@ export default function Dashboard({ children, title }) {
</ListItem>
))}
</List>
<Box
sx={{
bottom: 0,
width: '100%',
p: 2,
display: 'flex',
height: '100%',
alignItems: 'flex-end',
justifyContent: 'center'
}}
>
<IconButton
onClick={handlePopoverOpen}
{/* Add Logout option at the bottom for both users and guests */}
{isAuthenticated && (
<Box
sx={{
marginLeft: '0',
padding: '0'
position: 'absolute',
bottom: 0,
width: '100%',
p: 2
}}
>
<Avatar
alt="User Avatar"
src={
user?.image ||
'https://s3-poultrypro.s3.us-west-2.amazonaws.com/face-1.png'
}
/>
</IconButton>
<Popover
open={popoverOpen}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
>
<List>
<ListItem
button={'true'}
onClick={() => navigate('/settings')}
sx={{
'&:hover': {
cursor: 'pointer'
}
}}
>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary="Settings" />
</ListItem>
<ListItem
button={'true'}
onClick={handleLogout}
<ListItem
button
onClick={handleLogout}
sx={{
justifyContent: open ? 'initial' : 'center',
px: 2.5,
'&:hover': { cursor: 'pointer' }
}}
>
<ListItemIcon
sx={{
'&:hover': {
cursor: 'pointer'
}
minWidth: 0,
mr: open ? 3 : 'auto',
justifyContent: 'center'
}}
>
<ListItemIcon>
<LogoutIcon />
</ListItemIcon>
<ListItemText primary="Logout" />
</ListItem>
</List>
</Popover>
</Box>
<LogoutIcon />
</ListItemIcon>
<ListItemText
primary={isGuest ? 'Exit Guest' : 'Logout'}
sx={{ opacity: open ? 1 : 0 }}
/>
</ListItem>
</Box>
)}
</StyledDrawer>
{/* Notifications drawer */}
<Drawer
anchor="right"
open={notificationsOpen}
Expand Down Expand Up @@ -502,12 +486,14 @@ export default function Dashboard({ children, title }) {
</List>
</Box>
</Drawer>
{/* Main content area */}
<MainContent open={open}>
<Toolbar />
<Container
maxWidth="lg"
sx={{ mt: 0, ml: 0, mb: 4, pr: 0, mr: 0 }}
>
{/* Dynamic breadcrumb navigation */}
<Breadcrumbs
aria-label="breadcrumb"
sx={{ mb: 2 }}
Expand Down Expand Up @@ -542,6 +528,7 @@ export default function Dashboard({ children, title }) {
);
})}
</Breadcrumbs>
{/* Render child components */}
<Grid container>{children}</Grid>
</Container>
</MainContent>
Expand Down
Loading

0 comments on commit 71568d3

Please sign in to comment.