Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: The navbar can now display the username after logging in #65

Merged
merged 8 commits into from
Oct 28, 2024
7 changes: 4 additions & 3 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SignUp from './pages/SignUp';

const App: React.FC = () => {
const [isDarkMode, setIsDarkMode] = useState(false); // Dark mode state
const [username, setUsername] = useState<string | null>(null);

const toggleDarkMode = () => {
setIsDarkMode((prev) => !prev);
Expand All @@ -18,12 +19,12 @@ const App: React.FC = () => {

return (
<Router>
<Navbar toggleDarkMode={toggleDarkMode} isDarkMode={isDarkMode} />
<Navbar toggleDarkMode={toggleDarkMode} isDarkMode={isDarkMode} username={username} setUsername={setUsername} />
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/login" element={<Login setUsername={setUsername} />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/" element={<Home isDarkMode={isDarkMode} />} />
<Route path="/profile" element={<Profile isDarkMode={isDarkMode} />} />
<Route path="/profile" element={<Profile isDarkMode={isDarkMode} username={username} />} />
<Route
path="/events-dashboard"
element={<EventsDashboard isDarkMode={isDarkMode} />}
Expand Down
46 changes: 25 additions & 21 deletions client/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NavbarProps } from '../types';
import LOGO from '../assets/RAILGUIDE.png';
//import '../index.scss';

const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode }) => {
const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode, username, setUsername }) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const navigate = useNavigate();
Expand All @@ -15,15 +15,13 @@ const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode }) => {

const handleLogout = () => {
console.log('User logged out');
navigate('/');
setUsername(null);
navigate('/');
};

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setDropdownOpen(false);
}
};
Expand All @@ -37,7 +35,7 @@ const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode }) => {
return (
<nav className={isDarkMode ? 'dark-mode' : ''}>
<Link to="/" className="logo" title="Home">
<img src={LOGO} alt="Wood Plank T" className="logo-image" />
<img src={LOGO} alt="Wood Plank T" className="logo-image" />
</Link>
<div className="nav-buttons">
<Link to="/events-dashboard" className="nav-button">
Expand All @@ -46,26 +44,32 @@ const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode }) => {
<button onClick={toggleDarkMode} className="nav-button">
{isDarkMode ? 'LIGHT MODE' : 'DARK MODE'}
</button>
<div
className="nav-button"
onClick={toggleDropdown}
aria-haspopup="true"
aria-expanded={dropdownOpen}
>
USER
</div>

<div
className="nav-button"
onClick={toggleDropdown}
aria-haspopup="true"
aria-expanded={dropdownOpen}
>
{username && typeof username === 'string' ? username.toUpperCase() : "USER"}
</div>

</div>
{dropdownOpen && (
<div className="dropdown" ref={dropdownRef}>
<Link to="/profile" className="dropdown-link">
Profile
</Link>
<Link to="/login" className="dropdown-link">
Login
</Link>
<div className="dropdown-link" onClick={handleLogout}>
Logout
</div>
{!username && (
<Link to="/login" className="dropdown-link">
Login
</Link>
)}
{username && (
<div className="dropdown-link" onClick={handleLogout}>
Logout
</div>
)}
</div>
)}
</nav>
Expand Down
47 changes: 29 additions & 18 deletions client/src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import React, { useState } from "react";
import { useNavigate, Link } from "react-router-dom";

const Login: React.FC = () => {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const Login: React.FC<{
setUsername: React.Dispatch<React.SetStateAction<string | null>>;
}> = ({ setUsername }) => {
const [username, setLocalUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();

Expand All @@ -14,55 +16,62 @@ const Login: React.FC = () => {

// Basic form validation
if ((!username && !email) || (username && email)) {
setError('Please provide either a username or an email, but not both');
setError("Please provide either a username or an email, but not both");
return;
}

// Email format validation if email is provided
if (email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setError('Please enter a valid email address');
setError("Please enter a valid email address");
return;
}
}

try {
//Send resgiter request to the backend
const response = await fetch('/api/login', {
method: 'POST',
const response = await fetch("/api/login", {
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username || null,
work_email: email || null,
password,
}),
});

const { username: dbUsername } = await response.json() as {username: string};
console.log(dbUsername);
if (response.ok) {
console.log('Sign-up successful!');
navigate('/profile');
setUsername(dbUsername);
console.log("Sign-up successful!");
navigate("/profile");
}
} catch (err) {
setError('Error logging in. Please try again.');
console.error(err, 'Error in login at Login.tsx;');
setError("Error logging in. Please try again.");
console.error(err, "Error in login at Login.tsx;");
}
};

return (
<div className="login-container">
<h2>Login</h2>
{error && <div className="error-message">{error}</div>}
{error && (
<div className="error-message" role="alert">
{error}
</div>
)}
<form onSubmit={(event) => void handleLogin(event)}>
<div className="form-group">
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
onChange={(e) => setLocalUsername(e.target.value)}
autoComplete="username"
/>
</div>
<div className="form-group">
Expand All @@ -72,6 +81,7 @@ const Login: React.FC = () => {
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="email"
/>
</div>
<div className="form-group">
Expand All @@ -82,6 +92,7 @@ const Login: React.FC = () => {
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
/>
</div>
<button className="login-button" type="submit">
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
import { ProfileProps } from "../types";
//import "../profile.css";

Expand Down
4 changes: 4 additions & 0 deletions client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

export interface ProfileProps {
isDarkMode: boolean;
username: string | null

}

export interface CardProps {
Expand All @@ -36,6 +38,8 @@ export interface EventsDashboardProps {
export interface NavbarProps {
toggleDarkMode: () => void;
isDarkMode: boolean;
username: string | null;
setUsername: React.Dispatch<React.SetStateAction<string | null>>;
}

export interface EventCardProps {
Expand Down
22 changes: 13 additions & 9 deletions server/models/eventsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ try {
} catch (error) {
console.log(
`Cannot create cloudtrail client with following credentials: Access Key: ${
process.env.VITE_AWS_ACCESS_KEY_ID
process.env.AWS_ACCESS_KEY_ID
}, Region: ${
process.env.AWS_REGION
} Secret Access Key type: ${typeof process.env.AWS_SECRET_ACCESS_KEY}`
Expand All @@ -74,6 +74,7 @@ async function getLastEvent() {
LIMIT 1;
`
);
if (result.rows.length === 0) return;
return new Date(result.rows[0].time);
} catch (error) {
console.warn('Could not get last event!: ' + error);
Expand All @@ -85,11 +86,14 @@ async function updateEvents(next, config = {}) {
// continue receiving them
// otherwise, find the most recent event in the database,
// and get any events more recent than that
if (!next) config.StartTime = await getLastEvent();

const command = new LookupEventsCommand(config);
const data = await cloudtrailClient.send(command);
if (!next) {
const startTime = await getLastEvent();
if (startTime) config.StartTime = startTime;
}

const command = new LookupEventsCommand(config);
const data = await cloudtrailClient.send(command);

for (const event of data.Events) {
const cloudtrailevent = JSON.parse(event.CloudTrailEvent);
// console.log(cloudtrailevent);
Expand Down Expand Up @@ -137,8 +141,8 @@ async function updateEvents(next, config = {}) {
cloudtrailevent.userIdentity.accountId,
cloudtrailevent.userIdentity.arn,
cloudtrailevent.awsRegion,
cloudtrailevent.tlsDetails?.cipherSuite || NULL,
cloudtrailevent.tlsDetails?.clientProvidedHostHeader || NULL,
cloudtrailevent.tlsDetails?.cipherSuite || 'NULL',
cloudtrailevent.tlsDetails?.clientProvidedHostHeader || 'NULL',
cloudtrailevent.eventCategory,
event.EventTime.toUTCString(),
cloudtrailevent.eventType,
Expand All @@ -148,7 +152,7 @@ async function updateEvents(next, config = {}) {
cloudtrailevent.recipientAccountId,
cloudtrailevent.requestID,
cloudtrailevent.sourceIPAddress,
cloudtrailevent.tlsDetails?.tlsVersion || NULL,
cloudtrailevent.tlsDetails?.tlsVersion || 'NULL',
cloudtrailevent.userIdentity.type,
cloudtrailevent.userAgent,
]
Expand All @@ -164,6 +168,6 @@ function repeatUpdate(next, config) {
setTimeout(async () => {
const { new_next, new_config } = updateEvents(next, config);
repeatUpdate(new_next, new_config);
}, 1000 * 60);
}, 1000 * 10);
}
repeatUpdate();
1 change: 1 addition & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ app.post('/api/signup', userController.createUser, (req, res) => {
res.status(201).json(res.locals.createdUser);
});


//login router
app.post('/api/login', userController.loginUser, (req, res) => {
res.status(200).json(res.locals.loggedinuser);
Expand Down
Loading