diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py index 8c40d78cc2199df51af35cb716fe5798ff05bd8f..ef359568ce8d4d235b08a694ad5f7b412a9b5e97 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 b40dfe45ca97da52dd2d2ab32ddac2e1038cb9e9..3a063da5d825eba05740c0cc03a6a0a92d5c7b29 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 aa7f556a6b54adf37879edd8bae44b8f9b9d7b0b..416a509e6bc4be1a7b44b69d14945213092d009f 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 781add9d1fbd9f0ae69d2d8d2aad77edb1f84367..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..6b48ed4b2827a48b33544edbc5a9c7cb29f1c86f --- /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 0000000000000000000000000000000000000000..7695ae5cb0a2fcc5e66a47edb96b4ec88eceaa90 --- /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 0000000000000000000000000000000000000000..433877591c1617ce1f1881a5dd7156e4860762e0 --- /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 0000000000000000000000000000000000000000..6fea63b479f65b4f229b063ab21067cf1fffdf26 --- /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 f392d9dd52955cbc4e3197d8b865fcdd9e6a8f75..548b8ad79d99aad048f726f2a4ce553bb88455b4 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 acfafe8fe5b2f0f866fbde842fd24bf353a0453f..4dbb289c237dab3c95bdad1af8f3898ad229c311 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 24530fb8e4196e4be29b3efc9d61af66ff76b1f2..0000000000000000000000000000000000000000 --- 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 ffa4932707743a5e2dd65d97f009ec4b74c5097a..390f35831946476159ffe522025f4b836961ffe7 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 5d353d40e703c5f4bfd8ecfca841a9eae8d23ac9..0000000000000000000000000000000000000000 --- 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 3759599ee34c302f538a2848d045451735fc5a01..28ff36d62a5b2381119382ff71109448ac6c1faf 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 8343c42ab8ef006107aafa30269d27df16bb28be..0000000000000000000000000000000000000000 --- 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 18bcd7617c1f83c266a6649e3951d5704bf7086f..8334233f5abc2a1df0cbb317955e4ed83521564b 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 50ffc25f784e2821920ad4555c62fce87e8d3b87..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..aab2870e367af53c94fbfc56a79e322cdac1d46e --- /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 7e6ea9aaa6a5534b26f579f3966d482497c1d2b0..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..c2f7c3dc34dab01112de3115101c0dc4aecbb923 --- /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 0000000000000000000000000000000000000000..737338a9eef48c736df1b038b30e449b1cfd7afe --- /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 4f94607697d77b59c854555558294f59195bd5e4..6899fa30c198f25ad27e93af333ae622f9ca7e1a 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 cc9023e87a7210004ab1771a5520a7a3762abada..930e31c8ce8c6141032dcdcae7ad6ed1cabf5c59 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 e1e56137dd080f851a634c113e79c67a6d046bbd..41e5e8a1322d711bcb357f6af3d2e9177987c52e 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 aaefbd0c7978e23b091c907a8ca7fad6948ccbcd..63db5cf44bea5a6ff403a75bb30b48e088f746d7 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 85249754daf86f281e03697ed9bbe3918f30c65c..c752e286641f82a07d1b4e16b3356691af865bbc 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 0000000000000000000000000000000000000000..07b515eb733bb87770728377aa08e4dce540530c --- /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 76d65f32b8f708d04956df7150f5d2798c418f93..0e5770b8dd2a319901baa0a610dab6b64350b655 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 e8c0212b5e8f0ebd48163a9e5eb22f6b1f0900a4..a5176c46e284747db8b1a54af0f6340e8d6bc815 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 e904663d4815c0995e024193f829f8fbcbaf8fd8..f26affaf8e21652181d78758b765d93d2918ba41 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 2a899e203935232c10bfae055c57ac400ca472d6..ad39f78d6dd59dd116cdf12d2904b106130b36c6 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 c727cda02f262a010429aecd802bcc5b6d5bc68f..8d48ca67ee80a84c53d19adb2121419161da3a66 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 3dd29ae4f4607f53ce2402ce0f0c269011160a93..86502d62a82aee6d2825cc6266a590b566e534a5 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 55d02615d552718817df380dfb11b9ddab85d852..81f050cc5e3d2f31af01ada852d4dbe941434aaf 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 edc983f0b55ceced00eb255c9709331ac300ad35..a67b33e58a5d466a57fc1c683340fd7a84711d91 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 04e0bfc27f6b080f5d54fea40c9fa2a90359616e..f09308bda7de4f6b73eb85d01a3782a773492978 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 0a46aaf88a4f2132555dfe29b2c89dff97551e1e..3a2044c3db8cc1b4efac341039a8c3931c0b528f 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 a4a9ef74b55e6181cadc22521e9822f0686f45ba..59a825658b3e9b4bd9854174275f709d7a3d3d7a 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 4699e9d1598fb08f2e2e0c97e671f61d49b3727b..7ef344dc5943d6959de4cac36864de941c25b6ee 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 38fe6379f8254cc05074deabdb953832f7f84672..2bd3f31775aedf7b2817b1547464bdcbdef6df1a 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 fd729007566c5984f25c9edc53a3eaabbe325400..25370feb2ec3679396e0283a1e41bac529e56d59 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 30fbb91f97c5849e8084a7d5ee7207aed078da6f..787773780140cf5c3f424c3873c000028e29673c 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 0000000000000000000000000000000000000000..af6db5c81559849030c214455b2b871111fd7b13 --- /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 7ba7817bc817df2ad4629bf61c3511d8c749a145..4d1467f882faeb0a290ed7dc2dd262f9ef0e43b6 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 9add2a489ca28d61fa842b6e9c328adbaa59094f..24cdf8fd402d3f49ea8cf1973960a470310cfa48 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 68668b86747e510d5957e2f2b753775f161dad5c..4b0253e88136fff9045f184e31618b831e96b20a 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 70a9db74e42ec9d9c0f550fc76db51e0e7dfe209..4bc337a0d6e1bf1670358117c1674bb1712ba930 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 2c11258ece5dac196ec5919910d7eea38bf1021a..2fb70111d81b9ee18930e578e2efe4153c4b3e15 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 0000000000000000000000000000000000000000..8fe66e9a410a49405edc5d3fc5d9c84ef2f714bd --- /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 0000000000000000000000000000000000000000..2a281c3467d5b7413ebcb7b967ad6e21917a67b3 --- /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 0000000000000000000000000000000000000000..c7ba948bc33e7be17a5378211f05540d4496e395 --- /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 0000000000000000000000000000000000000000..819b0da76d5411bbc137de4c328ca09c4fb84ee6 --- /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 0000000000000000000000000000000000000000..ceda990611c3bec90fb70898ae07047f8697ed53 --- /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 239a070010b03e9e8240f13bb7469cd58d98ede4..4ee9db96a5bfa45cef65e248582e05d7a5b3380c 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