diff --git a/MisplaceAI/camera_integration/__init__.py b/MisplaceAI/camera_integration/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/MisplaceAI/camera_integration/admin.py b/MisplaceAI/camera_integration/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/MisplaceAI/camera_integration/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/MisplaceAI/camera_integration/apps.py b/MisplaceAI/camera_integration/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..991a054e1c4a8c9224838af49dc3d4ba5d936389 --- /dev/null +++ b/MisplaceAI/camera_integration/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CameraIntegrationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'camera_integration' diff --git a/MisplaceAI/camera_integration/models.py b/MisplaceAI/camera_integration/models.py new file mode 100644 index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91 --- /dev/null +++ b/MisplaceAI/camera_integration/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/MisplaceAI/camera_integration/tests.py b/MisplaceAI/camera_integration/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/MisplaceAI/camera_integration/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/MisplaceAI/camera_integration/views.py b/MisplaceAI/camera_integration/views.py new file mode 100644 index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e --- /dev/null +++ b/MisplaceAI/camera_integration/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py index 46b3e496d9d9d34bd7a2189cdee157b13ad98a4b..8c40d78cc2199df51af35cb716fe5798ff05bd8f 100644 --- a/MisplaceAI/user_dashboard/serializers.py +++ b/MisplaceAI/user_dashboard/serializers.py @@ -6,3 +6,20 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['username', 'email', 'first_name', 'last_name'] + +class UserUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['username', 'email', 'first_name', 'last_name'] + + def update(self, instance, validated_data): + instance.username = validated_data.get('username', instance.username) + instance.email = validated_data.get('email', instance.email) + instance.first_name = validated_data.get('first_name', instance.first_name) + instance.last_name = validated_data.get('last_name', instance.last_name) + instance.save() + return instance + +class UserUpdateEmailSerializer(serializers.Serializer): + email = serializers.EmailField() + password = serializers.CharField(write_only=True) \ No newline at end of file diff --git a/MisplaceAI/user_dashboard/urls.py b/MisplaceAI/user_dashboard/urls.py index 93f9170473eca27b05a49743362385633c4d9636..b40dfe45ca97da52dd2d2ab32ddac2e1038cb9e9 100644 --- a/MisplaceAI/user_dashboard/urls.py +++ b/MisplaceAI/user_dashboard/urls.py @@ -1,7 +1,10 @@ # MisplaceAI/user_dashboard/urls.py from django.urls import path -from .views import UserDashboardView +from .views import UserDashboardView, UserUpdateView, UpdateEmailView urlpatterns = [ path('dashboard/', UserDashboardView.as_view(), name='user-dashboard'), + path('dashboard/update/', UserUpdateView.as_view(), name='user-update'), + path('dashboard/update_email/', UpdateEmailView.as_view(), name='user-update-email'), + ] diff --git a/MisplaceAI/user_dashboard/views.py b/MisplaceAI/user_dashboard/views.py index b428d17abd77958cd77cb373b06a6da89178afae..aa7f556a6b54adf37879edd8bae44b8f9b9d7b0b 100644 --- a/MisplaceAI/user_dashboard/views.py +++ b/MisplaceAI/user_dashboard/views.py @@ -2,7 +2,11 @@ from django.contrib.auth.models import User from rest_framework import generics from rest_framework.permissions import IsAuthenticated -from .serializers import UserSerializer +from .serializers import UserSerializer, UserUpdateSerializer, UserUpdateEmailSerializer +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from django.contrib.auth import authenticate class UserDashboardView(generics.RetrieveAPIView): queryset = User.objects.all() @@ -10,6 +14,32 @@ class UserDashboardView(generics.RetrieveAPIView): permission_classes = [IsAuthenticated] def get_object(self): - user = self.request.user - print(f'User: {user}') # Debugging statement - return user + return self.request.user + +class UserUpdateView(generics.UpdateAPIView): + queryset = User.objects.all() + serializer_class = UserUpdateSerializer + permission_classes = [IsAuthenticated] + + def get_object(self): + return self.request.user + +class UpdateEmailView(APIView): + permission_classes = [IsAuthenticated] + + def put(self, request, *args, **kwargs): + user = request.user + serializer = UserUpdateEmailSerializer(data=request.data) + + if serializer.is_valid(): + new_email = serializer.validated_data.get('email') + password = serializer.validated_data.get('password') + + if not user.check_password(password): + return Response({'error': 'Password is incorrect'}, status=status.HTTP_400_BAD_REQUEST) + + user.email = new_email + user.save() + return Response({'message': 'Email updated successfully'}, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/frontend/public/index.html b/frontend/public/index.html index 9d3b8fe244e91bcd4b7cb9d60aee55c0118b2014..41546fa91aacf3957e1502372c625ffd07b9ed6c 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -6,6 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MisplaceAI</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> </head> <body> diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index 096b86f5ec31175ae6f9fb76da67f94e6f4eeb53..0000000000000000000000000000000000000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,66 +0,0 @@ -/* Example global styles - ensure they don't conflict with the Navbar */ -body { - font-family: Arial, sans-serif; -} - -.navbar .navbar-collapse { - justify-content: flex-end; -} - -.auth-container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #f8f9fa; -} - -.auth-form { - background-color: #ffffff; - padding: 2rem; - border-radius: 5px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - width: 100%; - max-width: 400px; -} - -.auth-form h2 { - margin-bottom: 1.5rem; -} - -.auth-form .form-group { - margin-bottom: 1rem; -} - -.auth-form .form-control { - height: 45px; - padding: 10px; - font-size: 1rem; -} - -.auth-form .btn { - height: 45px; - font-size: 1rem; -} - -.auth-form .text-center { - margin-top: 1rem; -} - -.admin-container { - padding: 2rem; -} - -.admin-container h1 { - margin-bottom: 2rem; -} - -.admin-container .list-group { - max-width: 600px; - margin: 0 auto; -} - -.table-striped th, -.table-striped td { - text-align: center; -} \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index c472e5b4419867e7530f1cf0052c0691c238022e..b2783fb88a4da604cd4933f78856a9a8485c29c7 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -14,11 +14,12 @@ import ManageRulesPage from './pages/Rules/ManageRulesPage'; import DetectionOptionsPage from './pages/DetectionOptions/DetectionOptionsPage'; import NormalDetectionPage from './pages/NormalDetection/NormalDetectionPage'; import UserDashboard from './pages/UserDashboard/UserDashboard'; +import UserProfile from './pages/UserProfile/UserProfile'; import ProtectedRoute from './firewall/ProtectedRoute'; import RouteProtection from './firewall/RouteProtection'; -import './App.css'; + function App() { return ( @@ -77,6 +78,12 @@ function App() { <UserDashboard /> </ProtectedRoute> } /> + <Route path="/user/profile" element={ + <ProtectedRoute> + <UserProfile /> + </ProtectedRoute> + } /> + <Route path="*" element={<HomePage />} /> {/* Catch-all route */} </Routes> </div> diff --git a/frontend/src/components/Common/Form/ErrorMessage.js b/frontend/src/components/Common/Form/ErrorMessage.js index 8e094efefb9c64599669e11d6906747e88e54597..1c08c41bfb1612af621321ba51a39e5458091bb0 100644 --- a/frontend/src/components/Common/Form/ErrorMessage.js +++ b/frontend/src/components/Common/Form/ErrorMessage.js @@ -1,4 +1,4 @@ -// src/components/Common/ErrorMessage.js +// src/components/Common/Form/ErrorMessage.js import React from 'react'; import PropTypes from 'prop-types'; diff --git a/frontend/src/components/Common/Form/FormContainer.js b/frontend/src/components/Common/Form/FormContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..9d2e12ce383cac02a11d9dfd64775531f0244809 --- /dev/null +++ b/frontend/src/components/Common/Form/FormContainer.js @@ -0,0 +1,34 @@ +// src/components/Common/Form/FormContainer.js +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import ErrorMessage from './ErrorMessage'; + +const FormContainer = ({ children, onSubmit }) => { + const [error, setError] = useState(''); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + try { + await onSubmit(e); // Pass the event object to the onSubmit function + } catch (err) { + setError(err.message || 'An error occurred'); + } + }; + + return ( + <div className="auth-form"> + {error && <ErrorMessage message={error} />} + <form onSubmit={handleSubmit}> + {children} + </form> + </div> + ); +}; + +FormContainer.propTypes = { + children: PropTypes.node.isRequired, + onSubmit: PropTypes.func.isRequired, +}; + +export default FormContainer; diff --git a/frontend/src/components/Common/Form/FormField.js b/frontend/src/components/Common/Form/FormField.js new file mode 100644 index 0000000000000000000000000000000000000000..a79c2c2a61b08d46c684b41f15fa44fee4d7128f --- /dev/null +++ b/frontend/src/components/Common/Form/FormField.js @@ -0,0 +1,57 @@ +// src/components/Common/Form/FormField.js +import React from 'react'; +import PropTypes from 'prop-types'; + +const FormField = ({ label, type, value, onChange, required, placeholder, options, multiple }) => ( + <div className="form-group"> + <label>{label}</label> + {type === 'select' ? ( + <select + className="form-control" + value={value} + onChange={onChange} + required={required} + multiple={multiple} + > + <option value="" disabled>{placeholder}</option> + {options.map((option) => ( + <option key={option.value} value={option.value}> + {option.label} + </option> + ))} + </select> + ) : ( + <input + type={type} + className="form-control" + value={value} + onChange={onChange} + required={required} + placeholder={placeholder} + /> + )} + </div> +); + +FormField.propTypes = { + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + onChange: PropTypes.func.isRequired, + required: PropTypes.bool, + placeholder: PropTypes.string, + options: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + label: PropTypes.string.isRequired, + })), + multiple: PropTypes.bool, +}; + +FormField.defaultProps = { + required: false, + placeholder: '', + options: [], + multiple: false, +}; + +export default FormField; diff --git a/frontend/src/components/Common/Form/InputField.js b/frontend/src/components/Common/Form/InputField.js index a4e4a62a290aed38e1f8c3fd93eaccbb20658cbc..61dbb3aeefff43e41a18909b92f4133cd5be0baf 100644 --- a/frontend/src/components/Common/Form/InputField.js +++ b/frontend/src/components/Common/Form/InputField.js @@ -1,4 +1,4 @@ -// src/components/Common/InputField.js +// src/components/Common/Form/InputField.js import React from 'react'; import PropTypes from 'prop-types'; diff --git a/frontend/src/components/Common/Form/SubmitButton.js b/frontend/src/components/Common/Form/SubmitButton.js index 1a41a5f7e9e82fac8fbc4a874faca617cbd36404..781add9d1fbd9f0ae69d2d8d2aad77edb1f84367 100644 --- a/frontend/src/components/Common/Form/SubmitButton.js +++ b/frontend/src/components/Common/Form/SubmitButton.js @@ -1,13 +1,18 @@ -// src/components/Common/SubmitButton.js +// src/components/Common/Form/SubmitButton.js import React from 'react'; import PropTypes from 'prop-types'; -const SubmitButton = ({ label }) => ( - <button type="submit" className="btn btn-primary btn-block">{label}</button> +const SubmitButton = ({ label, disabled }) => ( + <button type="submit" className="btn btn-primary btn-block" disabled={disabled}>{label}</button> ); SubmitButton.propTypes = { - label: PropTypes.string.isRequired + label: PropTypes.string.isRequired, + disabled: PropTypes.bool, +}; + +SubmitButton.defaultProps = { + disabled: false, }; export default SubmitButton; diff --git a/frontend/src/components/Common/Password/PasswordInputField.js b/frontend/src/components/Common/Password/PasswordInputField.js index dafb4455496ae6a35e2d5d6beb80bcf3e5ac5937..f8c6607ba57848fddf5dddca6874e02974914e6c 100644 --- a/frontend/src/components/Common/Password/PasswordInputField.js +++ b/frontend/src/components/Common/Password/PasswordInputField.js @@ -1,9 +1,11 @@ -// src/components/Common/PasswordInputField.js +// src/components/Common/Password/PasswordInputField.js + import React, { useState } from 'react'; import PropTypes from 'prop-types'; import PasswordRequirements from './PasswordRequirements'; +import '../../../styles/main.css'; -const PasswordInputField = ({ label, value, onChange, requirements, showRequirements }) => { +const PasswordInputField = ({ label, value, onChange, showRequirements }) => { const [showPassword, setShowPassword] = useState(false); const togglePasswordVisibility = () => { @@ -13,19 +15,24 @@ const PasswordInputField = ({ label, value, onChange, requirements, showRequirem return ( <div className="form-group position-relative"> <label>{label}</label> - <input - type={showPassword ? 'text' : 'password'} - className="form-control" - value={value} - onChange={onChange} - required - /> - <span - className="toggle-password" - onClick={togglePasswordVisibility} - > - {showPassword ? 'Hide' : 'Show'} - </span> + <div className="input-group"> + <input + type={showPassword ? 'text' : 'password'} + className="form-control" + value={value} + onChange={onChange} + required + /> + <div className="input-group-append"> + <span + className="input-group-text" + onClick={togglePasswordVisibility} + title={showPassword ? "Hide Password" : "Show Password"} + > + <i className={`fas fa-eye${showPassword ? '' : '-slash'}`}></i> + </span> + </div> + </div> {showRequirements && <PasswordRequirements password={value} />} </div> ); @@ -35,8 +42,11 @@ PasswordInputField.propTypes = { label: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, - requirements: PropTypes.object, showRequirements: PropTypes.bool, }; +PasswordInputField.defaultProps = { + showRequirements: false, +}; + export default PasswordInputField; diff --git a/frontend/src/components/Common/Password/PasswordRequirements.js b/frontend/src/components/Common/Password/PasswordRequirements.js index d6303a0774ec46d5374e8127285af7b9760422be..b14177f86b406e72d70a378ba247dfff95ba28b6 100644 --- a/frontend/src/components/Common/Password/PasswordRequirements.js +++ b/frontend/src/components/Common/Password/PasswordRequirements.js @@ -1,4 +1,4 @@ -// src/components/Common/PasswordRequirements.js +// src/components/Common/Password/PasswordRequirements.js import React from 'react'; import PropTypes from 'prop-types'; diff --git a/frontend/src/components/Items/AddItem.css b/frontend/src/components/Items/AddItem.css deleted file mode 100644 index 8a74316c2c5f79b5c7eb673d7e5c5c5571b41d61..0000000000000000000000000000000000000000 --- a/frontend/src/components/Items/AddItem.css +++ /dev/null @@ -1,4 +0,0 @@ -/* src/components/Items/AddItem.css */ -.add-item-form { - margin-bottom: 20px; -} \ No newline at end of file diff --git a/frontend/src/components/Items/AddItem.js b/frontend/src/components/Items/AddItem.js index 0ca226081f09f27f441c0df700e5fe6ae79e4184..df63b6f278190978cfdeea90722ebf96276c8cc3 100644 --- a/frontend/src/components/Items/AddItem.js +++ b/frontend/src/components/Items/AddItem.js @@ -1,7 +1,7 @@ // src/components/Items/AddItem.js import React, { useState } from 'react'; import { addItem } from '../../services/itemApi'; -import './AddItem.css'; +import '../../styles/main.css'; const AddItem = ({ onItemAdded }) => { const [itemName, setItemName] = useState(''); diff --git a/frontend/src/components/Items/GetItems.css b/frontend/src/components/Items/GetItems.css deleted file mode 100644 index c525ea1d38a013eab34baf4abc0a3bfaf78a09a8..0000000000000000000000000000000000000000 --- a/frontend/src/components/Items/GetItems.css +++ /dev/null @@ -1,6 +0,0 @@ -/* src/components/Items/GetItems.css */ -.list-group-item { - display: flex; - justify-content: space-between; - align-items: center; -} \ No newline at end of file diff --git a/frontend/src/components/Items/GetItems.js b/frontend/src/components/Items/GetItems.js index 8cbfbb3731e39fe40b541613c76ea30e10baf4b9..075a032af5076b18b9566f0173f6b1062a04be85 100644 --- a/frontend/src/components/Items/GetItems.js +++ b/frontend/src/components/Items/GetItems.js @@ -1,7 +1,7 @@ // src/components/Items/GetItems.js import React, { useEffect, useState } from 'react'; import { getItems } from '../../services/itemApi'; -import './GetItems.css'; +import '../../styles/main.css'; const GetItems = ({ onEditItem, onDeleteItem, refresh }) => { const [items, setItems] = useState([]); diff --git a/frontend/src/components/Items/UpdateItem.css b/frontend/src/components/Items/UpdateItem.css deleted file mode 100644 index 57c2875d87adab04286aaff817865c4e727fc583..0000000000000000000000000000000000000000 --- a/frontend/src/components/Items/UpdateItem.css +++ /dev/null @@ -1,4 +0,0 @@ -/* src/components/Items/UpdateItem.css */ -.update-item-form { - margin-bottom: 20px; -} \ No newline at end of file diff --git a/frontend/src/components/Items/UpdateItem.js b/frontend/src/components/Items/UpdateItem.js index f9bdf17a9ef22e8bed37799104f5844cc4024091..de4739e07f2ede2eb47341cd18a33933d94ac58d 100644 --- a/frontend/src/components/Items/UpdateItem.js +++ b/frontend/src/components/Items/UpdateItem.js @@ -1,7 +1,7 @@ // src/components/Items/UpdateItem.js import React, { useState, useEffect } from 'react'; import { updateItem } from '../../services/itemApi'; -import './UpdateItem.css'; +import '../../styles/main.css'; const UpdateItem = ({ item, onUpdateCompleted }) => { const [itemName, setItemName] = useState(item.name); diff --git a/frontend/src/components/Locations/AddLocation.css b/frontend/src/components/Locations/AddLocation.css deleted file mode 100644 index 5c9906ea351d3647de002bdcf62939255b3e6420..0000000000000000000000000000000000000000 --- a/frontend/src/components/Locations/AddLocation.css +++ /dev/null @@ -1,13 +0,0 @@ -/* src/components/Locations/AddLocation.css */ - -.add-location-container { - margin-top: 20px; - padding: 20px; - border: 1px solid #ddd; - border-radius: 5px; - background-color: #f9f9f9; -} - -.add-location-container .form-group { - margin-bottom: 15px; -} \ No newline at end of file diff --git a/frontend/src/components/Locations/AddLocation.js b/frontend/src/components/Locations/AddLocation.js index 5e4f722ee732c0f340fc927cc8dada2bdb3ba3be..f392d9dd52955cbc4e3197d8b865fcdd9e6a8f75 100644 --- a/frontend/src/components/Locations/AddLocation.js +++ b/frontend/src/components/Locations/AddLocation.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { addLocation } from '../../services/locationApi'; -import './AddLocation.css'; +import '../../styles/main.css'; const AddLocation = ({ onLocationAdded }) => { const [locationName, setLocationName] = useState(''); diff --git a/frontend/src/components/Locations/GetLocations.css b/frontend/src/components/Locations/GetLocations.css deleted file mode 100644 index 1b866c479652c234b924bcdff6dd276267b05443..0000000000000000000000000000000000000000 --- a/frontend/src/components/Locations/GetLocations.css +++ /dev/null @@ -1,15 +0,0 @@ -/* src/components/Locations/GetLocations.css */ - -.locations-list { - margin-top: 20px; -} - -.list-group-item { - display: flex; - justify-content: space-between; - align-items: center; -} - -.btn { - margin-right: 5px; -} \ No newline at end of file diff --git a/frontend/src/components/Locations/GetLocations.js b/frontend/src/components/Locations/GetLocations.js index 919cb614653d7c77a90b36e84df2c8953ef0225a..e60a850a8e20cb1f5d7f0bc6f62d28222d57e6f0 100644 --- a/frontend/src/components/Locations/GetLocations.js +++ b/frontend/src/components/Locations/GetLocations.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { getLocations } from '../../services/locationApi'; -import './GetLocations.css'; +import '../../styles/main.css'; const GetLocations = ({ onEditLocation, onDeleteLocation, refresh }) => { const [locations, setLocations] = useState([]); diff --git a/frontend/src/components/Locations/UpdateLocation.css b/frontend/src/components/Locations/UpdateLocation.css deleted file mode 100644 index 98456041da5c49c6458eee710d974c2a0ed13741..0000000000000000000000000000000000000000 --- a/frontend/src/components/Locations/UpdateLocation.css +++ /dev/null @@ -1,17 +0,0 @@ -/* src/components/Locations/UpdateLocation.css */ - -.update-location-container { - margin-top: 20px; - padding: 20px; - border: 1px solid #ddd; - border-radius: 5px; - background-color: #f9f9f9; -} - -.update-location-container .form-group { - margin-bottom: 15px; -} - -.update-location-container .error { - color: red; -} \ No newline at end of file diff --git a/frontend/src/components/Locations/UpdateLocation.js b/frontend/src/components/Locations/UpdateLocation.js index 71e5a96c866506071da00c386ebc74da90f4a567..acfafe8fe5b2f0f866fbde842fd24bf353a0453f 100644 --- a/frontend/src/components/Locations/UpdateLocation.js +++ b/frontend/src/components/Locations/UpdateLocation.js @@ -1,7 +1,7 @@ // src/components/Locations/UpdateLocation.js import React, { useState, useEffect } from 'react'; import { updateLocation } from '../../services/locationApi'; -import './UpdateLocation.css'; +import '../../styles/main.css'; const UpdateLocation = ({ location, onUpdateCompleted }) => { const [locationName, setLocationName] = useState(location.name); diff --git a/frontend/src/components/Rules/AddRule.js b/frontend/src/components/Rules/AddRule.js index 3e214372b29c299a7087024142505902e7f00b5c..ffa4932707743a5e2dd65d97f009ec4b74c5097a 100644 --- a/frontend/src/components/Rules/AddRule.js +++ b/frontend/src/components/Rules/AddRule.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { addRule } from '../../services/ruleApi'; import { getItems } from '../../services/itemApi'; import { getLocations } from '../../services/locationApi'; -import './AddRule.css'; +import '../../styles/main.css'; const AddRule = ({ onRuleAdded }) => { const [items, setItems] = useState([]); diff --git a/frontend/src/components/Rules/GetRules.js b/frontend/src/components/Rules/GetRules.js index bb7bd3f27437959e6848d6b068d1dc8287a14931..3759599ee34c302f538a2848d045451735fc5a01 100644 --- a/frontend/src/components/Rules/GetRules.js +++ b/frontend/src/components/Rules/GetRules.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { getRules, deleteRule } from '../../services/ruleApi'; -import './GetRules.css'; +import '../../styles/main.css'; const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { const [rules, setRules] = useState([]); diff --git a/frontend/src/components/Rules/UpdateRule.js b/frontend/src/components/Rules/UpdateRule.js index 1833d16e67eb0344004fba163112864271aaa166..18bcd7617c1f83c266a6649e3951d5704bf7086f 100644 --- a/frontend/src/components/Rules/UpdateRule.js +++ b/frontend/src/components/Rules/UpdateRule.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { updateRule } from '../../services/ruleApi'; import { getItems } from '../../services/itemApi'; import { getLocations } from '../../services/locationApi'; -import './UpdateRule.css'; +import '../../styles/main.css'; const UpdateRule = ({ rule, onUpdateCompleted }) => { const [items, setItems] = useState([]); diff --git a/frontend/src/components/UserProfile/ChangeEmailSection.js b/frontend/src/components/UserProfile/ChangeEmailSection.js new file mode 100644 index 0000000000000000000000000000000000000000..50ffc25f784e2821920ad4555c62fce87e8d3b87 --- /dev/null +++ b/frontend/src/components/UserProfile/ChangeEmailSection.js @@ -0,0 +1,28 @@ +// src/components/UserProfile/ChangeEmailSection.js + +import React, { useState } from 'react'; +import ChangeEmailForm from '../../forms/UserProfile/ChangeEmailForm'; + +const ChangeEmailSection = ({ currentEmail, onUpdateEmail }) => { + const [showChangeEmailForm, setShowChangeEmailForm] = useState(false); + + return ( + <div> + <div className="card"> + <div className="card-body"> + <h3 className="card-title">Current Email</h3> + <p className="card-text">{currentEmail}</p> + <button className="btn btn-primary" onClick={() => setShowChangeEmailForm(true)}>Change Email</button> + </div> + </div> + {showChangeEmailForm && ( + <ChangeEmailForm + onSubmit={onUpdateEmail} + onCancel={() => setShowChangeEmailForm(false)} + /> + )} + </div> + ); +}; + +export default ChangeEmailSection; diff --git a/frontend/src/components/UserProfile/CurrentEmailCard.js b/frontend/src/components/UserProfile/CurrentEmailCard.js new file mode 100644 index 0000000000000000000000000000000000000000..7e6ea9aaa6a5534b26f579f3966d482497c1d2b0 --- /dev/null +++ b/frontend/src/components/UserProfile/CurrentEmailCard.js @@ -0,0 +1,16 @@ +// src/components/UserProfile/CurrentEmailCard.js +import React from 'react'; + +const CurrentEmailCard = ({ email, onChangeEmailClick }) => { + return ( + <div className="card mb-4"> + <div className="card-body"> + <h5 className="card-title">Current Email</h5> + <p className="card-text">{email}</p> + <button onClick={onChangeEmailClick} className="btn btn-primary">Change Email</button> + </div> + </div> + ); +}; + +export default CurrentEmailCard; diff --git a/frontend/src/forms/Auth/AdminLoginForm.js b/frontend/src/forms/Auth/AdminLoginForm.js index 4f6846210349460dc8310a6e7f0ca141aedb045a..4f94607697d77b59c854555558294f59195bd5e4 100644 --- a/frontend/src/forms/Auth/AdminLoginForm.js +++ b/frontend/src/forms/Auth/AdminLoginForm.js @@ -1,10 +1,12 @@ // src/forms/Auth/AdminLoginForm.js + import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import api from '../../services/api'; import InputField from '../../components/Common/Form/InputField'; import ErrorMessage from '../../components/Common/Form/ErrorMessage'; import SubmitButton from '../../components/Common/Form/SubmitButton'; +import '../../styles/main.css'; const AdminLoginForm = () => { const [username, setUsername] = useState(''); @@ -27,28 +29,30 @@ const AdminLoginForm = () => { }; return ( - <div className="auth-form"> - <h2 className="text-center">Admin Login</h2> - {error && <ErrorMessage message={error} />} - <form onSubmit={handleSubmit}> - <InputField - label="Username" - type="text" - value={username} - onChange={(e) => setUsername(e.target.value)} - required - /> - <InputField - label="Password" - type="password" - value={password} - onChange={(e) => setPassword(e.target.value)} - required - /> - <SubmitButton label="Login" /> - </form> + <div className="auth-container"> + <div className="auth-form"> + <h2 className="text-center">Admin Login</h2> + {error && <ErrorMessage message={error} />} + <form onSubmit={handleSubmit}> + <InputField + label="Username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + required + /> + <InputField + label="Password" + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + required + /> + <SubmitButton label="Login" /> + </form> + </div> </div> ); }; -export default AdminLoginForm; \ No newline at end of file +export default AdminLoginForm; diff --git a/frontend/src/forms/Auth/LoginForm.js b/frontend/src/forms/Auth/LoginForm.js index bb1be29b1e0f487ef37e1f75abcc46ee571ebcfa..cc9023e87a7210004ab1771a5520a7a3762abada 100644 --- a/frontend/src/forms/Auth/LoginForm.js +++ b/frontend/src/forms/Auth/LoginForm.js @@ -1,11 +1,14 @@ // src/forms/Auth/LoginForm.js + import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import api from '../../services/api'; -import InputField from '../../components/Common/Form/InputField'; -import ErrorMessage from '../../components/Common/Form/ErrorMessage'; -import SubmitButton from '../../components/Common/Form/SubmitButton'; +import FormContainer from '../../components/Common/Form/FormContainer'; +import FormField from '../../components/Common/Form/FormField'; import PasswordInputField from '../../components/Common/Password/PasswordInputField'; +import SubmitButton from '../../components/Common/Form/SubmitButton'; +import ErrorMessage from '../../components/Common/Form/ErrorMessage'; +import '../../styles/main.css'; const LoginForm = () => { const [username, setUsername] = useState(''); @@ -18,7 +21,7 @@ const LoginForm = () => { try { const response = await api.post('/api/auth/login/', { username, password }); localStorage.setItem('token', response.data.access); - localStorage.setItem('isAuthenticated', true); // Set the authentication flag + localStorage.setItem('isAuthenticated', true); navigate('/'); window.location.reload(); // Refresh to update Navbar } catch (error) { @@ -27,11 +30,11 @@ const LoginForm = () => { }; return ( - <div className="auth-form"> - <h2 className="text-center">Login</h2> - {error && <ErrorMessage message={error} />} - <form onSubmit={handleSubmit}> - <InputField + <div className="auth-container"> + <FormContainer onSubmit={handleSubmit}> + <h2 className="text-center responsive-text">Login</h2> + {error && <ErrorMessage message={error} />} + <FormField label="Username" type="text" value={username} @@ -44,7 +47,7 @@ const LoginForm = () => { onChange={(e) => setPassword(e.target.value)} /> <SubmitButton label="Login" /> - </form> + </FormContainer> </div> ); }; diff --git a/frontend/src/forms/Auth/RegisterForm.js b/frontend/src/forms/Auth/RegisterForm.js index f6dbb7027d2c62ff55f71c938ab0048875973229..e1e56137dd080f851a634c113e79c67a6d046bbd 100644 --- a/frontend/src/forms/Auth/RegisterForm.js +++ b/frontend/src/forms/Auth/RegisterForm.js @@ -1,4 +1,5 @@ // src/forms/Auth/RegisterForm.js + import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import api from '../../services/api'; @@ -6,6 +7,7 @@ import InputField from '../../components/Common/Form/InputField'; import ErrorMessage from '../../components/Common/Form/ErrorMessage'; import SubmitButton from '../../components/Common/Form/SubmitButton'; import PasswordInputField from '../../components/Common/Password/PasswordInputField'; +import '../../styles/main.css'; const RegisterForm = () => { const [username, setUsername] = useState(''); @@ -88,40 +90,42 @@ const RegisterForm = () => { }; return ( - <div className="auth-form"> - <h2 className="text-center">Register</h2> - {error && <ErrorMessage message={error} />} - <form onSubmit={handleSubmit}> - <InputField - label="Username" - type="text" - value={username} - onChange={(e) => setUsername(e.target.value)} - required - /> - <InputField - label="Email" - type="email" - value={email} - onChange={(e) => setEmail(e.target.value)} - required - /> - <PasswordInputField - label="Password" - value={password} - onChange={(e) => setPassword(e.target.value)} - showRequirements={true} - /> - <PasswordInputField - label="Confirm Password" - value={password2} - onChange={(e) => setPassword2(e.target.value)} - /> - <small id="passwordMatchMessage" className={`form-text ${passwordMatchMessage.includes('match') ? 'text-success' : 'text-danger'}`}> - {passwordMatchMessage} - </small> - <SubmitButton label="Register" disabled={!isFormValid} /> - </form> + <div className="auth-container"> + <div className="auth-form"> + <h2 className="text-center">Register</h2> + {error && <ErrorMessage message={error} />} + <form onSubmit={handleSubmit}> + <InputField + label="Username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + required + /> + <InputField + label="Email" + type="email" + value={email} + onChange={(e) => setEmail(e.target.value)} + required + /> + <PasswordInputField + label="Password" + value={password} + onChange={(e) => setPassword(e.target.value)} + showRequirements={true} + /> + <PasswordInputField + label="Confirm Password" + value={password2} + onChange={(e) => setPassword2(e.target.value)} + /> + <small id="passwordMatchMessage" className={`form-text ${passwordMatchMessage.includes('match') ? 'text-success' : 'text-danger'}`}> + {passwordMatchMessage} + </small> + <SubmitButton label="Register" disabled={!isFormValid} /> + </form> + </div> </div> ); }; diff --git a/frontend/src/forms/Items/ItemForm.js b/frontend/src/forms/Items/ItemForm.js index 8b21caf93ff7c8a57b4b0b274e9c384343c161cd..aaefbd0c7978e23b091c907a8ca7fad6948ccbcd 100644 --- a/frontend/src/forms/Items/ItemForm.js +++ b/frontend/src/forms/Items/ItemForm.js @@ -1,20 +1,22 @@ // src/forms/Items/ItemForm.js + import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import InputField from '../../components/Common/Form/InputField'; +import FormContainer from '../../components/Common/Form/FormContainer'; +import FormField from '../../components/Common/Form/FormField'; import SubmitButton from '../../components/Common/Form/SubmitButton'; +import '../../styles/main.css'; const ItemForm = ({ initialData = {}, onSubmit }) => { const [name, setName] = useState(initialData.name || ''); - const handleSubmit = (e) => { - e.preventDefault(); + const handleSubmit = () => { onSubmit({ name }); }; return ( - <form onSubmit={handleSubmit}> - <InputField + <FormContainer onSubmit={handleSubmit}> + <FormField label="Name" type="text" value={name} @@ -22,7 +24,7 @@ const ItemForm = ({ initialData = {}, onSubmit }) => { required /> <SubmitButton label="Submit" /> - </form> + </FormContainer> ); }; diff --git a/frontend/src/forms/UserProfile/ChangeEmailForm.js b/frontend/src/forms/UserProfile/ChangeEmailForm.js new file mode 100644 index 0000000000000000000000000000000000000000..85249754daf86f281e03697ed9bbe3918f30c65c --- /dev/null +++ b/frontend/src/forms/UserProfile/ChangeEmailForm.js @@ -0,0 +1,46 @@ +// src/forms/UserProfile/ChangeEmailForm.js + +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import FormContainer from '../../components/Common/Form/FormContainer'; +import FormField from '../../components/Common/Form/FormField'; +import SubmitButton from '../../components/Common/Form/SubmitButton'; +import PasswordInputField from '../../components/Common/Password/PasswordInputField'; +import '../../styles/main.css'; + +const ChangeEmailForm = ({ onSubmit, onCancel }) => { + const [newEmail, setNewEmail] = useState(''); + const [password, setPassword] = useState(''); + + const handleSubmit = () => { + onSubmit({ newEmail, password }); + setNewEmail(''); + setPassword(''); + }; + + return ( + <FormContainer onSubmit={handleSubmit}> + <FormField + label="New Email" + type="email" + value={newEmail} + onChange={(e) => setNewEmail(e.target.value)} + required + /> + <PasswordInputField + label="Password" + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + <SubmitButton label="Update Email" /> + <button type="button" className="btn btn-secondary" onClick={onCancel}>Cancel</button> + </FormContainer> + ); +}; + +ChangeEmailForm.propTypes = { + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + +export default ChangeEmailForm; diff --git a/frontend/src/index.js b/frontend/src/index.js index 2c349a9fe401f502c8eba981f7db7deb00b62d9f..5ad68b7076c7dee13bb4e353cd0a2e943df8814b 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -2,9 +2,11 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import './styles/main.css'; + + ReactDOM.render( <React.StrictMode> diff --git a/frontend/src/layouts/Navbar/Navbar.js b/frontend/src/layouts/Navbar/Navbar.js index 749ee3976bf29d57583d15660fb5162fdfa39c4a..c68b43a66711a986f1e113cd78c8318f74491c2f 100644 --- a/frontend/src/layouts/Navbar/Navbar.js +++ b/frontend/src/layouts/Navbar/Navbar.js @@ -1,7 +1,7 @@ // src/layouts/Navbar/Navbar.js import React from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import './Navbar.css'; +import '../../styles/main.css'; const Navbar = () => { const navigate = useNavigate(); @@ -30,7 +30,7 @@ const Navbar = () => { <li className="nav-item"> <Link className="nav-link" to="/">Home</Link> </li> - {isAuthenticated && ( + {isAuthenticated && !isAdmin && ( <> <li className="nav-item"> <Link className="nav-link" to="/detection-options">Detect Misplaced Items</Link> @@ -50,7 +50,7 @@ const Navbar = () => { </li> </> )} - {isAuthenticated && !isAdmin && ( + {isAuthenticated && ( <li className="nav-item"> <button className="nav-link btn btn-link" onClick={handleLogout}>Logout</button> </li> @@ -60,9 +60,6 @@ const Navbar = () => { <li className="nav-item"> <Link className="nav-link" to="/admin/dashboard">Admin Dashboard</Link> </li> - <li className="nav-item"> - <button className="nav-link btn btn-link" onClick={handleLogout}>Logout</button> - </li> </> )} </ul> diff --git a/frontend/src/pages/Admin/Admin.css b/frontend/src/pages/Admin/Admin.css deleted file mode 100644 index 036fb771e97ade43479889d1c731270f05b38fcf..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Admin/Admin.css +++ /dev/null @@ -1,59 +0,0 @@ -/* src/pages/Admin/Admin.css */ - -.auth-container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #f8f9fa; -} - -.auth-form { - background-color: #ffffff; - padding: 2rem; - border-radius: 5px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - width: 100%; - max-width: 400px; -} - -.auth-form h2 { - margin-bottom: 1.5rem; -} - -.auth-form .form-group { - margin-bottom: 1rem; -} - -.auth-form .form-control { - height: 45px; - padding: 10px; - font-size: 1rem; -} - -.auth-form .btn { - height: 45px; - font-size: 1rem; -} - -.auth-form .text-center { - margin-top: 1rem; -} - -.admin-container { - padding: 2rem; -} - -.admin-container h1 { - margin-bottom: 2rem; -} - -.admin-container .list-group { - max-width: 600px; - margin: 0 auto; -} - -.table-striped th, -.table-striped td { - text-align: center; -} \ No newline at end of file diff --git a/frontend/src/pages/Admin/AdminDashboard.js b/frontend/src/pages/Admin/AdminDashboard.js index e45946d5a96335c0430aee406f74a07da5fef742..76d65f32b8f708d04956df7150f5d2798c418f93 100644 --- a/frontend/src/pages/Admin/AdminDashboard.js +++ b/frontend/src/pages/Admin/AdminDashboard.js @@ -2,7 +2,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import './Admin.css'; // Import the CSS file + const AdminDashboard = () => { return ( diff --git a/frontend/src/pages/Admin/AdminLoginPage.js b/frontend/src/pages/Admin/AdminLoginPage.js index 48a562eff269d7bcf4e907383f738978aa25d649..e8c0212b5e8f0ebd48163a9e5eb22f6b1f0900a4 100644 --- a/frontend/src/pages/Admin/AdminLoginPage.js +++ b/frontend/src/pages/Admin/AdminLoginPage.js @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import AdminLoginForm from '../../forms/Auth/AdminLoginForm'; -import './Admin.css'; + const AdminLoginPage = () => { const navigate = useNavigate(); diff --git a/frontend/src/pages/Admin/AdminUsers.js b/frontend/src/pages/Admin/AdminUsers.js index 6de0c656acd16e69e2f501e83927ac1bcf27bd67..e904663d4815c0995e024193f829f8fbcbaf8fd8 100644 --- a/frontend/src/pages/Admin/AdminUsers.js +++ b/frontend/src/pages/Admin/AdminUsers.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import api from '../../services/api'; -import './Admin.css'; // Import the CSS file + const AdminUsers = () => { const [users, setUsers] = useState([]); diff --git a/frontend/src/pages/Auth/Auth.css b/frontend/src/pages/Auth/Auth.css deleted file mode 100644 index 19dd76e4dcf2e26336a705cfa541d154f1426f9b..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Auth/Auth.css +++ /dev/null @@ -1,59 +0,0 @@ -.auth-container { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #f8f9fa; - padding: 20px; -} - -.auth-form { - background: #fff; - padding: 30px; - border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - max-width: 400px; - width: 100%; -} - -.auth-form h2 { - margin-bottom: 20px; -} - -.form-group { - margin-bottom: 15px; -} - -.form-control { - border-radius: 5px; - padding: 10px; -} - -.btn-block { - width: 100%; - padding: 10px; - border-radius: 5px; -} - -.toggle-password { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - cursor: pointer; - color: #007bff; -} - -.password-requirement { - margin-top: 5px; -} - -@media (max-width: 768px) { - .auth-container { - padding: 10px; - } - - .auth-form { - padding: 20px; - } -} \ No newline at end of file diff --git a/frontend/src/pages/Home/Home.css b/frontend/src/pages/Home/Home.css deleted file mode 100644 index cb437ffc15649c36439549a079a93060f89accfa..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Home/Home.css +++ /dev/null @@ -1,29 +0,0 @@ -.home-container { - padding: 4rem 2rem; - background-color: #f8f9fa; - min-height: calc(100vh - 56px); - /* Adjust for navbar height */ - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.home-container h1 { - font-size: 3rem; - margin-bottom: 1rem; - color: #343a40; -} - -.home-container p { - font-size: 1.2rem; - color: #6c757d; - max-width: 600px; - text-align: center; -} - -.btn-primary { - background-color: #007bff; - border-color: #007bff; - margin-top: 1rem; -} \ No newline at end of file diff --git a/frontend/src/pages/Home/Home.js b/frontend/src/pages/Home/Home.js index 2f76531040d4a68b217094466ea7fea419c8c1e9..55d02615d552718817df380dfb11b9ddab85d852 100644 --- a/frontend/src/pages/Home/Home.js +++ b/frontend/src/pages/Home/Home.js @@ -1,13 +1,18 @@ // src/pages/Home/Home.js + import React from 'react'; -import './Home.css'; +import '../../styles/main.css'; const Home = () => { return ( - <div className="container home-container"> - <h1>Welcome to MisplaceAI</h1> - <p>AThis is the home page of the MisplaceAI application. Use this tool to detect and manage misplaced items efficiently.</p> - <a href="/detection-options" className="btn btn-primary">Get Started</a> + <div className="container"> + <div className="card"> + <div className="card-body"> + <h1 className="card-title">Welcome to MisplaceAI</h1> + <p className="card-text">This is the home page of the MisplaceAI application. Use this tool to detect and manage misplaced items efficiently.</p> + <a href="/detection-options" className="btn btn-primary">Get Started</a> + </div> + </div> </div> ); }; diff --git a/frontend/src/pages/Items/ManageItemsPage.css b/frontend/src/pages/Items/ManageItemsPage.css deleted file mode 100644 index ec4e12ea13a08c0199422f0b4d6b0d89cba871cf..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Items/ManageItemsPage.css +++ /dev/null @@ -1,13 +0,0 @@ -/* src/pages/Items/ManageItemsPage.css */ -.manage-items-container { - max-width: 800px; - margin: auto; -} - -.card { - margin-bottom: 20px; -} - -.card-header h3 { - margin: 0; -} \ No newline at end of file diff --git a/frontend/src/pages/Items/ManageItemsPage.js b/frontend/src/pages/Items/ManageItemsPage.js index 5ffb0cb6bc9b9330dc824f575a7395cb04c70ab1..edc983f0b55ceced00eb255c9709331ac300ad35 100644 --- a/frontend/src/pages/Items/ManageItemsPage.js +++ b/frontend/src/pages/Items/ManageItemsPage.js @@ -4,7 +4,7 @@ import AddItem from '../../components/Items/AddItem'; import GetItems from '../../components/Items/GetItems'; import UpdateItem from '../../components/Items/UpdateItem'; import { deleteItem } from '../../services/itemApi'; -import './ManageItemsPage.css'; +import '../../styles/main.css'; const ManageItemsPage = () => { const [editingItem, setEditingItem] = useState(null); diff --git a/frontend/src/pages/Locations/ManageLocationsPage.css b/frontend/src/pages/Locations/ManageLocationsPage.css deleted file mode 100644 index 5839587e886bb5e218daeb52e85acd7a01755093..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Locations/ManageLocationsPage.css +++ /dev/null @@ -1,17 +0,0 @@ -/* src/pages/Locations/ManageLocationsPage.css */ - -.manage-locations-container { - max-width: 800px; - margin: 0 auto; - padding: 20px; -} - -.manage-locations-container h1 { - margin-bottom: 20px; - font-size: 2.5rem; -} - -.manage-locations-container h2 { - margin-bottom: 15px; - font-size: 2rem; -} \ No newline at end of file diff --git a/frontend/src/pages/Locations/ManageLocationsPage.js b/frontend/src/pages/Locations/ManageLocationsPage.js index 7c71bf52c70820eca306ea803cd595bf0307bdec..04e0bfc27f6b080f5d54fea40c9fa2a90359616e 100644 --- a/frontend/src/pages/Locations/ManageLocationsPage.js +++ b/frontend/src/pages/Locations/ManageLocationsPage.js @@ -3,7 +3,7 @@ import AddLocation from '../../components/Locations/AddLocation'; import GetLocations from '../../components/Locations/GetLocations'; import UpdateLocation from '../../components/Locations/UpdateLocation'; import { deleteLocation } from '../../services/locationApi'; -import './ManageLocationsPage.css'; +import '../../styles/main.css'; const ManageLocationsPage = () => { const [editingLocation, setEditingLocation] = useState(null); diff --git a/frontend/src/pages/NormalDetection/NormalDetectionPage.css b/frontend/src/pages/NormalDetection/NormalDetectionPage.css deleted file mode 100644 index e1107d7f20fbeb7fe30146166af29966fa3c4ae8..0000000000000000000000000000000000000000 --- a/frontend/src/pages/NormalDetection/NormalDetectionPage.css +++ /dev/null @@ -1,27 +0,0 @@ -/* src/pages/NormalDetection/NormalDetectionPage.css */ - -.card { - margin: auto; -} - -@media (max-width: 576px) { - .card-body { - padding: 1rem; - } - - .btn-lg { - font-size: 1rem; - padding: 0.5rem 1rem; - } -} - -@media (min-width: 1200px) { - .card-body { - padding: 3rem; - } - - .btn-lg { - font-size: 1.25rem; - padding: 0.75rem 1.5rem; - } -} \ No newline at end of file diff --git a/frontend/src/pages/NormalDetection/NormalDetectionPage.js b/frontend/src/pages/NormalDetection/NormalDetectionPage.js index f1c3a4000f603fae0a2ac51f309ce449f64e558a..0a46aaf88a4f2132555dfe29b2c89dff97551e1e 100644 --- a/frontend/src/pages/NormalDetection/NormalDetectionPage.js +++ b/frontend/src/pages/NormalDetection/NormalDetectionPage.js @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { Modal, Button } from 'react-bootstrap'; import { normalDetection } from '../../services/processMisplacedManagerApi'; -import './NormalDetectionPage.css'; +import '../../styles/main.css'; import loadingGif from '../../assets/loading.gif'; // Import your downloaded GIF const NormalDetectionPage = () => { diff --git a/frontend/src/pages/Rules/ManageRulesPage.css b/frontend/src/pages/Rules/ManageRulesPage.css deleted file mode 100644 index 27d05b7588987da0392487dc2728588f794438c8..0000000000000000000000000000000000000000 --- a/frontend/src/pages/Rules/ManageRulesPage.css +++ /dev/null @@ -1,15 +0,0 @@ -.manage-rules-page { - padding: 20px; - background-color: #fff; - border-radius: 5px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - margin-top: 20px; -} - -.manage-rules-page h1 { - margin-bottom: 20px; -} - -.manage-rules-page .form-group { - margin-bottom: 15px; -} \ No newline at end of file diff --git a/frontend/src/pages/Rules/ManageRulesPage.js b/frontend/src/pages/Rules/ManageRulesPage.js index fab2fa579a1d15ee67e32d66d0f1ddb08ab079fe..4699e9d1598fb08f2e2e0c97e671f61d49b3727b 100644 --- a/frontend/src/pages/Rules/ManageRulesPage.js +++ b/frontend/src/pages/Rules/ManageRulesPage.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import AddRule from '../../components/Rules/AddRule'; import GetRules from '../../components/Rules/GetRules'; import UpdateRule from '../../components/Rules/UpdateRule'; -import './ManageRulesPage.css'; +import '../../styles/main.css'; const ManageRulesPage = () => { const [editingRule, setEditingRule] = useState(null); diff --git a/frontend/src/pages/UserDashboard/UserDashboard.css b/frontend/src/pages/UserDashboard/UserDashboard.css deleted file mode 100644 index e3fcffc2499b01335a89d1e0f35dbf9f8e1632d2..0000000000000000000000000000000000000000 --- a/frontend/src/pages/UserDashboard/UserDashboard.css +++ /dev/null @@ -1,17 +0,0 @@ -/* src/pages/UserDashboard/UserDashboard.css */ - -.user-dashboard { - padding: 20px; - background-color: #f9f9f9; - border-radius: 8px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); -} - -.user-dashboard h1 { - margin-bottom: 20px; -} - -.user-dashboard .user-info p { - margin: 10px 0; - font-size: 16px; -} \ No newline at end of file diff --git a/frontend/src/pages/UserDashboard/UserDashboard.js b/frontend/src/pages/UserDashboard/UserDashboard.js index 94af5999124065ca6ed5d7638d881e99ef34868a..38fe6379f8254cc05074deabdb953832f7f84672 100644 --- a/frontend/src/pages/UserDashboard/UserDashboard.js +++ b/frontend/src/pages/UserDashboard/UserDashboard.js @@ -1,42 +1,13 @@ // src/pages/UserDashboard/UserDashboard.js -import React, { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; -import './UserDashboard.css'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import '../../styles/main.css'; const UserDashboard = () => { - const [userData, setUserData] = useState(null); - const navigate = useNavigate(); - - useEffect(() => { - const fetchUserData = async () => { - try { - const response = await api.get('/api/user_dashboard/dashboard/'); - setUserData(response.data); - } catch (error) { - console.error('Failed to fetch user data', error); - if (error.response && error.response.status === 401) { - navigate('/login'); - } - } - }; - - fetchUserData(); - }, [navigate]); - - if (!userData) { - return <div>Loading...</div>; - } - return ( - <div className="user-dashboard"> + <div className="user-dashboard-container"> <h1>User Dashboard</h1> - <div className="user-info"> - <p><strong>Username:</strong> {userData.username}</p> - <p><strong>Email:</strong> {userData.email}</p> - <p><strong>First Name:</strong> {userData.first_name}</p> - <p><strong>Last Name:</strong> {userData.last_name}</p> - </div> + <Link to="/user/profile" className="btn btn-primary">User Profile</Link> </div> ); }; diff --git a/frontend/src/pages/UserProfile/UserProfile.js b/frontend/src/pages/UserProfile/UserProfile.js new file mode 100644 index 0000000000000000000000000000000000000000..fd729007566c5984f25c9edc53a3eaabbe325400 --- /dev/null +++ b/frontend/src/pages/UserProfile/UserProfile.js @@ -0,0 +1,28 @@ +// src/pages/UserProfile/UserProfile.js + +import React, { useState } from 'react'; +import ChangeEmailSection from '../../components/UserProfile/ChangeEmailSection'; +import '../../styles/main.css'; + +const UserProfile = () => { + const [currentEmail, setCurrentEmail] = useState('freddy.imeri1@gmail.com'); // Placeholder, should be fetched from API + + const handleUpdateEmail = async ({ newEmail, password }) => { + // Implement the API call to update the email here + console.log('Updating email to:', newEmail); + console.log('Password for verification:', password); + setCurrentEmail(newEmail); + }; + + return ( + <div className="container"> + <h1>User Profile</h1> + <ChangeEmailSection + currentEmail={currentEmail} + onUpdateEmail={handleUpdateEmail} + /> + </div> + ); +}; + +export default UserProfile; diff --git a/frontend/src/styles/_components.css b/frontend/src/styles/_components.css new file mode 100644 index 0000000000000000000000000000000000000000..7ba7817bc817df2ad4629bf61c3511d8c749a145 --- /dev/null +++ b/frontend/src/styles/_components.css @@ -0,0 +1,56 @@ +/* src/styles/_components.css */ + +.auth-form { + background-color: #ffffff; + padding: 2rem; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; +} + +.card { + width: 100%; + max-width: 600px; + margin: 1rem 0; + border: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-radius: 10px; +} + +.card-body { + padding: 2.5rem; +} + +.card-title { + text-align: center; +} + +.btn { + width: 100%; + margin: 0.5rem 0; + padding: 0.75rem; + border-radius: 5px; +} + +.btn-primary { + background-color: var(--primary-color); + border: none; + color: #fff; + transition: background-color 0.3s ease; +} + +.btn-primary:hover { + background-color: #0056b3; +} + +.btn-secondary { + background-color: var(--secondary-color); + border: none; + color: #fff; + transition: background-color 0.3s ease; +} + +.btn-secondary:hover { + background-color: #5a6268; +} \ No newline at end of file diff --git a/frontend/src/styles/_forms.css b/frontend/src/styles/_forms.css new file mode 100644 index 0000000000000000000000000000000000000000..e42ec2d57783daf49446195fd59568e9689bb4a5 --- /dev/null +++ b/frontend/src/styles/_forms.css @@ -0,0 +1,27 @@ +/* src/styles/_forms.css */ + +.form-group { + margin-bottom: 15px; +} + +.form-control { + border-radius: 5px; + padding: 12px; + border-right: none; + box-shadow: none; +} + +.form-control:focus { + border-color: #ced4da; + box-shadow: none; +} + +.input-group-append { + cursor: pointer; +} + +.input-group-text { + background: transparent; + border-left: none; + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/src/styles/_global.css b/frontend/src/styles/_global.css new file mode 100644 index 0000000000000000000000000000000000000000..9add2a489ca28d61fa842b6e9c328adbaa59094f --- /dev/null +++ b/frontend/src/styles/_global.css @@ -0,0 +1,46 @@ +/* src/styles/_global.css */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body, +html { + margin: 0; + padding: 0; + height: 100%; + width: 100%; +} + +body { + font-family: var(--font-family-base), -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.6; + background-color: var(--light-color); + color: var(--dark-color); +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} + +/* Navbar specific styles */ +.navbar .navbar-collapse { + justify-content: flex-end; +} + +.nav-link { + color: #333 !important; + margin-left: 1rem; +} + +.nav-link.btn { + cursor: pointer; + border: none; + background: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/frontend/src/styles/_layout.css b/frontend/src/styles/_layout.css new file mode 100644 index 0000000000000000000000000000000000000000..68668b86747e510d5957e2f2b753775f161dad5c --- /dev/null +++ b/frontend/src/styles/_layout.css @@ -0,0 +1,31 @@ +/* src/styles/_layout.css */ + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +.auth-container { + display: flex; + justify-content: center; + align-items: flex-start; + height: 100vh; + background-color: var(--light-color); + padding-top: 10vh; + width: 100%; +} + +.admin-container { + padding: 2rem; +} + +.admin-container .list-group { + max-width: 600px; + margin: 0 auto; +} + +.table-striped th, +.table-striped td { + text-align: center; +} \ No newline at end of file diff --git a/frontend/src/layouts/Navbar/Navbar.css b/frontend/src/styles/_navbar.css similarity index 80% rename from frontend/src/layouts/Navbar/Navbar.css rename to frontend/src/styles/_navbar.css index 138e956a719da7697862a0f4b70995dfe63eac41..23d672c997794b84a5857b500cb8dddda86d1a69 100644 --- a/frontend/src/layouts/Navbar/Navbar.css +++ b/frontend/src/styles/_navbar.css @@ -1,7 +1,8 @@ -/* frontend/src/components/Navbar/Navbar.css */ +/* src/styles/_navbar.css */ + .navbar { width: 100%; - background-color: #f8f9fa; + background-color: var(--light-color); margin-bottom: 20px; } diff --git a/frontend/src/styles/_responsive.css b/frontend/src/styles/_responsive.css new file mode 100644 index 0000000000000000000000000000000000000000..70a9db74e42ec9d9c0f550fc76db51e0e7dfe209 --- /dev/null +++ b/frontend/src/styles/_responsive.css @@ -0,0 +1,22 @@ +/* src/styles/_responsive.css */ + +@media (max-width: 768px) { + .auth-container { + padding: 10px; + } + + .auth-form { + padding: 20px; + } +} + +@media (min-width: 1200px) { + .card-body { + padding: 3rem; + } + + .btn-lg { + font-size: 1.25rem; + padding: 0.75rem 1.5rem; + } +} \ No newline at end of file diff --git a/frontend/src/styles/_typography.css b/frontend/src/styles/_typography.css new file mode 100644 index 0000000000000000000000000000000000000000..48fc18912850ab064a7e28c5cf6c2a971b8c913a --- /dev/null +++ b/frontend/src/styles/_typography.css @@ -0,0 +1,15 @@ +/* src/styles/_typography.css */ + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: var(--spacing-unit); + font-weight: bold; +} + +p { + margin-bottom: var(--spacing-unit); +} \ No newline at end of file diff --git a/frontend/src/styles/_utilities.css b/frontend/src/styles/_utilities.css new file mode 100644 index 0000000000000000000000000000000000000000..2c11258ece5dac196ec5919910d7eea38bf1021a --- /dev/null +++ b/frontend/src/styles/_utilities.css @@ -0,0 +1,19 @@ +/* src/styles/_utilities.css */ + +.text-center { + text-align: center; +} + +.text-danger { + color: var(--danger-color); + text-align: center; + margin-top: 1rem; +} + +.m-1 { + margin: var(--spacing-unit); +} + +.p-1 { + padding: var(--spacing-unit); +} \ No newline at end of file diff --git a/frontend/src/styles/_variables.css b/frontend/src/styles/_variables.css new file mode 100644 index 0000000000000000000000000000000000000000..96a80390d799aefe2591031e9d2c26bc72f48f9f --- /dev/null +++ b/frontend/src/styles/_variables.css @@ -0,0 +1,14 @@ +/* src/styles/_variables.css */ + +:root { + --primary-color: #007bff; + --secondary-color: #6c757d; + --danger-color: #dc3545; + --light-color: #f8f9fa; + --dark-color: #343a40; + + --font-family-base: 'Arial, sans-serif'; + --font-size-base: 16px; + + --spacing-unit: 1rem; +} \ No newline at end of file diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css new file mode 100644 index 0000000000000000000000000000000000000000..239a070010b03e9e8240f13bb7469cd58d98ede4 --- /dev/null +++ b/frontend/src/styles/main.css @@ -0,0 +1,12 @@ +/* src/styles/main.css */ + +/* Import all CSS files */ +@import './_variables.css'; +@import './_global.css'; +@import './_layout.css'; +@import './_components.css'; +@import './_responsive.css'; +@import './_utilities.css'; +@import './_typography.css'; +@import './_forms.css'; +@import './_navbar.css'; \ No newline at end of file