From 7cb8e9dc6708f8f60cfc5f52e1b76521a6dafcf0 Mon Sep 17 00:00:00 2001 From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk> Date: Mon, 24 Jun 2024 16:49:08 +0100 Subject: [PATCH] Update CSS Stracture --- MisplaceAI/user_dashboard/serializers.py | 4 + MisplaceAI/user_dashboard/urls.py | 9 +- MisplaceAI/user_dashboard/views.py | 39 ++++++++- .../components/Common/Form/SubmitButton.js | 18 ---- .../components/Common/buttons/ActionButton.js | 17 ++++ .../components/Common/buttons/ButtonLink.js | 14 +++ .../components/Common/buttons/CancelButton.js | 26 ++++++ .../components/Common/buttons/SubmitButton.js | 24 +++++ .../src/components/Locations/AddLocation.js | 3 +- .../components/Locations/UpdateLocation.js | 5 +- frontend/src/components/Rules/AddRule.css | 15 ---- frontend/src/components/Rules/AddRule.js | 5 +- frontend/src/components/Rules/GetRules.css | 13 --- frontend/src/components/Rules/GetRules.js | 3 +- frontend/src/components/Rules/UpdateRule.css | 15 ---- frontend/src/components/Rules/UpdateRule.js | 7 +- .../UserProfile/ChangeEmailSection.js | 28 ------ .../UserProfile/ChangeInfoSection.js | 40 +++++++++ .../UserProfile/CurrentEmailCard.js | 16 ---- .../UserProfile/DisplayInfoWithAction.js | 26 ++++++ .../UserProfile/EditableInfoForm.js | 55 ++++++++++++ frontend/src/forms/Auth/AdminLoginForm.js | 48 +++++----- frontend/src/forms/Auth/LoginForm.js | 39 ++++----- frontend/src/forms/Auth/RegisterForm.js | 79 ++++++++--------- frontend/src/forms/Items/ItemForm.js | 27 +++--- .../src/forms/UserProfile/ChangeEmailForm.js | 87 ++++++++++++------- .../forms/UserProfile/ChangeUsernameForm.js | 69 +++++++++++++++ frontend/src/pages/Admin/AdminDashboard.js | 2 +- frontend/src/pages/Admin/AdminLoginPage.js | 4 +- frontend/src/pages/Admin/AdminUsers.js | 5 +- frontend/src/pages/Auth/LoginPage.js | 3 +- frontend/src/pages/Auth/RegisterPage.js | 25 ++++-- .../DetectionOptions/DetectionOptionsPage.js | 3 +- frontend/src/pages/Home/Home.js | 3 +- frontend/src/pages/Items/ManageItemsPage.js | 6 +- .../pages/Locations/ManageLocationsPage.js | 13 ++- .../NormalDetection/NormalDetectionPage.js | 2 +- .../src/pages/Results/DisplayResultsPage.js | 3 +- frontend/src/pages/Rules/ManageRulesPage.js | 2 +- .../src/pages/UserDashboard/UserDashboard.js | 27 ++++-- frontend/src/pages/UserProfile/UserProfile.js | 46 +++++++--- frontend/src/services/userApi.js | 23 ++++- frontend/src/styles/_auth.css | 42 +++++++++ frontend/src/styles/_components.css | 59 +++++++++---- frontend/src/styles/_global.css | 52 ++++++----- frontend/src/styles/_layout.css | 43 ++++++--- frontend/src/styles/_responsive.css | 1 + frontend/src/styles/_utilities.css | 31 ++++--- frontend/src/styles/buttons/ActionButton.css | 34 ++++++++ frontend/src/styles/buttons/ButtonLink.css | 62 +++++++++++++ frontend/src/styles/buttons/CancelButton.css | 63 ++++++++++++++ frontend/src/styles/buttons/SubmitButton.css | 68 +++++++++++++++ frontend/src/styles/dashboard.css | 53 +++++++++++ frontend/src/styles/main.css | 3 +- 54 files changed, 1047 insertions(+), 362 deletions(-) delete mode 100644 frontend/src/components/Common/Form/SubmitButton.js create mode 100644 frontend/src/components/Common/buttons/ActionButton.js create mode 100644 frontend/src/components/Common/buttons/ButtonLink.js create mode 100644 frontend/src/components/Common/buttons/CancelButton.js create mode 100644 frontend/src/components/Common/buttons/SubmitButton.js delete mode 100644 frontend/src/components/Rules/AddRule.css delete mode 100644 frontend/src/components/Rules/GetRules.css delete mode 100644 frontend/src/components/Rules/UpdateRule.css delete mode 100644 frontend/src/components/UserProfile/ChangeEmailSection.js create mode 100644 frontend/src/components/UserProfile/ChangeInfoSection.js delete mode 100644 frontend/src/components/UserProfile/CurrentEmailCard.js create mode 100644 frontend/src/components/UserProfile/DisplayInfoWithAction.js create mode 100644 frontend/src/components/UserProfile/EditableInfoForm.js create mode 100644 frontend/src/forms/UserProfile/ChangeUsernameForm.js create mode 100644 frontend/src/styles/_auth.css create mode 100644 frontend/src/styles/buttons/ActionButton.css create mode 100644 frontend/src/styles/buttons/ButtonLink.css create mode 100644 frontend/src/styles/buttons/CancelButton.css create mode 100644 frontend/src/styles/buttons/SubmitButton.css create mode 100644 frontend/src/styles/dashboard.css diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py index 8c40d78..ef35956 100644 --- a/MisplaceAI/user_dashboard/serializers.py +++ b/MisplaceAI/user_dashboard/serializers.py @@ -22,4 +22,8 @@ class UserUpdateSerializer(serializers.ModelSerializer): class UserUpdateEmailSerializer(serializers.Serializer): email = serializers.EmailField() + password = serializers.CharField(write_only=True) + +class UserUpdateUsernameSerializer(serializers.Serializer): + username = serializers.CharField(max_length=150) 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 b40dfe4..3a063da 100644 --- a/MisplaceAI/user_dashboard/urls.py +++ b/MisplaceAI/user_dashboard/urls.py @@ -1,10 +1,13 @@ # MisplaceAI/user_dashboard/urls.py from django.urls import path -from .views import UserDashboardView, UserUpdateView, UpdateEmailView +from .views import UserDashboardView, UserUpdateView, UpdateEmailView,CurrentUserEmailView,CurrentUserUsernameView,UpdateUsernameView 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'), + path('update/', UserUpdateView.as_view(), name='user-update'), + path('update_email/', UpdateEmailView.as_view(), name='user-update-email'), + path('email/', CurrentUserEmailView.as_view(), name='user-current-email'), + path('username/', CurrentUserUsernameView.as_view(), name='user-current-username'), + path('update_username/', UpdateUsernameView.as_view(), name='user-update-username'), ] diff --git a/MisplaceAI/user_dashboard/views.py b/MisplaceAI/user_dashboard/views.py index aa7f556..416a509 100644 --- a/MisplaceAI/user_dashboard/views.py +++ b/MisplaceAI/user_dashboard/views.py @@ -1,8 +1,9 @@ # MisplaceAI/user_dashboard/views.py + from django.contrib.auth.models import User from rest_framework import generics from rest_framework.permissions import IsAuthenticated -from .serializers import UserSerializer, UserUpdateSerializer, UserUpdateEmailSerializer +from .serializers import UserSerializer, UserUpdateSerializer, UserUpdateEmailSerializer, UserUpdateUsernameSerializer from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status @@ -42,4 +43,38 @@ class UpdateEmailView(APIView): 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 + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class UpdateUsernameView(APIView): + permission_classes = [IsAuthenticated] + + def put(self, request, *args, **kwargs): + user = request.user + serializer = UserUpdateUsernameSerializer(data=request.data) + + if serializer.is_valid(): + new_username = serializer.validated_data.get('username') + 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.username = new_username + user.save() + return Response({'message': 'Username updated successfully'}, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class CurrentUserEmailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + user = request.user + return Response({'email': user.email}, status=status.HTTP_200_OK) + +class CurrentUserUsernameView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + user = request.user + return Response({'username': user.username}, status=status.HTTP_200_OK) diff --git a/frontend/src/components/Common/Form/SubmitButton.js b/frontend/src/components/Common/Form/SubmitButton.js deleted file mode 100644 index 781add9..0000000 --- a/frontend/src/components/Common/Form/SubmitButton.js +++ /dev/null @@ -1,18 +0,0 @@ -// src/components/Common/Form/SubmitButton.js -import React from 'react'; -import PropTypes from 'prop-types'; - -const SubmitButton = ({ label, disabled }) => ( - <button type="submit" className="btn btn-primary btn-block" disabled={disabled}>{label}</button> -); - -SubmitButton.propTypes = { - label: PropTypes.string.isRequired, - disabled: PropTypes.bool, -}; - -SubmitButton.defaultProps = { - disabled: false, -}; - -export default SubmitButton; diff --git a/frontend/src/components/Common/buttons/ActionButton.js b/frontend/src/components/Common/buttons/ActionButton.js new file mode 100644 index 0000000..6b48ed4 --- /dev/null +++ b/frontend/src/components/Common/buttons/ActionButton.js @@ -0,0 +1,17 @@ +// src/components/Common/buttons/ActionButton.js + +import React from 'react'; +import '../../../styles/buttons/ActionButton.css'; + +const ActionButton = ({ label, onClick }) => { + return ( + <button className="action-button" onClick={onClick}> + {label} + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" className="icon"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" /> + </svg> + </button> + ); +}; + +export default ActionButton; diff --git a/frontend/src/components/Common/buttons/ButtonLink.js b/frontend/src/components/Common/buttons/ButtonLink.js new file mode 100644 index 0000000..7695ae5 --- /dev/null +++ b/frontend/src/components/Common/buttons/ButtonLink.js @@ -0,0 +1,14 @@ +// src/components/Common/buttons/ButtonLink.js +import React from 'react'; +import { Link } from 'react-router-dom'; +import '../../../styles/buttons/ButtonLink.css'; + +const ButtonLink = ({ to, label, className, ...props }) => { + return ( + <Link to={to} className={`button-64 ${className}`} {...props}> + <span className="text">{label}</span> + </Link> + ); +}; + +export default ButtonLink; diff --git a/frontend/src/components/Common/buttons/CancelButton.js b/frontend/src/components/Common/buttons/CancelButton.js new file mode 100644 index 0000000..4338775 --- /dev/null +++ b/frontend/src/components/Common/buttons/CancelButton.js @@ -0,0 +1,26 @@ +//https://uiverse.io/cssbuttons-io/stupid-impala-51 +// src/components/Common/buttons/CancelButton.js + +import React from 'react'; +import PropTypes from 'prop-types'; +import '../../../styles/buttons/CancelButton.css'; // Ensure this file imports the button styles + +const CancelButton = ({ label, onClick }) => { + return ( + <button className="noselect" type="button" onClick={onClick}> + <span className="text">{label}</span> + <span className="icon"> + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"></path> + </svg> + </span> + </button> + ); +}; + +CancelButton.propTypes = { + label: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +}; + +export default CancelButton; diff --git a/frontend/src/components/Common/buttons/SubmitButton.js b/frontend/src/components/Common/buttons/SubmitButton.js new file mode 100644 index 0000000..6fea63b --- /dev/null +++ b/frontend/src/components/Common/buttons/SubmitButton.js @@ -0,0 +1,24 @@ +// src/components/Common/buttons/SubmitButton.js + +import React from 'react'; +import PropTypes from 'prop-types'; +import '../../../styles/buttons/SubmitButton.css'; // Ensure this file imports the button styles + +const SubmitButton = ({ label }) => { + return ( + <button className="submit-button noselect" type="submit"> + <span className="text">{label}</span> + <span className="icon"> + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <path d="M10 17l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path> + </svg> + </span> + </button> + ); +}; + +SubmitButton.propTypes = { + label: PropTypes.string.isRequired, +}; + +export default SubmitButton; diff --git a/frontend/src/components/Locations/AddLocation.js b/frontend/src/components/Locations/AddLocation.js index f392d9d..548b8ad 100644 --- a/frontend/src/components/Locations/AddLocation.js +++ b/frontend/src/components/Locations/AddLocation.js @@ -1,3 +1,4 @@ +// src/components/Locations/AddLocation.js import React, { useState } from 'react'; import { addLocation } from '../../services/locationApi'; import '../../styles/main.css'; @@ -13,7 +14,7 @@ const AddLocation = ({ onLocationAdded }) => { }; return ( - <div className="add-location-container"> + <div className="form-container"> <h3>Add Location</h3> <form onSubmit={handleSubmit}> <div className="form-group"> diff --git a/frontend/src/components/Locations/UpdateLocation.js b/frontend/src/components/Locations/UpdateLocation.js index acfafe8..4dbb289 100644 --- a/frontend/src/components/Locations/UpdateLocation.js +++ b/frontend/src/components/Locations/UpdateLocation.js @@ -23,9 +23,9 @@ const UpdateLocation = ({ location, onUpdateCompleted }) => { }; return ( - <div className="update-location-container"> + <div className="form-container"> <h3>Update Location</h3> - {error && <p className="error">{error}</p>} + {error && <p className="text-danger">{error}</p>} <form onSubmit={handleSubmit}> <div className="form-group"> <label>Name</label> @@ -39,7 +39,6 @@ const UpdateLocation = ({ location, onUpdateCompleted }) => { </div> <button type="submit" className="btn btn-primary">Update</button> </form> - {error && <p style={{ color: 'red' }}>{error}</p>} </div> ); }; diff --git a/frontend/src/components/Rules/AddRule.css b/frontend/src/components/Rules/AddRule.css deleted file mode 100644 index 24530fb..0000000 --- a/frontend/src/components/Rules/AddRule.css +++ /dev/null @@ -1,15 +0,0 @@ -.add-rule-container { - margin-top: 20px; - padding: 20px; - border: 1px solid #ddd; - border-radius: 5px; - background-color: #f9f9f9; -} - -.add-rule-container .form-group { - margin-bottom: 15px; -} - -.add-rule-container .error { - color: red; -} \ No newline at end of file diff --git a/frontend/src/components/Rules/AddRule.js b/frontend/src/components/Rules/AddRule.js index ffa4932..390f358 100644 --- a/frontend/src/components/Rules/AddRule.js +++ b/frontend/src/components/Rules/AddRule.js @@ -1,3 +1,4 @@ +// src/components/Rules/AddRule.js import React, { useState, useEffect } from 'react'; import { addRule } from '../../services/ruleApi'; import { getItems } from '../../services/itemApi'; @@ -39,9 +40,9 @@ const AddRule = ({ onRuleAdded }) => { }; return ( - <div className="add-rule-container"> + <div className="form-container"> <h3>Add Rule</h3> - {error && <p className="error">{error}</p>} + {error && <p className="text-danger">{error}</p>} <form onSubmit={handleSubmit}> <div className="form-group"> <label>Item</label> diff --git a/frontend/src/components/Rules/GetRules.css b/frontend/src/components/Rules/GetRules.css deleted file mode 100644 index 5d353d4..0000000 --- a/frontend/src/components/Rules/GetRules.css +++ /dev/null @@ -1,13 +0,0 @@ -.rules-list { - margin-top: 20px; -} - -.rules-list .list-group-item { - display: flex; - justify-content: space-between; - align-items: center; -} - -.rules-list .list-group-item .btn { - margin-left: 5px; -} \ No newline at end of file diff --git a/frontend/src/components/Rules/GetRules.js b/frontend/src/components/Rules/GetRules.js index 3759599..28ff36d 100644 --- a/frontend/src/components/Rules/GetRules.js +++ b/frontend/src/components/Rules/GetRules.js @@ -1,6 +1,7 @@ +// src/components/Rules/GetRules.js import React, { useEffect, useState } from 'react'; import { getRules, deleteRule } from '../../services/ruleApi'; -import '../../styles/main.css'; +import '../../styles/main.css'; // Ensure this is the correct path to main.css const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { const [rules, setRules] = useState([]); diff --git a/frontend/src/components/Rules/UpdateRule.css b/frontend/src/components/Rules/UpdateRule.css deleted file mode 100644 index 8343c42..0000000 --- a/frontend/src/components/Rules/UpdateRule.css +++ /dev/null @@ -1,15 +0,0 @@ -.update-rule-container { - margin-top: 20px; - padding: 20px; - border: 1px solid #ddd; - border-radius: 5px; - background-color: #f9f9f9; -} - -.update-rule-container .form-group { - margin-bottom: 15px; -} - -.update-rule-container .error { - color: red; -} \ No newline at end of file diff --git a/frontend/src/components/Rules/UpdateRule.js b/frontend/src/components/Rules/UpdateRule.js index 18bcd76..8334233 100644 --- a/frontend/src/components/Rules/UpdateRule.js +++ b/frontend/src/components/Rules/UpdateRule.js @@ -1,8 +1,9 @@ +// src/components/Rules/UpdateRule.js import React, { useState, useEffect } from 'react'; import { updateRule } from '../../services/ruleApi'; import { getItems } from '../../services/itemApi'; import { getLocations } from '../../services/locationApi'; -import '../../styles/main.css'; +import '../../styles/main.css'; // Ensure this is the correct path to main.css const UpdateRule = ({ rule, onUpdateCompleted }) => { const [items, setItems] = useState([]); @@ -38,9 +39,9 @@ const UpdateRule = ({ rule, onUpdateCompleted }) => { }; return ( - <div className="update-rule-container"> + <div className="form-container"> <h3>Update Rule</h3> - {error && <p className="error">{error}</p>} + {error && <p className="text-danger">{error}</p>} <form onSubmit={handleSubmit}> <div className="form-group"> <label>Item</label> diff --git a/frontend/src/components/UserProfile/ChangeEmailSection.js b/frontend/src/components/UserProfile/ChangeEmailSection.js deleted file mode 100644 index 50ffc25..0000000 --- a/frontend/src/components/UserProfile/ChangeEmailSection.js +++ /dev/null @@ -1,28 +0,0 @@ -// 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/ChangeInfoSection.js b/frontend/src/components/UserProfile/ChangeInfoSection.js new file mode 100644 index 0000000..aab2870 --- /dev/null +++ b/frontend/src/components/UserProfile/ChangeInfoSection.js @@ -0,0 +1,40 @@ +// src/components/UserProfile/ChangeInfoSection.js +import React, { useState } from 'react'; +import DisplayInfoWithAction from './DisplayInfoWithAction'; +import EditableInfoForm from './EditableInfoForm'; + +const ChangeInfoSection = ({ title, infoLabel, currentInfo, onUpdateInfo }) => { + const [showForm, setShowForm] = useState(false); + + const handleActionClick = () => { + setShowForm(true); + }; + + const handleCancel = () => { + setShowForm(false); + }; + + const handleSubmit = (data) => { + onUpdateInfo(data); + setShowForm(false); + }; + + return ( + <div className="settings-section"> + <DisplayInfoWithAction + title={title} + info={currentInfo} + buttonLabel={`Change ${infoLabel}`} + onActionClick={handleActionClick} + /> + {showForm && ( + <EditableInfoForm + onSubmit={handleSubmit} + onCancel={handleCancel} + /> + )} + </div> + ); +}; + +export default ChangeInfoSection; diff --git a/frontend/src/components/UserProfile/CurrentEmailCard.js b/frontend/src/components/UserProfile/CurrentEmailCard.js deleted file mode 100644 index 7e6ea9a..0000000 --- a/frontend/src/components/UserProfile/CurrentEmailCard.js +++ /dev/null @@ -1,16 +0,0 @@ -// 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/components/UserProfile/DisplayInfoWithAction.js b/frontend/src/components/UserProfile/DisplayInfoWithAction.js new file mode 100644 index 0000000..c2f7c3d --- /dev/null +++ b/frontend/src/components/UserProfile/DisplayInfoWithAction.js @@ -0,0 +1,26 @@ +// src/components/UserProfile/DisplayInfoWithAction.js + +import React from 'react'; +import PropTypes from 'prop-types'; +import ActionButton from '../Common/buttons/ActionButton'; + +const DisplayInfoWithAction = ({ title, info, buttonLabel, onActionClick }) => { + return ( + <div className="card"> + <div className="card-body"> + <h3 className="card-title">{title}</h3> + <p className="card-text">{info}</p> + <ActionButton label={buttonLabel} onClick={onActionClick} /> + </div> + </div> + ); +}; + +DisplayInfoWithAction.propTypes = { + title: PropTypes.string.isRequired, + info: PropTypes.string.isRequired, + buttonLabel: PropTypes.string.isRequired, + onActionClick: PropTypes.func.isRequired, +}; + +export default DisplayInfoWithAction; diff --git a/frontend/src/components/UserProfile/EditableInfoForm.js b/frontend/src/components/UserProfile/EditableInfoForm.js new file mode 100644 index 0000000..737338a --- /dev/null +++ b/frontend/src/components/UserProfile/EditableInfoForm.js @@ -0,0 +1,55 @@ +// src/components/UserProfile/EditableInfoForm.js + +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import FormContainer from '../Common/Form/FormContainer'; +import FormField from '../Common/Form/FormField'; +import PasswordInputField from '../Common/Password/PasswordInputField'; +import SubmitButton from '../Common/buttons/SubmitButton'; +import CancelButton from '../Common/buttons/CancelButton'; + +const EditableInfoForm = ({ label, type, onSubmit, onCancel, loading, error }) => { + const [newInfo, setNewInfo] = useState(''); + const [password, setPassword] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + onSubmit({ newInfo, password }); + setNewInfo(''); + setPassword(''); + }; + + return ( + <FormContainer onSubmit={handleSubmit}> + <FormField + label={label} + type={type} + value={newInfo} + onChange={(e) => setNewInfo(e.target.value)} + required + /> + <PasswordInputField + label="Password" + value={password} + onChange={(e) => setPassword(e.target.value)} + required + /> + {error && <p className="text-danger">{error}</p>} + <div className="form-buttons"> + <SubmitButton className="submit-button" label={loading ? 'Updating...' : `Update ${label}`} disabled={loading} /> + <CancelButton className="action-button" label="Cancel" onClick={onCancel} /> + </div> + </FormContainer> + ); +}; + +EditableInfoForm.propTypes = { + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + loading: PropTypes.bool, + error: PropTypes.string, +}; + +export default EditableInfoForm; diff --git a/frontend/src/forms/Auth/AdminLoginForm.js b/frontend/src/forms/Auth/AdminLoginForm.js index 4f94607..6899fa3 100644 --- a/frontend/src/forms/Auth/AdminLoginForm.js +++ b/frontend/src/forms/Auth/AdminLoginForm.js @@ -1,11 +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 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/buttons/SubmitButton'; import ErrorMessage from '../../components/Common/Form/ErrorMessage'; -import SubmitButton from '../../components/Common/Form/SubmitButton'; import '../../styles/main.css'; const AdminLoginForm = () => { @@ -20,6 +21,7 @@ const AdminLoginForm = () => { const response = await api.post('/api/admin-app/login/', { username, password }); localStorage.setItem('adminToken', response.data.access); localStorage.setItem('isAdmin', true); + localStorage.setItem('username', username); localStorage.setItem('isAuthenticated', true); navigate('/admin/dashboard'); window.location.reload(); // Refresh to update Navbar @@ -29,29 +31,23 @@ const AdminLoginForm = () => { }; return ( - <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> + <FormContainer onSubmit={handleSubmit}> + <h2 className="text-center">Admin Login</h2> + {error && <ErrorMessage message={error} />} + <FormField + label="Username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + required + /> + <PasswordInputField + label="Password" + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + <SubmitButton label="Login" /> + </FormContainer> ); }; diff --git a/frontend/src/forms/Auth/LoginForm.js b/frontend/src/forms/Auth/LoginForm.js index cc9023e..930e31c 100644 --- a/frontend/src/forms/Auth/LoginForm.js +++ b/frontend/src/forms/Auth/LoginForm.js @@ -6,7 +6,7 @@ import api from '../../services/api'; 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 SubmitButton from '../../components/Common/buttons/SubmitButton'; import ErrorMessage from '../../components/Common/Form/ErrorMessage'; import '../../styles/main.css'; @@ -21,6 +21,7 @@ const LoginForm = () => { try { const response = await api.post('/api/auth/login/', { username, password }); localStorage.setItem('token', response.data.access); + localStorage.setItem('username', username); localStorage.setItem('isAuthenticated', true); navigate('/'); window.location.reload(); // Refresh to update Navbar @@ -30,25 +31,23 @@ const LoginForm = () => { }; return ( - <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} - onChange={(e) => setUsername(e.target.value)} - required - /> - <PasswordInputField - label="Password" - value={password} - onChange={(e) => setPassword(e.target.value)} - /> - <SubmitButton label="Login" /> - </FormContainer> - </div> + <FormContainer onSubmit={handleSubmit}> + <h2 className="text-center">Login</h2> + {error && <ErrorMessage message={error} />} + <FormField + label="Username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + required + /> + <PasswordInputField + label="Password" + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + <SubmitButton label="Login" /> + </FormContainer> ); }; diff --git a/frontend/src/forms/Auth/RegisterForm.js b/frontend/src/forms/Auth/RegisterForm.js index e1e5613..41e5e8a 100644 --- a/frontend/src/forms/Auth/RegisterForm.js +++ b/frontend/src/forms/Auth/RegisterForm.js @@ -1,12 +1,12 @@ // src/forms/Auth/RegisterForm.js - import React, { useState, useEffect, useCallback } 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 ErrorMessage from '../../components/Common/Form/ErrorMessage'; +import SubmitButton from '../../components/Common/buttons/SubmitButton'; import '../../styles/main.css'; const RegisterForm = () => { @@ -81,6 +81,7 @@ const RegisterForm = () => { try { const response = await api.post('/api/auth/register/', { username, email, password, password2 }); localStorage.setItem('token', response.data.access); + localStorage.setItem('username', username); localStorage.setItem('isAuthenticated', true); navigate('/'); window.location.reload(); // Refresh to update Navbar @@ -90,43 +91,39 @@ const RegisterForm = () => { }; return ( - <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> + <FormContainer onSubmit={handleSubmit}> + <h2 className="text-center">Register</h2> + {error && <ErrorMessage message={error} />} + <FormField + label="Username" + type="text" + value={username} + onChange={(e) => setUsername(e.target.value)} + required + /> + <FormField + 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} /> + </FormContainer> ); }; diff --git a/frontend/src/forms/Items/ItemForm.js b/frontend/src/forms/Items/ItemForm.js index aaefbd0..63db5cf 100644 --- a/frontend/src/forms/Items/ItemForm.js +++ b/frontend/src/forms/Items/ItemForm.js @@ -4,27 +4,30 @@ 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 SubmitButton from '../../components/Common/buttons/SubmitButton'; import '../../styles/main.css'; const ItemForm = ({ initialData = {}, onSubmit }) => { const [name, setName] = useState(initialData.name || ''); - const handleSubmit = () => { + const handleSubmit = (e) => { + e.preventDefault(); // Prevent the default form submission onSubmit({ name }); }; return ( - <FormContainer onSubmit={handleSubmit}> - <FormField - label="Name" - type="text" - value={name} - onChange={(e) => setName(e.target.value)} - required - /> - <SubmitButton label="Submit" /> - </FormContainer> + <div className="form-container"> + <FormContainer onSubmit={handleSubmit}> + <FormField + label="Name" + type="text" + value={name} + onChange={(e) => setName(e.target.value)} + required + /> + <SubmitButton label="Submit" /> + </FormContainer> + </div> ); }; diff --git a/frontend/src/forms/UserProfile/ChangeEmailForm.js b/frontend/src/forms/UserProfile/ChangeEmailForm.js index 8524975..c752e28 100644 --- a/frontend/src/forms/UserProfile/ChangeEmailForm.js +++ b/frontend/src/forms/UserProfile/ChangeEmailForm.js @@ -2,45 +2,68 @@ 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(''); +import EditableInfoForm from '../../components/UserProfile/EditableInfoForm'; +import { updateUserEmail } from '../../services/userApi'; +import ActionButton from '../../components/Common/buttons/ActionButton'; + +const ChangeEmailForm = ({ currentEmail, onUpdateEmail }) => { + const [showForm, setShowForm] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleActionClick = () => { + setShowForm(!showForm); + }; + + const handleCancel = () => { + setShowForm(false); + setError(null); + }; + + const handleSubmit = async ({ newInfo, password }) => { + setLoading(true); + setError(null); + try { + const response = await updateUserEmail({ email: newInfo, password }); + console.log('Email updated:', response); + onUpdateEmail({ newInfo }); // Update the parent state + setShowForm(false); + } catch (err) { + console.error('Failed to update email:', err); + setError('Failed to update email. Please try again.'); + } + setLoading(false); }; 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> + <div className="dashboard-card"> + <div className="card card-wide"> + <div className="card-body"> + <h3 className="card-title">Current Email: {currentEmail}</h3> + <ActionButton className="action-button" label={showForm ? 'Cancel' : 'Change Email'} onClick={handleActionClick} /> + </div> + </div> + {showForm && ( + <div className="card card-wide"> + <div className="card-body"> + <EditableInfoForm + label="New Email" + type="email" + onSubmit={handleSubmit} + onCancel={handleCancel} + loading={loading} + error={error} + /> + </div> + </div> + )} + </div> ); }; ChangeEmailForm.propTypes = { - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, + currentEmail: PropTypes.string.isRequired, + onUpdateEmail: PropTypes.func.isRequired, }; export default ChangeEmailForm; diff --git a/frontend/src/forms/UserProfile/ChangeUsernameForm.js b/frontend/src/forms/UserProfile/ChangeUsernameForm.js new file mode 100644 index 0000000..07b515e --- /dev/null +++ b/frontend/src/forms/UserProfile/ChangeUsernameForm.js @@ -0,0 +1,69 @@ +// src/forms/UserProfile/ChangeUsernameForm.js + +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import EditableInfoForm from '../../components/UserProfile/EditableInfoForm'; +import { updateUsername } from '../../services/userApi'; +import ActionButton from '../../components/Common/buttons/ActionButton'; + +const ChangeUsernameForm = ({ currentUsername, onUpdateUsername }) => { + const [showForm, setShowForm] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleActionClick = () => { + setShowForm(!showForm); + }; + + const handleCancel = () => { + setShowForm(false); + setError(null); + }; + + const handleSubmit = async ({ newInfo, password }) => { + setLoading(true); + setError(null); + try { + const response = await updateUsername({ username: newInfo, password }); + console.log('Username updated:', response); + onUpdateUsername({ newInfo }); // Update the parent state + setShowForm(false); + } catch (err) { + console.error('Failed to update username:', err); + setError('Failed to update username. Please try again.'); + } + setLoading(false); + }; + + return ( + <div className="dashboard-card"> + <div className="card card-wide"> + <div className="card-body"> + <h3 className="card-title">Current Username: {currentUsername}</h3> + <ActionButton className="action-button" label={showForm ? 'Cancel' : 'Change Username'} onClick={handleActionClick} /> + </div> + </div> + {showForm && ( + <div className="card card-wide"> + <div className="card-body"> + <EditableInfoForm + label="New Username" + type="text" + onSubmit={handleSubmit} + onCancel={handleCancel} + loading={loading} + error={error} + /> + </div> + </div> + )} + </div> + ); +}; + +ChangeUsernameForm.propTypes = { + currentUsername: PropTypes.string.isRequired, + onUpdateUsername: PropTypes.func.isRequired, +}; + +export default ChangeUsernameForm; diff --git a/frontend/src/pages/Admin/AdminDashboard.js b/frontend/src/pages/Admin/AdminDashboard.js index 76d65f3..0e5770b 100644 --- a/frontend/src/pages/Admin/AdminDashboard.js +++ b/frontend/src/pages/Admin/AdminDashboard.js @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'; const AdminDashboard = () => { return ( - <div className="admin-container"> + <div className="pages-container-center"> <h1>Admin Dashboard</h1> <div className="list-group"> <Link to="/admin/users" className="list-group-item list-group-item-action">Users Activity</Link> diff --git a/frontend/src/pages/Admin/AdminLoginPage.js b/frontend/src/pages/Admin/AdminLoginPage.js index e8c0212..a5176c4 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 '../../styles/main.css'; const AdminLoginPage = () => { const navigate = useNavigate(); @@ -15,7 +15,7 @@ const AdminLoginPage = () => { }, [navigate]); return ( - <div className="auth-container"> + <div className="pages-container-center"> <AdminLoginForm /> </div> ); diff --git a/frontend/src/pages/Admin/AdminUsers.js b/frontend/src/pages/Admin/AdminUsers.js index e904663..f26affa 100644 --- a/frontend/src/pages/Admin/AdminUsers.js +++ b/frontend/src/pages/Admin/AdminUsers.js @@ -1,6 +1,7 @@ +// src/pages/Admin/AdminUsers.js import React, { useEffect, useState } from 'react'; import api from '../../services/api'; - +import '../../styles/main.css'; // Ensure this is the correct path to main.css const AdminUsers = () => { const [users, setUsers] = useState([]); @@ -29,7 +30,7 @@ const AdminUsers = () => { }; return ( - <div className="admin-container"> + <div className="pages-container-center"> <h1>Users Activity</h1> <table className="table table-striped"> <thead> diff --git a/frontend/src/pages/Auth/LoginPage.js b/frontend/src/pages/Auth/LoginPage.js index 2a899e2..ad39f78 100644 --- a/frontend/src/pages/Auth/LoginPage.js +++ b/frontend/src/pages/Auth/LoginPage.js @@ -2,6 +2,7 @@ import React, { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import LoginForm from '../../forms/Auth/LoginForm'; +import '../../styles/main.css'; const LoginPage = () => { const navigate = useNavigate(); @@ -14,7 +15,7 @@ const LoginPage = () => { }, [navigate]); return ( - <div className="auth-container"> + <div className="pages-container-center"> <LoginForm /> </div> ); diff --git a/frontend/src/pages/Auth/RegisterPage.js b/frontend/src/pages/Auth/RegisterPage.js index c727cda..8d48ca6 100644 --- a/frontend/src/pages/Auth/RegisterPage.js +++ b/frontend/src/pages/Auth/RegisterPage.js @@ -1,11 +1,24 @@ // src/pages/Auth/RegisterPage.js -import React from 'react'; +import React, { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import RegisterForm from '../../forms/Auth/RegisterForm'; +import '../../styles/main.css'; -const RegisterPage = () => ( - <div className="auth-container"> - <RegisterForm /> - </div> -); +const RegisterPage = () => { + const navigate = useNavigate(); + + useEffect(() => { + const isAuthenticated = !!localStorage.getItem('isAuthenticated'); + if (isAuthenticated) { + navigate('/'); + } + }, [navigate]); + + return ( + <div className="pages-container-center"> + <RegisterForm /> + </div> + ); +}; export default RegisterPage; diff --git a/frontend/src/pages/DetectionOptions/DetectionOptionsPage.js b/frontend/src/pages/DetectionOptions/DetectionOptionsPage.js index 3dd29ae..86502d6 100644 --- a/frontend/src/pages/DetectionOptions/DetectionOptionsPage.js +++ b/frontend/src/pages/DetectionOptions/DetectionOptionsPage.js @@ -1,10 +1,11 @@ // src/pages/DetectionOptions/DetectionOptionsPage.js import React from 'react'; import { Link } from 'react-router-dom'; +import '../../styles/main.css'; const DetectionOptionsPage = () => { return ( - <div className="container mt-5"> + <div className="pages-container-center"> <h1 className="text-center">Select Detection Method</h1> <div className="row justify-content-center mt-4"> <div className="col-md-6 text-center"> diff --git a/frontend/src/pages/Home/Home.js b/frontend/src/pages/Home/Home.js index 55d0261..81f050c 100644 --- a/frontend/src/pages/Home/Home.js +++ b/frontend/src/pages/Home/Home.js @@ -1,11 +1,10 @@ // src/pages/Home/Home.js - import React from 'react'; import '../../styles/main.css'; const Home = () => { return ( - <div className="container"> + <div className="pages-container-center"> <div className="card"> <div className="card-body"> <h1 className="card-title">Welcome to MisplaceAI</h1> diff --git a/frontend/src/pages/Items/ManageItemsPage.js b/frontend/src/pages/Items/ManageItemsPage.js index edc983f..a67b33e 100644 --- a/frontend/src/pages/Items/ManageItemsPage.js +++ b/frontend/src/pages/Items/ManageItemsPage.js @@ -1,10 +1,10 @@ -// src/pages/Items/ManageItemsPage.js +// src/pages/Items/p import React, { useState } from 'react'; import AddItem from '../../components/Items/AddItem'; import GetItems from '../../components/Items/GetItems'; import UpdateItem from '../../components/Items/UpdateItem'; import { deleteItem } from '../../services/itemApi'; -import '../../styles/main.css'; +import '../../styles/main.css'; // Ensure this is the correct path to main.css const ManageItemsPage = () => { const [editingItem, setEditingItem] = useState(null); @@ -29,7 +29,7 @@ const ManageItemsPage = () => { }; return ( - <div className="manage-items-container container mt-5"> + <div className="pages-container-center"> <h1 className="text-center mb-4">Manage Items</h1> <h2 className="text-center mb-4">Add Item</h2> <AddItem onItemAdded={handleItemAdded} /> diff --git a/frontend/src/pages/Locations/ManageLocationsPage.js b/frontend/src/pages/Locations/ManageLocationsPage.js index 04e0bfc..f09308b 100644 --- a/frontend/src/pages/Locations/ManageLocationsPage.js +++ b/frontend/src/pages/Locations/ManageLocationsPage.js @@ -1,9 +1,10 @@ +// src/pages/Locations/ManageLocationsPage.js import React, { useState } from 'react'; import AddLocation from '../../components/Locations/AddLocation'; import GetLocations from '../../components/Locations/GetLocations'; import UpdateLocation from '../../components/Locations/UpdateLocation'; import { deleteLocation } from '../../services/locationApi'; -import '../../styles/main.css'; +import '../../styles/main.css'; // Ensure this is the correct path to main.css const ManageLocationsPage = () => { const [editingLocation, setEditingLocation] = useState(null); @@ -28,12 +29,16 @@ const ManageLocationsPage = () => { }; return ( - <div className="manage-locations-page"> - <h1>Manage Locations</h1> + <div className="pages-container-center"> + <h1 className="text-center mb-4">Manage Locations</h1> <AddLocation onLocationAdded={handleLocationAdded} /> {editingLocation && ( - <UpdateLocation location={editingLocation} onUpdateCompleted={handleUpdateCompleted} /> + <> + <h2 className="text-center mb-4">Edit Location</h2> + <UpdateLocation location={editingLocation} onUpdateCompleted={handleUpdateCompleted} /> + </> )} + <h2 className="text-center mb-4">Locations List</h2> <GetLocations onEditLocation={handleEditLocation} onDeleteLocation={handleDeleteLocation} refresh={refreshLocations} /> </div> ); diff --git a/frontend/src/pages/NormalDetection/NormalDetectionPage.js b/frontend/src/pages/NormalDetection/NormalDetectionPage.js index 0a46aaf..3a2044c 100644 --- a/frontend/src/pages/NormalDetection/NormalDetectionPage.js +++ b/frontend/src/pages/NormalDetection/NormalDetectionPage.js @@ -55,7 +55,7 @@ const NormalDetectionPage = () => { }; return ( - <div className="container mt-5"> + <div className="pages-container-center"> <div className="row justify-content-center"> <div className="col-12 col-md-10 col-lg-8"> <div className="card shadow-lg border-0"> diff --git a/frontend/src/pages/Results/DisplayResultsPage.js b/frontend/src/pages/Results/DisplayResultsPage.js index a4a9ef7..59a8256 100644 --- a/frontend/src/pages/Results/DisplayResultsPage.js +++ b/frontend/src/pages/Results/DisplayResultsPage.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { getImageResults } from '../../services/processMisplacedManagerApi'; +import '../../styles/main.css'; const DisplayResultsPage = () => { const { imageId } = useParams(); @@ -20,7 +21,7 @@ const DisplayResultsPage = () => { } return ( - <div className="container mt-5"> + <div className="pages-container-center"> <div className="row justify-content-center"> <div className="col-md-8"> <h1 className="text-center">Detection Results</h1> diff --git a/frontend/src/pages/Rules/ManageRulesPage.js b/frontend/src/pages/Rules/ManageRulesPage.js index 4699e9d..7ef344d 100644 --- a/frontend/src/pages/Rules/ManageRulesPage.js +++ b/frontend/src/pages/Rules/ManageRulesPage.js @@ -26,7 +26,7 @@ const ManageRulesPage = () => { }; return ( - <div className="manage-rules-page"> + <div className="pages-container-center"> <h1>Manage Rules</h1> <AddRule onRuleAdded={handleRuleAdded} /> {editingRule && ( diff --git a/frontend/src/pages/UserDashboard/UserDashboard.js b/frontend/src/pages/UserDashboard/UserDashboard.js index 38fe637..2bd3f31 100644 --- a/frontend/src/pages/UserDashboard/UserDashboard.js +++ b/frontend/src/pages/UserDashboard/UserDashboard.js @@ -1,13 +1,30 @@ // src/pages/UserDashboard/UserDashboard.js -import React from 'react'; -import { Link } from 'react-router-dom'; +import React, { useEffect, useState } from 'react'; import '../../styles/main.css'; +import '../../styles/dashboard.css'; +import ButtonLink from '../../components/Common/buttons/ButtonLink'; const UserDashboard = () => { + const [username, setUsername] = useState(''); + + useEffect(() => { + // Simulate fetching user data from localStorage or an API + const user = localStorage.getItem('username'); + if (user) { + setUsername(user); + } else { + setUsername('User'); // Default if no username found + } + }, []); + return ( - <div className="user-dashboard-container"> - <h1>User Dashboard</h1> - <Link to="/user/profile" className="btn btn-primary">User Profile</Link> + <div className="pages-container-center"> + <h1 className="dashboard-title">User Dashboard</h1> + <h2 className="dashboard-greeting">Hello, {username}</h2> + <div className="dashboard-buttons-container"> + <ButtonLink to="/user/profile" label="User Profile" /> + {/* Add more ButtonLink components here as needed */} + </div> </div> ); }; diff --git a/frontend/src/pages/UserProfile/UserProfile.js b/frontend/src/pages/UserProfile/UserProfile.js index fd72900..25370fe 100644 --- a/frontend/src/pages/UserProfile/UserProfile.js +++ b/frontend/src/pages/UserProfile/UserProfile.js @@ -1,26 +1,50 @@ // src/pages/UserProfile/UserProfile.js -import React, { useState } from 'react'; -import ChangeEmailSection from '../../components/UserProfile/ChangeEmailSection'; +import React, { useState, useEffect } from 'react'; +import ChangeEmailForm from '../../forms/UserProfile/ChangeEmailForm'; +import ChangeUsernameForm from '../../forms/UserProfile/ChangeUsernameForm'; +import { getCurrentUserEmail, getCurrentUserUsername } from '../../services/userApi'; import '../../styles/main.css'; +import '../../styles/dashboard.css'; const UserProfile = () => { - const [currentEmail, setCurrentEmail] = useState('freddy.imeri1@gmail.com'); // Placeholder, should be fetched from API + const [currentEmail, setCurrentEmail] = useState(''); + const [currentUsername, setCurrentUsername] = useState(''); - 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); + useEffect(() => { + const fetchCurrentUserDetails = async () => { + try { + const emailResponse = await getCurrentUserEmail(); + setCurrentEmail(emailResponse.email); + const usernameResponse = await getCurrentUserUsername(); + setCurrentUsername(usernameResponse.username); + } catch (error) { + console.error('Failed to fetch current user details:', error); + } + }; + + fetchCurrentUserDetails(); + }, []); + + const handleUpdateEmail = async (data) => { + setCurrentEmail(data.newInfo); + }; + + const handleUpdateUsername = async (data) => { + setCurrentUsername(data.newInfo); }; return ( - <div className="container"> - <h1>User Profile</h1> - <ChangeEmailSection + <div className="pages-container-center"> + <h1 className="dashboard-title">User Profile</h1> + <ChangeEmailForm currentEmail={currentEmail} onUpdateEmail={handleUpdateEmail} /> + <ChangeUsernameForm + currentUsername={currentUsername} + onUpdateUsername={handleUpdateUsername} + /> </div> ); }; diff --git a/frontend/src/services/userApi.js b/frontend/src/services/userApi.js index 30fbb91..7877737 100644 --- a/frontend/src/services/userApi.js +++ b/frontend/src/services/userApi.js @@ -1,7 +1,28 @@ -// frontend/src/services/userApi.js +// src/services/userApi.js + import api from './api'; export const getUsers = async () => { const response = await api.get('/api/rules/users/'); return response.data; }; + +export const updateUserEmail = async (emailData) => { + const response = await api.put('/api/user_dashboard/update_email/', emailData); + return response.data; +}; + +export const updateUsername = async (usernameData) => { + const response = await api.put('/api/user_dashboard/update_username/', usernameData); + return response.data; +}; + +export const getCurrentUserEmail = async () => { + const response = await api.get('/api/user_dashboard/email/'); + return response.data; +}; + +export const getCurrentUserUsername = async () => { + const response = await api.get('/api/user_dashboard/username/'); + return response.data; +}; diff --git a/frontend/src/styles/_auth.css b/frontend/src/styles/_auth.css new file mode 100644 index 0000000..af6db5c --- /dev/null +++ b/frontend/src/styles/_auth.css @@ -0,0 +1,42 @@ +/* src/styles/_auth.css */ + + +.auth-form { + background-color: #ffffff; + padding: 3rem; + border-radius: 10px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 500px; +} + +.auth-form h2 { + margin-bottom: 1.5rem; + font-size: 1.5rem; + font-weight: bold; + text-align: center; +} + +.auth-form .form-group { + margin-bottom: 1.5rem; +} + +.auth-form .form-control { + padding: 1rem; + font-size: 1rem; + border-radius: 5px; +} + +.auth-form .btn-primary { + background-color: #007bff; + border-color: #007bff; + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border-radius: 5px; +} + +.auth-form .btn-primary:hover { + background-color: #0056b3; + border-color: #004085; +} \ No newline at end of file diff --git a/frontend/src/styles/_components.css b/frontend/src/styles/_components.css index 7ba7817..4d1467f 100644 --- a/frontend/src/styles/_components.css +++ b/frontend/src/styles/_components.css @@ -1,14 +1,6 @@ /* 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; -} - +/* Shared styles for cards */ .card { width: 100%; max-width: 600px; @@ -26,13 +18,7 @@ text-align: center; } -.btn { - width: 100%; - margin: 0.5rem 0; - padding: 0.75rem; - border-radius: 5px; -} - +/* Button styles */ .btn-primary { background-color: var(--primary-color); border: none; @@ -41,7 +27,7 @@ } .btn-primary:hover { - background-color: #0056b3; + background-color: darken(var(--primary-color), 10%); } .btn-secondary { @@ -52,5 +38,42 @@ } .btn-secondary:hover { - background-color: #5a6268; + background-color: darken(var(--secondary-color), 10%); +} + +.btn-danger { + background-color: var(--danger-color); + border: none; + color: #fff; + transition: background-color 0.3s ease; +} + +.btn-danger:hover { + background-color: darken(var(--danger-color), 10%); +} + +.btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +/* Utility classes */ +.ml-2 { + margin-left: 0.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +/* List styles */ +.list-group-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1.25rem; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; } \ No newline at end of file diff --git a/frontend/src/styles/_global.css b/frontend/src/styles/_global.css index 9add2a4..24cdf8f 100644 --- a/frontend/src/styles/_global.css +++ b/frontend/src/styles/_global.css @@ -1,46 +1,44 @@ /* src/styles/_global.css */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - +/* Reset default margin and padding */ 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; + font-family: Arial, 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; +/* General container styles */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; } -/* Navbar specific styles */ -.navbar .navbar-collapse { - justify-content: flex-end; +/* Utility classes */ +.text-center { + text-align: center; } -.nav-link { - color: #333 !important; - margin-left: 1rem; +.text-danger { + color: #dc3545; + text-align: center; + margin-top: 1rem; } -.nav-link.btn { - cursor: pointer; - border: none; - background: none; - padding: 0; - margin: 0; + +.pages-container-center { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + /* Align items from top to bottom */ + padding-top: 2rem; + /* Adjust padding as needed */ + min-height: 100vh; + /* Ensure it takes the full viewport height */ } \ No newline at end of file diff --git a/frontend/src/styles/_layout.css b/frontend/src/styles/_layout.css index 68668b8..4b0253e 100644 --- a/frontend/src/styles/_layout.css +++ b/frontend/src/styles/_layout.css @@ -1,31 +1,48 @@ /* src/styles/_layout.css */ +/* General container styles */ .container { max-width: 1200px; margin: 0 auto; padding: 2rem; } -.auth-container { +.form-container { + background-color: #ffffff; + padding: 2rem; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; +} + + + + +.full-height-container { display: flex; justify-content: center; - align-items: flex-start; - height: 100vh; - background-color: var(--light-color); - padding-top: 10vh; - width: 100%; + align-items: center; + min-height: 100vh; } -.admin-container { +.manage-content-container { + max-width: 900px; + margin: 0 auto; padding: 2rem; } -.admin-container .list-group { - max-width: 600px; - margin: 0 auto; +.centered-content { + display: flex; + justify-content: center; + align-items: center; + min-height: 80vh; + /* Adjust based on your layout needs */ } -.table-striped th, -.table-striped td { - text-align: center; +.page-container { + display: flex; + flex-direction: column; + min-height: 100vh; + justify-content: center; + align-items: center; } \ No newline at end of file diff --git a/frontend/src/styles/_responsive.css b/frontend/src/styles/_responsive.css index 70a9db7..4bc337a 100644 --- a/frontend/src/styles/_responsive.css +++ b/frontend/src/styles/_responsive.css @@ -1,5 +1,6 @@ /* src/styles/_responsive.css */ +/* Responsive utilities */ @media (max-width: 768px) { .auth-container { padding: 10px; diff --git a/frontend/src/styles/_utilities.css b/frontend/src/styles/_utilities.css index 2c11258..2fb7011 100644 --- a/frontend/src/styles/_utilities.css +++ b/frontend/src/styles/_utilities.css @@ -1,19 +1,30 @@ /* src/styles/_utilities.css */ -.text-center { - text-align: center; +/* Utility classes */ +.m-0 { + margin: 0 !important; } -.text-danger { - color: var(--danger-color); - text-align: center; - margin-top: 1rem; +.p-0 { + padding: 0 !important; } -.m-1 { - margin: var(--spacing-unit); +.mt-1 { + margin-top: 0.25rem !important; } -.p-1 { - padding: var(--spacing-unit); +.mt-2 { + margin-top: 0.5rem !important; +} + +.mt-3 { + margin-top: 1rem !important; +} + +.mt-4 { + margin-top: 1.5rem !important; +} + +.mt-5 { + margin-top: 3rem !important; } \ No newline at end of file diff --git a/frontend/src/styles/buttons/ActionButton.css b/frontend/src/styles/buttons/ActionButton.css new file mode 100644 index 0000000..8fe66e9 --- /dev/null +++ b/frontend/src/styles/buttons/ActionButton.css @@ -0,0 +1,34 @@ +/* src/styles/buttons/ActionButton.css */ +.action-button { + cursor: pointer; + font-weight: 700; + font-family: Helvetica, "sans-serif"; + transition: all .2s; + padding: 15px 40px; + /* Increased padding to make the button wider */ + border-radius: 100px; + background: #cfef00; + border: 1px solid transparent; + display: flex; + align-items: center; + font-size: 15px; +} + +.action-button:hover { + background: #c4e201; +} + +.action-button>svg { + width: 24px; + /* Adjusted size for better alignment */ + margin-left: 10px; + transition: transform .3s ease-in-out; +} + +.action-button:hover svg { + transform: translateX(5px); +} + +.action-button:active { + transform: scale(0.95); +} \ No newline at end of file diff --git a/frontend/src/styles/buttons/ButtonLink.css b/frontend/src/styles/buttons/ButtonLink.css new file mode 100644 index 0000000..2a281c3 --- /dev/null +++ b/frontend/src/styles/buttons/ButtonLink.css @@ -0,0 +1,62 @@ +/* https://getcssscan.com/css-buttons-examples +Code used from the above link and modified as per the requirement +*/ +/* src/components/Common/buttons/ButtonLink.css */ +.button-64 { + align-items: center; + background-image: linear-gradient(144deg, #AF40FF, #5B42F3 50%, #00DDEB); + border: 0; + border-radius: 8px; + box-shadow: rgba(151, 65, 252, 0.2) 0 15px 30px -5px; + box-sizing: border-box; + color: #FFFFFF; + display: flex; + font-family: Phantomsans, sans-serif; + font-size: 20px; + justify-content: center; + line-height: 1em; + max-width: 100%; + min-width: 280px; + /* Increase min-width to make the button wider */ + padding: 3px; + text-decoration: none; + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease-in-out; + /* Add smooth transition */ +} + +.button-64:active, +.button-64:hover { + outline: 0; +} + +.button-64 span { + background-color: rgb(5, 6, 45); + padding: 16px 24px; + border-radius: 6px; + width: 100%; + height: 100%; + transition: 300ms; + display: flex; + /* Add display flex */ + align-items: center; + /* Center items vertically */ + justify-content: center; + /* Center items horizontally */ +} + +.button-64:hover span { + background: none; +} + +@media (min-width: 768px) { + .button-64 { + font-size: 24px; + min-width: 320px; + /* Increase min-width for larger screens */ + } +} \ No newline at end of file diff --git a/frontend/src/styles/buttons/CancelButton.css b/frontend/src/styles/buttons/CancelButton.css new file mode 100644 index 0000000..c7ba948 --- /dev/null +++ b/frontend/src/styles/buttons/CancelButton.css @@ -0,0 +1,63 @@ +/* src/styles/buttons/CancelButton.css */ + +button { + width: 150px; + height: 50px; + cursor: pointer; + display: flex; + align-items: center; + background: red; + border: none; + border-radius: 5px; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.15); + background: #e62222; +} + +button, +button span { + transition: 200ms; +} + +button .text { + transform: translateX(35px); + color: white; + font-weight: bold; +} + +button .icon { + position: absolute; + border-left: 1px solid #c41b1b; + transform: translateX(110px); + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; +} + +button svg { + width: 15px; + fill: #eee; +} + +button:hover { + background: #ff3636; +} + +button:hover .text { + color: transparent; +} + +button:hover .icon { + width: 150px; + border-left: none; + transform: translateX(0); +} + +button:focus { + outline: none; +} + +button:active .icon svg { + transform: scale(0.8); +} \ No newline at end of file diff --git a/frontend/src/styles/buttons/SubmitButton.css b/frontend/src/styles/buttons/SubmitButton.css new file mode 100644 index 0000000..819b0da --- /dev/null +++ b/frontend/src/styles/buttons/SubmitButton.css @@ -0,0 +1,68 @@ +/* src/styles/buttons/SubmitButton.css */ +.submit-button { + width: 300px; + /* Increased width to double the size */ + height: 50px; + cursor: pointer; + display: flex; + align-items: center; + background: green; + border: none; + border-radius: 5px; + box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.15); + background: #28a745; + position: relative; + /* Added position relative for proper alignment of children */ +} + +.submit-button, +.submit-button span { + transition: 200ms; +} + +.submit-button .text { + transform: translateX(75px); + /* Adjusted to match new width */ + color: white; + font-weight: bold; +} + +.submit-button .icon { + position: absolute; + border-left: 1px solid #218838; + transform: translateX(220px); + /* Adjusted to match new width */ + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; +} + +.submit-button svg { + width: 15px; + fill: #eee; +} + +.submit-button:hover { + background: #218838; +} + +.submit-button:hover .text { + color: transparent; +} + +.submit-button:hover .icon { + width: 300px; + /* Adjusted to match new width */ + border-left: none; + transform: translateX(0); +} + +.submit-button:focus { + outline: none; +} + +.submit-button:active .icon svg { + transform: scale(0.8); +} \ No newline at end of file diff --git a/frontend/src/styles/dashboard.css b/frontend/src/styles/dashboard.css new file mode 100644 index 0000000..ceda990 --- /dev/null +++ b/frontend/src/styles/dashboard.css @@ -0,0 +1,53 @@ +/* src/styles/dashboard.css */ + +.dashboard-title { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.dashboard-greeting { + font-size: 1.5rem; + margin-bottom: 2rem; +} + +.dashboard-buttons-container { + display: flex; + flex-direction: column; + align-items: center; +} + +.dashboard-card { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.card-wide { + width: 60%; + max-width: 900px; + 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; +} + +@media (max-width: 768px) { + .card-wide { + width: 80%; + } +} + +@media (max-width: 480px) { + .card-wide { + width: 100%; + } +} \ No newline at end of file diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index 239a070..4ee9db9 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -9,4 +9,5 @@ @import './_utilities.css'; @import './_typography.css'; @import './_forms.css'; -@import './_navbar.css'; \ No newline at end of file +@import './_navbar.css'; +@import './_auth.css'; \ No newline at end of file -- GitLab