diff --git a/client/src/components/MapComponent/MapComponent.tsx b/client/src/components/MapComponent/MapComponent.tsx index 1fe4b66..4799dc2 100644 --- a/client/src/components/MapComponent/MapComponent.tsx +++ b/client/src/components/MapComponent/MapComponent.tsx @@ -17,6 +17,7 @@ import FitBounds from "./FitBounds"; import { InstructionsI, SummaryI } from "../../Types/Route"; import SosButton from "../../pages/journeyPage/SosButton"; + export interface MapComponentProps { latitude: number | null; // User's latitude longitude: number | null; // User's longitude @@ -122,7 +123,7 @@ const MapComponent: React.FC = ({ latLng(lat, lon))} - color="green" + color="pink" /> ))} diff --git a/client/src/hooks/useSocket.tsx b/client/src/hooks/useSocket.tsx index b35f07d..1c46b97 100644 --- a/client/src/hooks/useSocket.tsx +++ b/client/src/hooks/useSocket.tsx @@ -49,9 +49,11 @@ export const useSocket = ({ password }: { password?: string }) => { } } + function sendPosition (position: PositionI) { if (!position) { console.warn("No position to send!"); + return; } diff --git a/client/src/pages/journeyPage/JourneyPage.tsx b/client/src/pages/journeyPage/JourneyPage.tsx index 154fadf..f797ad2 100644 --- a/client/src/pages/journeyPage/JourneyPage.tsx +++ b/client/src/pages/journeyPage/JourneyPage.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import MapComponent from "../../components/MapComponent/MapComponent"; import ProgressBar from "./ProgressBar"; -import SosButton from "./SosButton"; + import AlarmButton from "./AlarmButton"; import WeatherInfo from "./WeatherInfo"; import { PositionI, usePosition } from "../../hooks/usePosition"; @@ -19,6 +19,8 @@ import { useSocket } from "../../hooks/useSocket"; import { TbNavigationCancel } from "react-icons/tb"; import Footer from "../../components/Footer" import { MdEmergencyShare } from "react-icons/md"; +import { MdOutlineAltRoute } from "react-icons/md"; + @@ -101,10 +103,7 @@ const JourneyPage: React.FC = () => { - const handleSOSActivated = () => { - console.log('SOS button activated on Journey Page!'); - - }; + // Handle rerouting if the user deviates from the route const handleReroute = useCallback(async () => { @@ -260,7 +259,7 @@ useEffect(() => { < ProgressBar progress={rerouted ? 75 : 50} />
- + < AlarmButton /> { }
- + < WeatherInfo /> diff --git a/client/src/pages/profilePage/InfoComponent.tsx b/client/src/pages/profilePage/InfoComponent.tsx index 6b508eb..84d798c 100644 --- a/client/src/pages/profilePage/InfoComponent.tsx +++ b/client/src/pages/profilePage/InfoComponent.tsx @@ -4,247 +4,182 @@ import { useLoginStatus } from "../../hooks/userLogin"; import { editProfile } from "../../services/authService"; const InfoComponent: React.FC = () => { - const { userData, handleLogin } = useLoginStatus(); - const [editMode, setEditMode] = useState(false); - const [showModal, setShowModal] = useState(false); - const [reenteredPassword, setReenteredPassword] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); - const [fieldBeingEdited, setFieldBeingEdited] = useState(null); - const [formData, setFormData] = useState({ - firstName: userData?.firstName || "", - lastName: userData?.lastName || "", - email: userData?.email || "", - password: userData?.password || "", - telephone: userData?.telephone || "", + const { userData, handleLogin } = useLoginStatus(); + const [editMode, setEditMode] = useState(false); + const [showModal, setShowModal] = useState(false); + const [reenteredPassword, setReenteredPassword] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const [formData, setFormData] = useState({ + firstName: userData?.firstName || "", + lastName: userData?.lastName || "", + email: userData?.email || "", + password: "", + telephone: userData?.telephone || "", + }); + + useEffect(() => { + // Load user data from local storage or userData + const storedUserData = localStorage.getItem("userData"); + if (storedUserData) { + setFormData(JSON.parse(storedUserData)); + } else if (userData) { + setFormData({ + firstName: userData.firstName || "", + lastName: userData.lastName || "", + email: userData.email || "", + password: "", + telephone: userData.telephone || "", + }); + } + }, [userData]); + + const toggleEditMode = () => { + setEditMode(!editMode); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, }); - - useEffect(() => { - if (userData) { - setFormData({ - firstName: userData.firstName || "", - lastName: userData.lastName || "", - email: userData.email || "", - password: userData.password || "", - telephone: userData.telephone || "", - }); - } - }, [userData]); - - const toggleEditMode = () => { - setEditMode(!editMode); - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData({ - ...formData, - [name]: value, - }); - }; - - const handleSaveField = (fieldName: string) => { - setFieldBeingEdited(fieldName); - - if (fieldName === "password") { - // Show the modal only for password changes - setShowModal(true); - } else { - // Directly submit data for other fields - submitData(fieldName); - } - }; - - const handleReenteredPasswordChange = (e: React.ChangeEvent) => { - setReenteredPassword(e.target.value); - }; - - const submitData = async (fieldName: string) => { - if (!userData) return; - - try { - const payload: { - [key in keyof typeof formData]?: string; - } & { _id: string } = { - [fieldName]: formData[fieldName as keyof typeof formData], - _id: userData._id, - - }; - - if (fieldName === "password") { - payload.password = reenteredPassword; - } - - await editProfile(payload, handleLogin); - - console.log("Profile updated successfully!"); - setShowModal(false); - setFieldBeingEdited(null); - setEditMode(false); - setErrorMessage('') - } catch (error) { - console.error("Error updating profile:", error); - setErrorMessage("Failed to update profile. Please try again."); - } - - setReenteredPassword(""); - }; - - return ( -
- {!editMode ? ( - <> - - - - - - - - - - - - - - - - - - - - - - - -
First Name:{userData && userData.firstName}
Last Name:{userData && userData.lastName}
Email:{userData && userData.email}
Password:{userData && userData.password}
Phone:{userData && userData.telephone}
- - - ) : ( -
- {/* First Name Field */} -
- - - -
- - {/* Last Name Field */} -
- - - -
- - {/* Email Field */} -
- - - -
- - {/* Password Field */} -
- - - -
- - {/* Phone Field */} -
- - - -
- -
- )} - {showModal && ( -
-
-

Confirm Your Password

-

Please re-enter your password to confirm changes:

- - {errorMessage &&

{errorMessage}

} - - -
-
- )} -
- ); + }; + + const handleSaveField = async (fieldName: string) => { + try { + const payload = { + [fieldName]: formData[fieldName as keyof typeof formData], + _id: userData?._id || "", + }; + + // If editing password, show the modal + if (fieldName === "password") { + setShowModal(true); + return; + } + + const response = await editProfile(payload, handleLogin); + if (response) { + console.log("Profile updated successfully!"); + const updatedUserData = { + ...formData, + [fieldName]: formData[fieldName as keyof typeof formData], + }; + localStorage.setItem("userData", JSON.stringify(updatedUserData)); + setFormData(updatedUserData); + setEditMode(false); + } + } catch (error) { + console.error("Error updating profile:", error); + setErrorMessage("Failed to update profile. Please try again."); + } + }; + + const handleReenteredPasswordChange = (e: React.ChangeEvent) => { + setReenteredPassword(e.target.value); + }; + + const submitPasswordChange = async () => { + try { + const payload = { + password: reenteredPassword, + _id: userData?._id || "", + }; + + const response = await editProfile(payload, handleLogin); + if (response) { + console.log("Password updated successfully!"); + localStorage.setItem("userData", JSON.stringify({ ...formData, password: "" })); + setFormData({ ...formData, password: "" }); + setShowModal(false); + } + } catch (error) { + console.error("Error updating password:", error); + setErrorMessage("Failed to update password. Please try again."); + } + setReenteredPassword(""); + }; + + const handleCancel = () => { + // Reset form data to the original state + const storedUserData = localStorage.getItem("userData"); + if (storedUserData) { + setFormData(JSON.parse(storedUserData)); + } + setEditMode(false); + }; + + return ( +
+ {!editMode ? ( + <> + + + + + + + + + + + + + + + + + + + +
First Name:{formData.firstName}
Last Name:{formData.lastName}
Email:{formData.email}
Phone:{formData.telephone}
+ + + ) : ( +
+ {["firstName", "lastName", "email", "telephone"].map((field) => ( +
+ + + +
+ ))} + +
+ )} + {showModal && ( +
+
+

Confirm Your Password

+

Please re-enter your password to confirm changes:

+ + {errorMessage &&

{errorMessage}

} + + +
+
+ )} +
+ ); }; export default InfoComponent; \ No newline at end of file diff --git a/client/src/services/authService.tsx b/client/src/services/authService.tsx index 3624a0c..e02e6df 100644 --- a/client/src/services/authService.tsx +++ b/client/src/services/authService.tsx @@ -1,15 +1,7 @@ import axios, { AxiosResponse } from "axios"; -import { RegisterDataI, LoginDataI, UserI } from "../Types/User"; +import { UserI, RegisterDataI, LoginDataI } from "../Types/User"; - - -// i could not get env to work - have it here -// Base URL for the backend API -const AUTH_URL = "https://glowpath-a7681fe09c29.herokuapp.com" + '/auth'; -// Get the stored token from localStorage -export const getToken = (): string | null => { - return localStorage.getItem("token"); -}; +const AUTH_URL = "https://glowpath-a7681fe09c29.herokuapp.com/auth"; export interface AuthResponse { token?: string; @@ -17,86 +9,101 @@ export interface AuthResponse { user?: UserI; } +// Function to retrieve token from localStorage +export const getToken = (): string | null => { + return localStorage.getItem("token"); +}; -export const register = async (userData: RegisterDataI, handleLogin: (token: string, userData: UserI) => void +// Function to register a new user +export const register = async ( + userData: RegisterDataI, + handleLogin: (token: string, userData: UserI) => void ): Promise | undefined> => { try { const response = await axios.post(`${AUTH_URL}/register`, userData, { withCredentials: true, headers: { - 'Content-Type': 'application/json' - } + "Content-Type": "application/json", + }, }); + if (response && response.data.token) { localStorage.setItem("token", response.data.token); if (response.data.user) { - const user: UserI = response.data.user - handleLogin(response.data.token, user); - + handleLogin(response.data.token, response.data.user); } - - return response - }; - - - } - catch (error) { - console.log('auth service error on register: ', error) + return response; + } + } catch (error) { + console.error("AuthService error on register:", error); throw error; } -} - +}; -export const login = async (userData: LoginDataI, handleLogin: (token: string, userData: UserI) => void +// Function to log in a user +export const login = async ( + userData: LoginDataI, + handleLogin: (token: string, userData: UserI) => void ): Promise> => { try { const response = await axios.post(`${AUTH_URL}/login`, userData, { - withCredentials: true, - headers: { - 'Content-Type': 'application/json', - } + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, }); if (response.data.token && response.data.user) { - handleLogin(response.data.token, response.data.user); + localStorage.setItem("token", response.data.token); + handleLogin(response.data.token, response.data.user); } - return response - - } - catch (error) { - console.log('service error logging in: ', error) - throw error + return response; + } catch (error) { + console.error("Service error logging in:", error); + throw error; } }; +// Function to edit user profile export const editProfile = async ( userEdit: { - [key in keyof typeof FormData]?: string; - } & { _id: string }, + [key: string]: string | undefined; // Accepts any key with string values + _id: string; // _id is required + }, handleLogin: (token: string, userData: UserI) => void -): Promise | undefined> => { +): Promise> => { try { - const token = getToken(); + const token = getToken(); // Retrieve the token from localStorage + if (!token) { + throw new Error("No token found. Please log in again."); + } - console.log('sending to edit: ', userEdit); + console.log("Sending to edit: ", userEdit); const response = await axios.post(`${AUTH_URL}/edit`, userEdit, { withCredentials: true, headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - } + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, // Attach the token + }, }); + // If the server responds with new token and user data, update the state if (response.data.token && response.data.user) { handleLogin(response.data.token, response.data.user); } + return response; } catch (error) { - console.log('error loading profile', error); - throw error; + // Handle errors more gracefully + if (axios.isAxiosError(error)) { + console.error("Axios error:", error.response?.data || error.message); + if (error.response?.status === 401) { + throw new Error("Unauthorized: Your session may have expired. Please log in again."); + } + } else { + console.error("Unexpected error:", error); + } + throw error; // Rethrow error for higher-level handling } -}; - - - +}; \ No newline at end of file diff --git a/client/src/styles/InfoComponent.css b/client/src/styles/InfoComponent.css index 8cb95e4..44a2ea8 100644 --- a/client/src/styles/InfoComponent.css +++ b/client/src/styles/InfoComponent.css @@ -86,3 +86,258 @@ .theme-toggle-button:hover { background-color: black; } + +/* Info Form Styling */ +.info-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + padding: 1.5rem; + background-color: #f5f5f5; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 500px; + margin: 0 auto; +} + +/* Form Group */ +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-group label { + font-size: 1rem; + font-weight: bold; + color: #333; +} + +.form-group input { + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; + transition: border-color 0.3s ease, box-shadow 0.3s ease; +} + +.form-group input:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 4px rgba(0, 123, 255, 0.5); +} + +/* Buttons */ +button { + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: bold; + color: #fff; + background-color: #007bff; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +button:hover { + background-color: #0056b3; + transform: scale(1.03); +} + +.cancel-button { + background-color: #dc3545; +} + +.cancel-button:hover { + background-color: #c82333; +} + +/* Modal Styling */ +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +} + +.modal { + background-color: #fff; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + text-align: center; + width: 100%; + max-width: 400px; +} + +.modal h3 { + font-size: 1.5rem; + color: #333; + margin-bottom: 1rem; +} + +.modal p { + font-size: 1rem; + color: #555; + margin-bottom: 1rem; +} + +.modal input { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 5px; + margin-bottom: 1rem; +} + +.modal input:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 4px rgba(0, 123, 255, 0.5); +} + +.modal .confirm-button { + background-color: #28a745; +} + +.modal .confirm-button:hover { + background-color: #218838; +} + +/* Info Form Styling */ +.info-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + padding: 1.5rem; + background-color: #232323; /* Black background */ + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.7); + width: 100%; + max-width: 500px; + margin: 0 auto; + } + + /* Form Group */ + .form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .form-group label { + font-size: 1rem; + font-weight: bold; + color: #f5f5f5; /* Light grey text */ + } + + .form-group input { + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #4a464d; /* Dark grey border */ + border-radius: 5px; + background-color: #2c2c2c; /* Darker grey input background */ + color: #f5f5f5; /* Light grey text */ + transition: border-color 0.3s ease, box-shadow 0.3s ease; + } + + .form-group input:focus { + outline: none; + border-color: #ff3c99; /* Pink highlight */ + box-shadow: 0 0 4px rgba(255, 60, 153, 0.5); /* Subtle pink glow */ + } + + /* Buttons */ + button { + padding: 0.75rem 1.5rem; + font-size: 1rem; + font-weight: bold; + color: #f5f5f5; /* Light grey text */ + background-color: #4a464d; /* Grey button background */ + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + button:hover { + background-color: #ff3c99; /* Pink hover */ + transform: scale(1.03); + } + + .cancel-button { + background-color: #6c757d; /* Grey cancel button */ + } + + .cancel-button:hover { + background-color: #5a6268; /* Darker grey */ + } + + /* Modal Styling */ + .modal-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); /* Darker backdrop */ + display: flex; + justify-content: center; + align-items: center; + } + + .modal { + background-color: #2c2c2c; /* Dark grey modal background */ + padding: 2rem; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.7); + text-align: center; + width: 100%; + max-width: 400px; + } + + .modal h3 { + font-size: 1.5rem; + color: #f5f5f5; /* Light grey text */ + margin-bottom: 1rem; + } + + .modal p { + font-size: 1rem; + color: #dcdcdc; /* Grey text */ + margin-bottom: 1rem; + } + + .modal input { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #4a464d; /* Dark grey border */ + border-radius: 5px; + background-color: #2c2c2c; /* Dark grey background */ + color: #f5f5f5; /* Light grey text */ + margin-bottom: 1rem; + } + + .modal input:focus { + outline: none; + border-color: #ff3c99; /* Pink highlight */ + box-shadow: 0 0 4px rgba(255, 60, 153, 0.5); /* Pink glow */ + } + + .modal .confirm-button { + background-color: #4a464d; /* Grey confirm button */ + } + + .modal .confirm-button:hover { + background-color: #ff3c99; /* Pink hover */ + } \ No newline at end of file diff --git a/client/src/styles/MapComponent.css b/client/src/styles/MapComponent.css index 99ea8bf..681f172 100644 --- a/client/src/styles/MapComponent.css +++ b/client/src/styles/MapComponent.css @@ -1,5 +1,5 @@ .glowing-polyline { - stroke:pink; + stroke:rgb(255, 192, 203); stroke-width: 4px; stroke-linecap: round; stroke-linejoin: round; @@ -12,7 +12,7 @@ svg { } .glow { - stroke: pink; + stroke: rgb(255, 192, 203); stroke-width: 10px; stroke-linecap: round; stroke-linejoin: round; diff --git a/client/src/styles/SettingsPage.css b/client/src/styles/SettingsPage.css index 11f26c0..d7b9bb1 100644 --- a/client/src/styles/SettingsPage.css +++ b/client/src/styles/SettingsPage.css @@ -97,7 +97,7 @@ padding: 10px 20px; font-size: 1rem; border: none; background-color: var(--button-background); - color: var(--button-color); + color: white; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease, color 0.3s ease, transform 0.2s ease;