diff --git a/rsconcept/backend/apps/users/serializers.py b/rsconcept/backend/apps/users/serializers.py
index cd81331b..6b76243a 100644
--- a/rsconcept/backend/apps/users/serializers.py
+++ b/rsconcept/backend/apps/users/serializers.py
@@ -70,6 +70,17 @@ class UserSerializer(serializers.ModelSerializer):
'last_name',
]
+class ChangePasswordSerializer(serializers.Serializer):
+ """
+ Serializer for password change endpoint.
+ """
+ old_password = serializers.CharField(required=True)
+ new_password = serializers.CharField(required=True)
+
+ # def validate(self, attrs):
+ # if attrs['new_password'] != "123":
+ # raise serializers.ValidationError({"password": r"Пароль не '123'"})
+ # return attrs
class SignupSerializer(serializers.ModelSerializer):
''' User profile create '''
diff --git a/rsconcept/backend/apps/users/urls.py b/rsconcept/backend/apps/users/urls.py
index c80049d0..403f3932 100644
--- a/rsconcept/backend/apps/users/urls.py
+++ b/rsconcept/backend/apps/users/urls.py
@@ -10,4 +10,5 @@ urlpatterns = [
path('api/signup', views.SignupAPIView.as_view()),
path('api/login', views.LoginAPIView.as_view()),
path('api/logout', views.LogoutAPIView.as_view()),
+ path('api/change-password', views.UpdatePassword.as_view()),
]
diff --git a/rsconcept/backend/apps/users/views.py b/rsconcept/backend/apps/users/views.py
index 137dde1a..7c09a51d 100644
--- a/rsconcept/backend/apps/users/views.py
+++ b/rsconcept/backend/apps/users/views.py
@@ -6,6 +6,7 @@ from rest_framework.response import Response
from . import serializers
from . import models
+from django.contrib.auth.models import User
class LoginAPIView(views.APIView):
'''
@@ -74,3 +75,32 @@ class UserProfileAPIView(generics.RetrieveUpdateAPIView):
def get_object(self):
return self.request.user
+
+class UpdatePassword(views.APIView):
+ """
+ An endpoint for changing password.
+ """
+ # {"username": "admin", "password": "1234"}
+ # {"old_password": "1234", "new_password": "1234"}
+ permission_classes = (permissions.IsAuthenticated, )
+
+ def get_object(self, queryset=None):
+ return self.request.user
+
+ def patch(self, request, *args, **kwargs):
+ self.object = self.get_object()
+
+ serializer = serializers.ChangePasswordSerializer(data=request.data)
+
+ if serializer.is_valid():
+ # Check old password
+ old_password = serializer.data.get("old_password")
+ if not self.object.check_password(old_password):
+ return Response({"old_password": ["Wrong password."]},
+ status=status.HTTP_400_BAD_REQUEST)
+ # set_password also hashes the password that the user will get
+ self.object.set_password(serializer.data.get("new_password"))
+ self.object.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
diff --git a/rsconcept/frontend/src/App.tsx b/rsconcept/frontend/src/App.tsx
index ab78ed49..83bbd004 100644
--- a/rsconcept/frontend/src/App.tsx
+++ b/rsconcept/frontend/src/App.tsx
@@ -3,6 +3,7 @@ import { Route, Routes } from 'react-router-dom';
import Footer from './components/Footer';
import Navigation from './components/Navigation/Navigation';
import ToasterThemed from './components/ToasterThemed';
+import { useConceptTheme } from './context/ThemeContext';
import CreateRSFormPage from './pages/CreateRSFormPage';
import HomePage from './pages/HomePage';
import LibraryPage from './pages/LibraryPage';
@@ -15,6 +16,9 @@ import RSFormPage from './pages/RSFormPage';
import UserProfilePage from './pages/UserProfilePage';
function App () {
+ const {noNavigation} = useConceptTheme()
+ const nav_height: string = noNavigation ? "7.5rem" : "7.4rem";
+ const main_clsN: string = `min-h-[calc(100vh-${nav_height})] px-2 h-fit`;
return (
@@ -24,7 +28,7 @@ function App () {
draggable={false}
pauseOnFocusLoss={false}
/>
-
+
} />
diff --git a/rsconcept/frontend/src/context/UserProfileContext.tsx b/rsconcept/frontend/src/context/UserProfileContext.tsx
index d5e20254..6179b18f 100644
--- a/rsconcept/frontend/src/context/UserProfileContext.tsx
+++ b/rsconcept/frontend/src/context/UserProfileContext.tsx
@@ -1,19 +1,22 @@
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { ErrorInfo } from '../components/BackendError';
-import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI';
-import { IUserProfile, IUserUpdateData } from '../utils/models';
+import { DataCallback, getProfile, patchPassword,patchProfile } from '../utils/backendAPI';
+import { IUserProfile, IUserUpdateData, IUserUpdatePassword } from '../utils/models';
+import { useAuth } from './AuthContext';
-interface IUserProfileContextContext {
+interface IUserProfileContext {
user: IUserProfile | undefined
loading: boolean
processing: boolean
error: ErrorInfo
setError: (error: ErrorInfo) => void
updateUser: (data: IUserUpdateData, callback?: DataCallback) => void
+ updatePassword: (data: IUserUpdatePassword, callback?: () => void) => void
}
-const ProfileContext = createContext(null);
+const ProfileContext = createContext(null);
+
export const useUserProfile = () => {
const context = useContext(ProfileContext);
if (!context) {
@@ -33,6 +36,7 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
const [loading, setLoading] = useState(false);
const [processing, setProcessing] = useState(false);
const [error, setError] = useState(undefined);
+ const auth = useAuth()
const reload = useCallback(
() => {
@@ -63,13 +67,29 @@ export const UserProfileState = ({ children }: UserProfileStateProps) => {
}, [setUser]
);
+ const updatePassword = useCallback(
+ (data: IUserUpdatePassword, callback?: () => void) => {
+ setError(undefined);
+ patchPassword({
+ data: data,
+ showError: true,
+ setLoading: setProcessing,
+ onError: error => { setError(error); },
+ onSuccess: () => {
+ setUser(undefined);
+ auth.logout();
+ if (callback) callback();
+ }});
+ }, [setUser, auth]
+ );
+
useEffect(() => {
reload();
}, [reload]);
return (
{children}
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/ChangePassword.tsx b/rsconcept/frontend/src/pages/UserProfilePage/ChangePassword.tsx
new file mode 100644
index 00000000..1c5c7e24
--- /dev/null
+++ b/rsconcept/frontend/src/pages/UserProfilePage/ChangePassword.tsx
@@ -0,0 +1,68 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { toast } from 'react-toastify';
+
+import TextInput from '../../components/Common/TextInput';
+import { useUserProfile } from '../../context/UserProfileContext';
+import { IUserUpdatePassword } from '../../utils/models';
+
+
+export function ChangePassword() {
+ const { updatePassword, processing } = useUserProfile();
+
+ const [old_password, setOldPassword] = useState('');
+ const [new_password, setNewPassword] = useState('');
+ const [new_password_repeat, setNewPasswordRepeat] = useState('');
+ const [password_equal, setPasswordEqual] = useState(true);
+ const navigate = useNavigate();
+
+ const input_class: string = `flex-grow max-w-xl px-3 py-2 border center min-w-full ${password_equal ? "" : "text-red-500"}`
+
+ function handleSubmit(event: React.FormEvent) {
+ event.preventDefault();
+ if (new_password !== new_password_repeat) {
+ setPasswordEqual(false);
+ toast.error('Пароли не совпадают');
+ }
+ else {
+ const data: IUserUpdatePassword = {
+ old_password: old_password,
+ new_password: new_password,
+ };
+ updatePassword(data, () => {toast.success('Изменения сохранены'); navigate('/login')});
+ }
+ }
+
+ return (
+
+ )}
\ No newline at end of file
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserProfile.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserProfile.tsx
index 539da2b4..a4deed6b 100644
--- a/rsconcept/frontend/src/pages/UserProfilePage/UserProfile.tsx
+++ b/rsconcept/frontend/src/pages/UserProfilePage/UserProfile.tsx
@@ -4,15 +4,20 @@ import { toast } from 'react-toastify';
import TextInput from '../../components/Common/TextInput';
import { useUserProfile } from '../../context/UserProfileContext';
import { IUserUpdateData } from '../../utils/models';
+import { ChangePassword } from './ChangePassword';
export function UserProfile() {
- const { updateUser, user, processing } = useUserProfile();
+ const { updateUser, user, processing, error } = useUserProfile();
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [first_name, setFirstName] = useState('');
const [last_name, setLastName] = useState('');
+
+
+ // const [showChangePassword, setShowChangePassword] = useState(false);
+
useLayoutEffect(() => {
if (user) {
@@ -35,8 +40,10 @@ export function UserProfile() {
}
return (
-