Skip to content

Commit

Permalink
Merge pull request #33 from UtrechtUniversity/dev
Browse files Browse the repository at this point in the history
V1.1 Alpha ResearchConnect
  • Loading branch information
Meefish authored Nov 13, 2024
2 parents b78cba2 + 1c3ae85 commit 34adc62
Show file tree
Hide file tree
Showing 20 changed files with 450 additions and 270 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "reacttestfrontend",
"name": "researchconnect",
"version": "0.1.0",
"private": true,
"dependencies": {
Expand Down Expand Up @@ -56,6 +56,5 @@
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
},
"proxy": "https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl"
}
}
4 changes: 2 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
name="ResearchConnect"
content="Website created by Max Meevis for WildlifeNL researchers"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
Expand Down
57 changes: 31 additions & 26 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,46 @@ import Experiment from './pages/Experiment';
import MessageCreation from './pages/MessageCreation';
import MessageDashboard from './pages/MessageDashboard';
import Unauthorized from './pages/Unauthorized';
import { isAuthenticated } from './services/authService';
import AuthWrapper from './components/AuthWrapper';

const App: React.FC = () => {
return (
<Router>
<AuthWrapper>
<Routes>
{/* Default Route - Redirects to dashboard or login */}
<Route
path="/"
element={<Navigate to={isAuthenticated() ? "/dashboard" : "/login"} replace />}
/>
<Routes>
{/* Public Routes */}
<Route path="/login" element={<Login />} />

{/* Public Route - Login */}
<Route path="/login" element={<Login />} />
{/* Unauthorized Route */}
<Route path="/unauthorized" element={<Unauthorized />} />

{/* Private Routes */}
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/activity" element={<Activity />} />
<Route path="/profile" element={<Profile />} />
<Route path="/export" element={<Export />} />
<Route path="/experimentcreation" element={<ExperimentCreation />} />
<Route path="/experiment/:id" element={<Experiment />} />
<Route path="/messagecreation/:id" element={<MessageCreation />} />
<Route path="/messagedashboard/:id" element={<MessageDashboard />} />
{/* Unauthorized Route */}
<Route path="/unauthorized" element={<Unauthorized />} />
{/* Protected Routes */}
<Route
path="/*"
element={
<AuthWrapper>
<Routes>
{/* Default Route - Redirects to dashboard */}
<Route path="/" element={<Navigate to="/dashboard" replace />} />

{/* Wildcard Route */}
<Route path="*" element={<Navigate to={isAuthenticated() ? "/dashboard" : "/login"} replace />} />
</Routes>
</AuthWrapper>
{/* Private Routes */}
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/activity" element={<Activity />} />
<Route path="/profile" element={<Profile />} />
<Route path="/export" element={<Export />} />
<Route path="/experimentcreation" element={<ExperimentCreation />} />
<Route path="/experiment/:id" element={<Experiment />} />
<Route path="/messagecreation/:id" element={<MessageCreation />} />
<Route path="/messagedashboard/:id" element={<MessageDashboard />} />

{/* Wildcard Route */}
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</AuthWrapper>
}
/>
</Routes>
</Router>
);
};

export default App;
export default App;
38 changes: 23 additions & 15 deletions src/components/AuthWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { isAuthenticated } from '../services/authService';
// components/AuthWrapper.tsx

import React, { useEffect, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { isAuthenticated, getUserRole } from '../services/authService';

interface AuthWrapperProps {
children: React.ReactNode;
}

const AuthWrapper: React.FC<AuthWrapperProps> = ({ children }) => {
const navigate = useNavigate();
const location = useLocation();
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
const checkAuthStatus = () => {
const checkAuthStatus = async () => {
if (isAuthenticated()) {
if (window.location.pathname === '/login') {
navigate('/dashboard', { replace: true });
const role = await getUserRole();
if (role === 3) {
// User has the Researcher role
setLoading(false); // Allow rendering of protected components
} else {
// User does not have the Researcher role
navigate('/unauthorized', { replace: true });
}
} else {
if (window.location.pathname !== '/login') {
navigate('/login', { replace: true });
}
// User is not authenticated
navigate('/login', { replace: true });
}
};

checkAuthStatus();
}, [navigate, location]);

// Set an interval to keep checking if the user is authenticated
const intervalId = setInterval(checkAuthStatus, 5000); // Check every 5 seconds

return () => clearInterval(intervalId);
}, [navigate]);
if (loading) {
// You can show a loading indicator while checking authentication
return <div>Loading...</div>;
}

return <>{children}</>;
};

export default AuthWrapper;
export default AuthWrapper;
94 changes: 18 additions & 76 deletions src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ const Dashboard: React.FC = () => {
const [endDate, setEndDate] = useState<string>('');
const [sortConfig, setSortConfig] = useState<{ key: keyof Experiment; direction: string } | null>(null);

// State for checkboxes
const [selectedExperiments, setSelectedExperiments] = useState<{ [key: string]: boolean }>({});
const [selectAll, setSelectAll] = useState<boolean>(false);

// State for loading
const [isLoading, setIsLoading] = useState<boolean>(true);

Expand Down Expand Up @@ -57,14 +53,6 @@ const Dashboard: React.FC = () => {
responses: (exp.messageActivity ?? 0) + (exp.questionnaireActivity ?? 0),
}));

// Initialize selected experiments state
const initialSelected = exps.reduce((acc, exp) => {
acc[exp.ID] = false;
return acc;
}, {} as { [key: string]: boolean });

setSelectedExperiments(initialSelected);

// Set experiments and filtered experiments
setExperiments(experimentsWithResponses);
setFilteredExperiments(experimentsWithResponses);
Expand Down Expand Up @@ -109,10 +97,6 @@ const Dashboard: React.FC = () => {
let bValue: any;

switch (sortConfig.key) {
case 'user':
aValue = a.user?.name || '';
bValue = b.user?.name || '';
break;
case 'livingLab':
aValue = a.livingLab?.name || '';
bValue = b.livingLab?.name || '';
Expand Down Expand Up @@ -176,40 +160,14 @@ const Dashboard: React.FC = () => {
navigate(`/experiment/${experiment.ID}`, { state: { experiment } });
};

// Handle checkbox change
const handleCheckboxChange = (experimentID: string) => {
setSelectedExperiments((prevSelected) => {
const newSelected = {
...prevSelected,
[experimentID]: !prevSelected[experimentID],
};
const allSelected = Object.values(newSelected).every((selected) => selected);
setSelectAll(allSelected);
return newSelected;
});
};

// Handle select all checkbox change
const handleSelectAllChange = () => {
const newSelectAll = !selectAll;
setSelectAll(newSelectAll);
setSelectedExperiments((prevSelected) => {
const newSelected = { ...prevSelected };
Object.keys(newSelected).forEach((key) => {
newSelected[key] = newSelectAll;
});
return newSelected;
});
};

return (
<div className="dashboard-container" data-testid="dashboard-container">
{/* Navbar */}
<Navbar />

{/* Experiments Title */}
<h1 className="experiments-title" data-testid="experiments-title">
Experiments
My Experiments
</h1>

{/* Filters Container */}
Expand Down Expand Up @@ -313,7 +271,7 @@ const Dashboard: React.FC = () => {
/>
</th>
<th onClick={() => requestSort('name')}>
Experiment
Name
<img
src="/assets/vblacksvg.svg"
alt="Sort Icon"
Expand All @@ -336,14 +294,6 @@ const Dashboard: React.FC = () => {
className={`sort-icon ${getSortIconClass('numberOfMessages')}`}
/>
</th>
<th onClick={() => requestSort('user')}>
Creator
<img
src="/assets/vblacksvg.svg"
alt="Sort Icon"
className={`sort-icon ${getSortIconClass('user')}`}
/>
</th>
<th onClick={() => requestSort('start')}>
Date Range
<img
Expand All @@ -361,14 +311,6 @@ const Dashboard: React.FC = () => {
className={`sort-icon ${getSortIconClass('responses')}`}
/>
</th>
<th>
<input
type="checkbox"
checked={selectAll}
onChange={handleSelectAllChange}
data-testid="select-all-checkbox"
/>
</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -397,24 +339,12 @@ const Dashboard: React.FC = () => {
{exp.numberOfMessages}
</td>
<td onClick={() => handleExperimentClick(exp)}>
{exp.user?.name || 'N/A'}
</td>
<td onClick={() => handleExperimentClick(exp)}>
{new Date(exp.start).toLocaleDateString()} -{' '}
{new Date(exp.end).toLocaleDateString()}
{formatDate(exp.start)} - {exp.end ? formatDate(exp.end) : 'No End Date'}
</td>
<td onClick={() => handleExperimentClick(exp)}>{status}</td>
<td onClick={() => handleExperimentClick(exp)}>
{exp.responses}
</td>
<td>
<input
type="checkbox"
checked={selectedExperiments[exp.ID] || false}
onChange={() => handleCheckboxChange(exp.ID)}
data-testid={`experiment-checkbox-${exp.ID}`}
/>
</td>
</tr>
);
})}
Expand All @@ -437,15 +367,27 @@ const Dashboard: React.FC = () => {
};

// Function to determine the status based on date range
const getStatus = (startDate: string, endDate: string): string => {
const getStatus = (startDate: string, endDate?: string | null): string => {
const today = new Date();
if (new Date(startDate) > today) {
const start = new Date(startDate);
const end = endDate ? new Date(endDate) : null;

if (start > today) {
return 'Upcoming';
} else if (new Date(endDate) < today) {
} else if (end && end < today) {
return 'Completed';
} else {
return 'Live';
}
};

const formatDate = (dateString: string): string => {
if (!dateString) return 'No Date';
const date = new Date(dateString);
if (isNaN(date.getTime())) {
return 'Invalid Date';
}
return date.toLocaleDateString();
};

export default Dashboard;
4 changes: 1 addition & 3 deletions src/pages/Experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import Navbar from '../components/Navbar';
import '../styles/Experiment.css';
import { getMessagesByExperimentID } from '../services/messageService';
import { Experiment as ExperimentType } from '../types/experiment';
import { updateExperiment } from '../services/experimentService'; // Import the update function

Expand Down Expand Up @@ -30,8 +29,7 @@ const Experiment: React.FC = () => {
const handleMessageOverviewClick = async () => {
if (!experiment) return;
try {
const messages = await getMessagesByExperimentID(experiment.ID);
navigate(`/messagedashboard/${experiment.ID}`, { state: { messages, experiment } });
navigate(`/messagedashboard/${experiment.ID}`);
} catch (error) {
console.error('Error fetching messages:', error);
// Optionally display an error message to the user
Expand Down
Loading

0 comments on commit 34adc62

Please sign in to comment.