From 76aee5bea74d8936231c7c0ac98fdf4418cb6d61 Mon Sep 17 00:00:00 2001
From: Ivan <8611739+IRBorisov@users.noreply.github.com>
Date: Tue, 21 Jan 2025 20:33:05 +0300
Subject: [PATCH] F: Implement react-query pt2
---
.../frontend/src/app/GlobalProviders.tsx | 3 -
.../src/app/Navigation/Navigation.tsx | 4 +-
.../src/app/Navigation/UserButton.tsx | 35 +++
.../src/app/Navigation/UserDropdown.tsx | 6 +-
.../frontend/src/app/Navigation/UserMenu.tsx | 33 +--
.../frontend/src/backend/apiTransport.ts | 40 ++--
rsconcept/frontend/src/backend/auth/api.ts | 67 ++++++
.../frontend/src/backend/auth/useAuth.tsx | 21 ++
.../src/backend/auth/useChangePassword.tsx | 18 ++
.../frontend/src/backend/auth/useLogin.tsx | 19 ++
.../frontend/src/backend/auth/useLogout.tsx | 13 ++
.../backend/auth/useRequestPasswordReset.tsx | 16 ++
.../src/backend/auth/useResetPassword.tsx | 24 +++
rsconcept/frontend/src/backend/users.ts | 99 ---------
rsconcept/frontend/src/backend/users/api.ts | 38 +++-
.../frontend/src/backend/users/useProfile.tsx | 21 ++
.../frontend/src/backend/users/useSignup.tsx | 22 ++
.../src/backend/users/useUpdateProfile.tsx | 23 ++
.../frontend/src/backend/users/useUsers.tsx | 13 +-
.../src/components/ui/SubmitButton.tsx | 2 +-
.../src/components/wrap/ExpectedAnonymous.tsx | 6 +-
.../src/components/wrap/RequireAuth.tsx | 6 +-
.../frontend/src/context/AuthContext.tsx | 204 ------------------
.../frontend/src/context/LibraryContext.tsx | 5 +-
rsconcept/frontend/src/context/OssContext.tsx | 2 +-
.../frontend/src/context/RSFormContext.tsx | 2 +-
.../src/context/UserProfileContext.tsx | 76 -------
.../src/dialogs/DlgChangeLocation.tsx | 2 +-
.../src/dialogs/DlgCloneLibraryItem.tsx | 2 +-
rsconcept/frontend/src/hooks/useOssDetails.ts | 4 +-
.../frontend/src/hooks/useRSFormDetails.ts | 4 +-
rsconcept/frontend/src/models/user.ts | 39 ----
.../pages/CreateItemPage/FormCreateItem.tsx | 2 +-
rsconcept/frontend/src/pages/HomePage.tsx | 8 +-
.../pages/LibraryPage/ViewSideLocation.tsx | 2 +-
rsconcept/frontend/src/pages/LoginPage.tsx | 21 +-
.../src/pages/OssPage/MenuOssTabs.tsx | 2 +-
.../src/pages/OssPage/OssEditContext.tsx | 2 +-
.../frontend/src/pages/OssPage/OssTabs.tsx | 4 +-
.../frontend/src/pages/PasswordChangePage.tsx | 23 +-
.../src/pages/RSFormPage/MenuRSTabs.tsx | 2 +-
.../src/pages/RSFormPage/RSEditContext.tsx | 2 +-
.../src/pages/RegisterPage/FormSignup.tsx | 15 +-
.../src/pages/RegisterPage/RegisterPage.tsx | 6 +-
.../src/pages/RestorePasswordPage.tsx | 18 +-
.../pages/UserProfilePage/EditorPassword.tsx | 16 +-
.../pages/UserProfilePage/EditorProfile.tsx | 39 ++--
.../pages/UserProfilePage/UserContents.tsx | 25 ---
.../pages/UserProfilePage/UserProfilePage.tsx | 21 +-
rsconcept/frontend/src/utils/labels.ts | 3 +-
50 files changed, 460 insertions(+), 620 deletions(-)
create mode 100644 rsconcept/frontend/src/app/Navigation/UserButton.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/api.ts
create mode 100644 rsconcept/frontend/src/backend/auth/useAuth.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/useChangePassword.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/useLogin.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/useLogout.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/useRequestPasswordReset.tsx
create mode 100644 rsconcept/frontend/src/backend/auth/useResetPassword.tsx
delete mode 100644 rsconcept/frontend/src/backend/users.ts
create mode 100644 rsconcept/frontend/src/backend/users/useProfile.tsx
create mode 100644 rsconcept/frontend/src/backend/users/useSignup.tsx
create mode 100644 rsconcept/frontend/src/backend/users/useUpdateProfile.tsx
delete mode 100644 rsconcept/frontend/src/context/AuthContext.tsx
delete mode 100644 rsconcept/frontend/src/context/UserProfileContext.tsx
delete mode 100644 rsconcept/frontend/src/pages/UserProfilePage/UserContents.tsx
diff --git a/rsconcept/frontend/src/app/GlobalProviders.tsx b/rsconcept/frontend/src/app/GlobalProviders.tsx
index e46b530b..2dd21953 100644
--- a/rsconcept/frontend/src/app/GlobalProviders.tsx
+++ b/rsconcept/frontend/src/app/GlobalProviders.tsx
@@ -6,7 +6,6 @@ import { ErrorBoundary } from 'react-error-boundary';
import { IntlProvider } from 'react-intl';
import { queryClient } from '@/backend/queryClient';
-import { AuthState } from '@/context/AuthContext';
import { GlobalOssState } from '@/context/GlobalOssContext';
import { LibraryState } from '@/context/LibraryContext';
@@ -33,7 +32,6 @@ function GlobalProviders({ children }: React.PropsWithChildren) {
>
-
@@ -42,7 +40,6 @@ function GlobalProviders({ children }: React.PropsWithChildren) {
-
);
diff --git a/rsconcept/frontend/src/app/Navigation/Navigation.tsx b/rsconcept/frontend/src/app/Navigation/Navigation.tsx
index 224c1231..571e7a87 100644
--- a/rsconcept/frontend/src/app/Navigation/Navigation.tsx
+++ b/rsconcept/frontend/src/app/Navigation/Navigation.tsx
@@ -29,7 +29,8 @@ function Navigation() {
className={clsx(
'z-navigation', // prettier: split lines
'sticky top-0 left-0 right-0',
- 'select-none'
+ 'select-none',
+ 'bg-prim-100'
)}
>
@@ -57,6 +58,7 @@ function Navigation() {
} onClick={navigateCreateNew} />
} onClick={navigateLibrary} />
} onClick={navigateHelp} />
+
diff --git a/rsconcept/frontend/src/app/Navigation/UserButton.tsx b/rsconcept/frontend/src/app/Navigation/UserButton.tsx
new file mode 100644
index 00000000..ff13baf0
--- /dev/null
+++ b/rsconcept/frontend/src/app/Navigation/UserButton.tsx
@@ -0,0 +1,35 @@
+import { useAuthSuspense } from '@/backend/auth/useAuth';
+import { IconLogin, IconUser2 } from '@/components/Icons';
+import { usePreferencesStore } from '@/stores/preferences';
+
+import NavigationButton from './NavigationButton';
+
+interface UserButtonProps {
+ onLogin: () => void;
+ onClickUser: () => void;
+}
+
+function UserButton({ onLogin, onClickUser }: UserButtonProps) {
+ const { user } = useAuthSuspense();
+ const adminMode = usePreferencesStore(state => state.adminMode);
+ if (!user) {
+ return (
+ }
+ onClick={onLogin}
+ />
+ );
+ } else {
+ return (
+ }
+ onClick={onClickUser}
+ />
+ );
+ }
+}
+
+export default UserButton;
diff --git a/rsconcept/frontend/src/app/Navigation/UserDropdown.tsx b/rsconcept/frontend/src/app/Navigation/UserDropdown.tsx
index 4284f515..16b77812 100644
--- a/rsconcept/frontend/src/app/Navigation/UserDropdown.tsx
+++ b/rsconcept/frontend/src/app/Navigation/UserDropdown.tsx
@@ -1,3 +1,5 @@
+import { useAuth } from '@/backend/auth/useAuth';
+import { useLogout } from '@/backend/auth/useLogout';
import {
IconAdmin,
IconAdminOff,
@@ -15,7 +17,6 @@ import {
import { CProps } from '@/components/props';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { usePreferencesStore } from '@/stores/preferences';
@@ -28,7 +29,8 @@ interface UserDropdownProps {
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
const router = useConceptNavigation();
- const { user, logout } = useAuth();
+ const { user } = useAuth();
+ const { logout } = useLogout();
const darkMode = usePreferencesStore(state => state.darkMode);
const toggleDarkMode = usePreferencesStore(state => state.toggleDarkMode);
diff --git a/rsconcept/frontend/src/app/Navigation/UserMenu.tsx b/rsconcept/frontend/src/app/Navigation/UserMenu.tsx
index 236336da..a36499aa 100644
--- a/rsconcept/frontend/src/app/Navigation/UserMenu.tsx
+++ b/rsconcept/frontend/src/app/Navigation/UserMenu.tsx
@@ -1,41 +1,22 @@
-import { IconLogin, IconUser2 } from '@/components/Icons';
+import { Suspense } from 'react';
+
import Loader from '@/components/ui/Loader';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import useDropdown from '@/hooks/useDropdown';
-import { usePreferencesStore } from '@/stores/preferences';
import { urls } from '../urls';
-import NavigationButton from './NavigationButton';
+import UserButton from './UserButton';
import UserDropdown from './UserDropdown';
function UserMenu() {
const router = useConceptNavigation();
- const { user, loading } = useAuth();
- const adminMode = usePreferencesStore(state => state.adminMode);
const menu = useDropdown();
-
- const navigateLogin = () => router.push(urls.login);
-
return (
- {loading ? : null}
- {!user && !loading ? (
- }
- onClick={navigateLogin}
- />
- ) : null}
- {user && !loading ? (
- }
- onClick={menu.toggle}
- />
- ) : null}
- menu.hide()} />
+ }>
+ router.push(urls.login)} onClickUser={menu.toggle} />
+
+ menu.hide()} />
);
}
diff --git a/rsconcept/frontend/src/backend/apiTransport.ts b/rsconcept/frontend/src/backend/apiTransport.ts
index 3e29bc54..af21e549 100644
--- a/rsconcept/frontend/src/backend/apiTransport.ts
+++ b/rsconcept/frontend/src/backend/apiTransport.ts
@@ -42,17 +42,17 @@ export interface IAxiosRequest {
// ================ Transport API calls ================
export function AxiosGet({ endpoint, request, options }: IAxiosRequest) {
- if (request.setLoading) request.setLoading(true);
+ request.setLoading?.(true);
axiosInstance
.get(endpoint, options)
.then(response => {
- if (request.setLoading) request.setLoading(false);
- if (request.onSuccess) request.onSuccess(response.data);
+ request.setLoading?.(false);
+ request.onSuccess?.(response.data);
})
.catch((error: Error | AxiosError) => {
- if (request.setLoading) request.setLoading(false);
+ request.setLoading?.(false);
if (request.showError) toast.error(extractErrorMessage(error));
- if (request.onError) request.onError(error);
+ request.onError?.(error);
});
}
@@ -61,17 +61,17 @@ export function AxiosPost({
request,
options
}: IAxiosRequest) {
- if (request.setLoading) request.setLoading(true);
+ request.setLoading?.(true);
axiosInstance
.post(endpoint, request.data, options)
.then(response => {
- if (request.setLoading) request.setLoading(false);
- if (request.onSuccess) request.onSuccess(response.data);
+ request.setLoading?.(false);
+ request.onSuccess?.(response.data);
})
.catch((error: Error | AxiosError) => {
- if (request.setLoading) request.setLoading(false);
+ request.setLoading?.(false);
if (request.showError) toast.error(extractErrorMessage(error));
- if (request.onError) request.onError(error);
+ request.onError?.(error);
});
}
@@ -80,17 +80,17 @@ export function AxiosDelete({
request,
options
}: IAxiosRequest) {
- if (request.setLoading) request.setLoading(true);
+ request.setLoading?.(true);
axiosInstance
.delete(endpoint, options)
.then(response => {
- if (request.setLoading) request.setLoading(false);
- if (request.onSuccess) request.onSuccess(response.data);
+ request.setLoading?.(false);
+ request.onSuccess?.(response.data);
})
.catch((error: Error | AxiosError) => {
- if (request.setLoading) request.setLoading(false);
+ request.setLoading?.(false);
if (request.showError) toast.error(extractErrorMessage(error));
- if (request.onError) request.onError(error);
+ request.onError?.(error);
});
}
@@ -99,17 +99,17 @@ export function AxiosPatch({
request,
options
}: IAxiosRequest) {
- if (request.setLoading) request.setLoading(true);
+ request.setLoading?.(true);
axiosInstance
.patch(endpoint, request.data, options)
.then(response => {
- if (request.setLoading) request.setLoading(false);
- if (request.onSuccess) request.onSuccess(response.data);
+ request.setLoading?.(false);
+ request.onSuccess?.(response.data);
return response.data;
})
.catch((error: Error | AxiosError) => {
- if (request.setLoading) request.setLoading(false);
+ request.setLoading?.(false);
if (request.showError) toast.error(extractErrorMessage(error));
- if (request.onError) request.onError(error);
+ request.onError?.(error);
});
}
diff --git a/rsconcept/frontend/src/backend/auth/api.ts b/rsconcept/frontend/src/backend/auth/api.ts
new file mode 100644
index 00000000..4d543c1d
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/api.ts
@@ -0,0 +1,67 @@
+import { queryOptions } from '@tanstack/react-query';
+
+import { ICurrentUser, IUser } from '@/models/user';
+
+import { axiosInstance } from '../apiConfiguration';
+
+/**
+ * Represents login data, used to authenticate users.
+ */
+export interface IUserLoginDTO {
+ username: string;
+ password: string;
+}
+
+/**
+ * Represents data needed to update password for current user.
+ */
+export interface IChangePasswordDTO {
+ old_password: string;
+ new_password: string;
+}
+
+/**
+ * Represents password reset request data.
+ */
+export interface IRequestPasswordDTO extends Pick {}
+
+/**
+ * Represents password reset data.
+ */
+export interface IResetPasswordDTO {
+ password: string;
+ token: string;
+}
+
+/**
+ * Represents password token data.
+ */
+export interface IPasswordTokenDTO extends Pick {}
+
+/**
+ * Authentication API.
+ */
+export const authApi = {
+ baseKey: 'auth',
+
+ getAuthQueryOptions: () => {
+ return queryOptions({
+ queryKey: [authApi.baseKey, 'user'],
+ queryFn: meta =>
+ axiosInstance
+ .get('/users/api/auth', {
+ signal: meta.signal
+ })
+ .then(response => (response.data.id === null ? null : response.data)),
+ placeholderData: null,
+ staleTime: 24 * 60 * 60 * 1000
+ });
+ },
+
+ logout: () => axiosInstance.post('/users/api/logout'),
+ login: (data: IUserLoginDTO) => axiosInstance.post('/users/api/login', data),
+ changePassword: (data: IChangePasswordDTO) => axiosInstance.post('/users/api/change-password', data),
+ requestPasswordReset: (data: IRequestPasswordDTO) => axiosInstance.post('/users/api/password-reset', data),
+ validatePasswordToken: (data: IPasswordTokenDTO) => axiosInstance.post('/users/api/password-reset/validate', data),
+ resetPassword: (data: IResetPasswordDTO) => axiosInstance.post('/users/api/password-reset/confirm', data)
+};
diff --git a/rsconcept/frontend/src/backend/auth/useAuth.tsx b/rsconcept/frontend/src/backend/auth/useAuth.tsx
new file mode 100644
index 00000000..14f86f2e
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useAuth.tsx
@@ -0,0 +1,21 @@
+import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
+
+import { authApi } from './api';
+
+export function useAuth() {
+ const {
+ data: user,
+ isLoading,
+ error
+ } = useQuery({
+ ...authApi.getAuthQueryOptions()
+ });
+ return { user, isLoading, error };
+}
+
+export function useAuthSuspense() {
+ const { data: user } = useSuspenseQuery({
+ ...authApi.getAuthQueryOptions()
+ });
+ return { user };
+}
diff --git a/rsconcept/frontend/src/backend/auth/useChangePassword.tsx b/rsconcept/frontend/src/backend/auth/useChangePassword.tsx
new file mode 100644
index 00000000..79c00fc5
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useChangePassword.tsx
@@ -0,0 +1,18 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { authApi, IChangePasswordDTO } from './api';
+
+export const useChangePassword = () => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationKey: ['change-password'],
+ mutationFn: authApi.changePassword,
+ onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
+ });
+ return {
+ changePassword: (data: IChangePasswordDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
+ isPending: mutation.isPending,
+ error: mutation.error,
+ reset: mutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/auth/useLogin.tsx b/rsconcept/frontend/src/backend/auth/useLogin.tsx
new file mode 100644
index 00000000..9924a2ad
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useLogin.tsx
@@ -0,0 +1,19 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { authApi } from './api';
+
+export const useLogin = () => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationKey: ['login'],
+ mutationFn: authApi.login,
+ onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
+ });
+ return {
+ login: (username: string, password: string, onSuccess?: () => void) =>
+ mutation.mutate({ username, password }, { onSuccess }),
+ isPending: mutation.isPending,
+ error: mutation.error,
+ reset: mutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/auth/useLogout.tsx b/rsconcept/frontend/src/backend/auth/useLogout.tsx
new file mode 100644
index 00000000..f0f9b252
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useLogout.tsx
@@ -0,0 +1,13 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { authApi } from './api';
+
+export const useLogout = () => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationKey: ['logout'],
+ mutationFn: authApi.logout,
+ onSettled: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
+ });
+ return { logout: (onSuccess?: () => void) => mutation.mutate(undefined, { onSuccess }) };
+};
diff --git a/rsconcept/frontend/src/backend/auth/useRequestPasswordReset.tsx b/rsconcept/frontend/src/backend/auth/useRequestPasswordReset.tsx
new file mode 100644
index 00000000..f31d5118
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useRequestPasswordReset.tsx
@@ -0,0 +1,16 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { authApi, IRequestPasswordDTO } from './api';
+
+export const useRequestPasswordReset = () => {
+ const mutation = useMutation({
+ mutationKey: ['request-password-reset'],
+ mutationFn: authApi.requestPasswordReset
+ });
+ return {
+ requestPasswordReset: (data: IRequestPasswordDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
+ isPending: mutation.isPending,
+ error: mutation.error,
+ reset: mutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/auth/useResetPassword.tsx b/rsconcept/frontend/src/backend/auth/useResetPassword.tsx
new file mode 100644
index 00000000..683c05c6
--- /dev/null
+++ b/rsconcept/frontend/src/backend/auth/useResetPassword.tsx
@@ -0,0 +1,24 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { authApi, IPasswordTokenDTO, IResetPasswordDTO } from './api';
+
+export const useResetPassword = () => {
+ const queryClient = useQueryClient();
+ const validateMutation = useMutation({
+ mutationKey: ['reset-password'],
+ mutationFn: authApi.validatePasswordToken,
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
+ });
+ const resetMutation = useMutation({
+ mutationKey: ['reset-password'],
+ mutationFn: authApi.resetPassword,
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: [authApi.baseKey] })
+ });
+ return {
+ validateToken: (data: IPasswordTokenDTO, onSuccess?: () => void) => validateMutation.mutate(data, { onSuccess }),
+ resetPassword: (data: IResetPasswordDTO, onSuccess?: () => void) => resetMutation.mutate(data, { onSuccess }),
+ isPending: resetMutation.isPending,
+ error: resetMutation.error,
+ reset: resetMutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/users.ts b/rsconcept/frontend/src/backend/users.ts
deleted file mode 100644
index eda6ef97..00000000
--- a/rsconcept/frontend/src/backend/users.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Endpoints: users.
- */
-
-import {
- ICurrentUser,
- IPasswordTokenData,
- IRequestPasswordData,
- IResetPasswordData,
- IUserInfo,
- IUserLoginData,
- IUserProfile,
- IUserSignupData,
- IUserUpdateData,
- IUserUpdatePassword
-} from '@/models/user';
-
-import { AxiosGet, AxiosPatch, AxiosPost, FrontAction, FrontExchange, FrontPull, FrontPush } from './apiTransport';
-
-export function getAuth(request: FrontPull) {
- AxiosGet({
- endpoint: `/users/api/auth`,
- request: request
- });
-}
-
-export function postLogin(request: FrontPush) {
- AxiosPost({
- endpoint: '/users/api/login',
- request: request
- });
-}
-
-export function postLogout(request: FrontAction) {
- AxiosPost({
- endpoint: '/users/api/logout',
- request: request
- });
-}
-
-export function postSignup(request: FrontExchange) {
- AxiosPost({
- endpoint: '/users/api/signup',
- request: request
- });
-}
-
-export function getProfile(request: FrontPull) {
- AxiosGet({
- endpoint: '/users/api/profile',
- request: request
- });
-}
-
-export function patchProfile(request: FrontExchange) {
- AxiosPatch({
- endpoint: '/users/api/profile',
- request: request
- });
-}
-
-export function patchPassword(request: FrontPush) {
- AxiosPatch({
- endpoint: '/users/api/change-password',
- request: request
- });
-}
-
-export function postRequestPasswordReset(request: FrontPush) {
- // title: 'Request password reset',
- AxiosPost({
- endpoint: '/users/api/password-reset',
- request: request
- });
-}
-
-export function postValidatePasswordToken(request: FrontPush) {
- // title: 'Validate password token',
- AxiosPost({
- endpoint: '/users/api/password-reset/validate',
- request: request
- });
-}
-
-export function postResetPassword(request: FrontPush) {
- // title: 'Reset password',
- AxiosPost({
- endpoint: '/users/api/password-reset/confirm',
- request: request
- });
-}
-
-export function getActiveUsers(request: FrontPull) {
- // title: 'Active users list',
- AxiosGet({
- endpoint: '/users/api/active-users',
- request: request
- });
-}
diff --git a/rsconcept/frontend/src/backend/users/api.ts b/rsconcept/frontend/src/backend/users/api.ts
index ffabbd30..b20983e9 100644
--- a/rsconcept/frontend/src/backend/users/api.ts
+++ b/rsconcept/frontend/src/backend/users/api.ts
@@ -1,18 +1,40 @@
import { queryOptions } from '@tanstack/react-query';
-import { IUserInfo } from '@/models/user';
+import { IUser, IUserInfo, IUserProfile, IUserSignupData } from '@/models/user';
import { axiosInstance } from '../apiConfiguration';
+/**
+ * Represents user data, intended to update user profile in persistent storage.
+ */
+export interface IUpdateProfileDTO extends Omit {}
+
export const usersApi = {
baseKey: 'users',
- getUsersQueryOptions: () => {
- return queryOptions({
+ getUsersQueryOptions: () =>
+ queryOptions({
queryKey: [usersApi.baseKey, 'list'],
queryFn: meta =>
- axiosInstance.get(`/users/api/active-users`, {
- signal: meta.signal
- })
- });
- }
+ axiosInstance
+ .get('/users/api/active-users', {
+ signal: meta.signal
+ })
+ .then(response => response.data),
+ placeholderData: []
+ }),
+ getProfileQueryOptions: () =>
+ queryOptions({
+ queryKey: [usersApi.baseKey, 'profile'],
+ queryFn: meta =>
+ axiosInstance
+ .get('/users/api/profile', {
+ signal: meta.signal
+ })
+ .then(response => response.data)
+ }),
+
+ signup: (data: IUserSignupData) => axiosInstance.post('/users/api/signup', data),
+ updateProfile: (data: IUpdateProfileDTO) => axiosInstance.patch('/users/api/profile', data)
};
+
+//DataCallback
diff --git a/rsconcept/frontend/src/backend/users/useProfile.tsx b/rsconcept/frontend/src/backend/users/useProfile.tsx
new file mode 100644
index 00000000..f74802a3
--- /dev/null
+++ b/rsconcept/frontend/src/backend/users/useProfile.tsx
@@ -0,0 +1,21 @@
+import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
+
+import { usersApi } from './api';
+
+export function useProfile() {
+ const {
+ data: profile,
+ isLoading,
+ error
+ } = useQuery({
+ ...usersApi.getProfileQueryOptions()
+ });
+ return { profile, isLoading, error };
+}
+
+export function useProfileSuspense() {
+ const { data: profile } = useSuspenseQuery({
+ ...usersApi.getProfileQueryOptions()
+ });
+ return { profile };
+}
diff --git a/rsconcept/frontend/src/backend/users/useSignup.tsx b/rsconcept/frontend/src/backend/users/useSignup.tsx
new file mode 100644
index 00000000..777c6fcf
--- /dev/null
+++ b/rsconcept/frontend/src/backend/users/useSignup.tsx
@@ -0,0 +1,22 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { usersApi } from '@/backend/users/api';
+import { IUserProfile, IUserSignupData } from '@/models/user';
+
+import { DataCallback } from '../apiTransport';
+
+export const useSignup = () => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationKey: ['signup'],
+ mutationFn: usersApi.signup,
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: [usersApi.baseKey] })
+ });
+ return {
+ signup: (data: IUserSignupData, onSuccess?: DataCallback) =>
+ mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }),
+ isPending: mutation.isPending,
+ error: mutation.error,
+ reset: mutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/users/useUpdateProfile.tsx b/rsconcept/frontend/src/backend/users/useUpdateProfile.tsx
new file mode 100644
index 00000000..051f5ac1
--- /dev/null
+++ b/rsconcept/frontend/src/backend/users/useUpdateProfile.tsx
@@ -0,0 +1,23 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { IUserProfile } from '@/models/user';
+
+import { IUpdateProfileDTO, usersApi } from './api';
+
+// TODO: reload users / optimistic update
+
+export const useUpdateProfile = () => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationKey: ['update-profile'],
+ mutationFn: usersApi.updateProfile,
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: [usersApi.baseKey] })
+ });
+ return {
+ updateProfile: (data: IUpdateProfileDTO, onSuccess?: (newUser: IUserProfile) => void) =>
+ mutation.mutate(data, { onSuccess: response => onSuccess?.(response.data as IUserProfile) }),
+ isPending: mutation.isPending,
+ error: mutation.error,
+ reset: mutation.reset
+ };
+};
diff --git a/rsconcept/frontend/src/backend/users/useUsers.tsx b/rsconcept/frontend/src/backend/users/useUsers.tsx
index 8312fe89..d4172871 100644
--- a/rsconcept/frontend/src/backend/users/useUsers.tsx
+++ b/rsconcept/frontend/src/backend/users/useUsers.tsx
@@ -3,17 +3,16 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { usersApi } from './api';
export function useUsersSuspense() {
- const { data: users, refetch } = useSuspenseQuery({
- ...usersApi.getUsersQueryOptions()
+ const { data: users } = useSuspenseQuery({
+ ...usersApi.getUsersQueryOptions(),
+ initialData: []
});
-
- return { users: users?.data ?? [], refetch };
+ return { users };
}
export function useUsers() {
- const { data: users, refetch } = useQuery({
+ const { data: users } = useQuery({
...usersApi.getUsersQueryOptions()
});
-
- return { users: users?.data ?? [], refetch };
+ return { users: users ?? [] };
}
diff --git a/rsconcept/frontend/src/components/ui/SubmitButton.tsx b/rsconcept/frontend/src/components/ui/SubmitButton.tsx
index 815ba686..6cf3911e 100644
--- a/rsconcept/frontend/src/components/ui/SubmitButton.tsx
+++ b/rsconcept/frontend/src/components/ui/SubmitButton.tsx
@@ -29,7 +29,7 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...re
loading && 'cursor-progress',
className
)}
- disabled={disabled ?? loading}
+ disabled={disabled || loading}
{...restProps}
>
{icon ? {icon} : null}
diff --git a/rsconcept/frontend/src/components/wrap/ExpectedAnonymous.tsx b/rsconcept/frontend/src/components/wrap/ExpectedAnonymous.tsx
index ce5f9428..6889419d 100644
--- a/rsconcept/frontend/src/components/wrap/ExpectedAnonymous.tsx
+++ b/rsconcept/frontend/src/components/wrap/ExpectedAnonymous.tsx
@@ -1,11 +1,13 @@
import { urls } from '@/app/urls';
-import { useAuth } from '@/context/AuthContext';
+import { useAuth } from '@/backend/auth/useAuth';
+import { useLogout } from '@/backend/auth/useLogout';
import { useConceptNavigation } from '@/context/NavigationContext';
import TextURL from '../ui/TextURL';
function ExpectedAnonymous() {
- const { user, logout } = useAuth();
+ const { user } = useAuth();
+ const { logout } = useLogout();
const router = useConceptNavigation();
function logoutAndRedirect() {
diff --git a/rsconcept/frontend/src/components/wrap/RequireAuth.tsx b/rsconcept/frontend/src/components/wrap/RequireAuth.tsx
index f9b3bcfa..280ef4c2 100644
--- a/rsconcept/frontend/src/components/wrap/RequireAuth.tsx
+++ b/rsconcept/frontend/src/components/wrap/RequireAuth.tsx
@@ -1,14 +1,14 @@
'use client';
-import { useAuth } from '@/context/AuthContext';
+import { useAuth } from '@/backend/auth/useAuth';
import Loader from '../ui/Loader';
import TextURL from '../ui/TextURL';
function RequireAuth({ children }: React.PropsWithChildren) {
- const { user, loading } = useAuth();
+ const { user, isLoading } = useAuth();
- if (loading) {
+ if (isLoading) {
return ;
}
if (user) {
diff --git a/rsconcept/frontend/src/context/AuthContext.tsx b/rsconcept/frontend/src/context/AuthContext.tsx
deleted file mode 100644
index d8fad021..00000000
--- a/rsconcept/frontend/src/context/AuthContext.tsx
+++ /dev/null
@@ -1,204 +0,0 @@
-'use client';
-
-import { createContext, useCallback, useContext, useEffect, useState } from 'react';
-
-import { DataCallback } from '@/backend/apiTransport';
-import {
- getAuth,
- patchPassword,
- postLogin,
- postLogout,
- postRequestPasswordReset,
- postResetPassword,
- postSignup,
- postValidatePasswordToken
-} from '@/backend/users';
-import { type ErrorData } from '@/components/info/InfoError';
-import {
- ICurrentUser,
- IPasswordTokenData,
- IRequestPasswordData,
- IResetPasswordData,
- IUserLoginData,
- IUserProfile,
- IUserSignupData,
- IUserUpdatePassword
-} from '@/models/user';
-import { contextOutsideScope } from '@/utils/labels';
-
-interface IAuthContext {
- user: ICurrentUser | undefined;
- login: (data: IUserLoginData, callback?: DataCallback) => void;
- logout: (callback?: DataCallback) => void;
- signup: (data: IUserSignupData, callback?: DataCallback) => void;
- updatePassword: (data: IUserUpdatePassword, callback?: () => void) => void;
- requestPasswordReset: (data: IRequestPasswordData, callback?: () => void) => void;
- validateToken: (data: IPasswordTokenData, callback?: () => void) => void;
- resetPassword: (data: IResetPasswordData, callback?: () => void) => void;
- loading: boolean;
- error: ErrorData;
- setError: (error: ErrorData) => void;
-}
-
-const AuthContext = createContext(null);
-export const useAuth = () => {
- const context = useContext(AuthContext);
- if (!context) {
- throw new Error(contextOutsideScope('useAuth', 'AuthState'));
- }
- return context;
-};
-
-export const AuthState = ({ children }: React.PropsWithChildren) => {
- const [user, setUser] = useState(undefined);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(undefined);
-
- const reload = useCallback(
- (callback?: () => void) => {
- getAuth({
- onError: () => setUser(undefined),
- setLoading: setLoading,
- onSuccess: currentUser => {
- if (currentUser.id) {
- setUser(currentUser);
- } else {
- setUser(undefined);
- }
- callback?.();
- }
- });
- },
- [setUser]
- );
-
- function login(data: IUserLoginData, callback?: DataCallback) {
- setError(undefined);
- postLogin({
- data: data,
- showError: false,
- setLoading: setLoading,
- onError: setError,
- onSuccess: newData =>
- reload(() => {
- callback?.(newData);
- })
- });
- }
-
- function logout(callback?: DataCallback) {
- setError(undefined);
- postLogout({
- showError: true,
- onSuccess: newData =>
- reload(() => {
- callback?.(newData);
- })
- });
- }
-
- function signup(data: IUserSignupData, callback?: DataCallback) {
- setError(undefined);
- postSignup({
- data: data,
- showError: true,
- setLoading: setLoading,
- onError: setError,
- onSuccess: newData =>
- reload(() => {
- // TODO: reload users / optimistic update
- // users.push(newData as IUserInfo);
- callback?.(newData);
- })
- });
- }
-
- const updatePassword = useCallback(
- (data: IUserUpdatePassword, callback?: () => void) => {
- setError(undefined);
- patchPassword({
- data: data,
- showError: true,
- setLoading: setLoading,
- onError: setError,
- onSuccess: () => reload(callback)
- });
- },
- [reload]
- );
-
- const requestPasswordReset = useCallback(
- (data: IRequestPasswordData, callback?: () => void) => {
- setError(undefined);
- postRequestPasswordReset({
- data: data,
- showError: false,
- setLoading: setLoading,
- onError: setError,
- onSuccess: () =>
- reload(() => {
- callback?.();
- })
- });
- },
- [reload]
- );
-
- const validateToken = useCallback(
- (data: IPasswordTokenData, callback?: () => void) => {
- setError(undefined);
- postValidatePasswordToken({
- data: data,
- showError: false,
- setLoading: setLoading,
- onError: setError,
- onSuccess: () =>
- reload(() => {
- callback?.();
- })
- });
- },
- [reload]
- );
-
- const resetPassword = useCallback(
- (data: IResetPasswordData, callback?: () => void) => {
- setError(undefined);
- postResetPassword({
- data: data,
- showError: false,
- setLoading: setLoading,
- onError: setError,
- onSuccess: () =>
- reload(() => {
- callback?.();
- })
- });
- },
- [reload]
- );
-
- useEffect(() => {
- reload();
- }, [reload]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/rsconcept/frontend/src/context/LibraryContext.tsx b/rsconcept/frontend/src/context/LibraryContext.tsx
index e087c0ab..e212f467 100644
--- a/rsconcept/frontend/src/context/LibraryContext.tsx
+++ b/rsconcept/frontend/src/context/LibraryContext.tsx
@@ -3,6 +3,7 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { DataCallback } from '@/backend/apiTransport';
+import { useAuth } from '@/backend/auth/useAuth';
import {
deleteLibraryItem,
getAdminLibrary,
@@ -24,8 +25,6 @@ import { RSFormLoader } from '@/models/RSFormLoader';
import { usePreferencesStore } from '@/stores/preferences';
import { contextOutsideScope } from '@/utils/labels';
-import { useAuth } from './AuthContext';
-
interface ILibraryContext {
items: ILibraryItem[];
templates: ILibraryItem[];
@@ -62,7 +61,7 @@ export const useLibrary = (): ILibraryContext => {
};
export const LibraryState = ({ children }: React.PropsWithChildren) => {
- const { user, loading: userLoading } = useAuth();
+ const { user, isLoading: userLoading } = useAuth();
const adminMode = usePreferencesStore(state => state.adminMode);
const [items, setItems] = useState([]);
diff --git a/rsconcept/frontend/src/context/OssContext.tsx b/rsconcept/frontend/src/context/OssContext.tsx
index c13d5b6e..9bc96fae 100644
--- a/rsconcept/frontend/src/context/OssContext.tsx
+++ b/rsconcept/frontend/src/context/OssContext.tsx
@@ -3,6 +3,7 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { DataCallback } from '@/backend/apiTransport';
+import { useAuth } from '@/backend/auth/useAuth';
import {
patchLibraryItem,
patchSetAccessPolicy,
@@ -38,7 +39,6 @@ import {
import { UserID } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
-import { useAuth } from './AuthContext';
import { useGlobalOss } from './GlobalOssContext';
import { useLibrary } from './LibraryContext';
diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx
index 11533474..5148c11c 100644
--- a/rsconcept/frontend/src/context/RSFormContext.tsx
+++ b/rsconcept/frontend/src/context/RSFormContext.tsx
@@ -3,6 +3,7 @@
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { DataCallback } from '@/backend/apiTransport';
+import { useAuth } from '@/backend/auth/useAuth';
import {
patchLibraryItem,
patchSetAccessPolicy,
@@ -50,7 +51,6 @@ import {
import { UserID } from '@/models/user';
import { contextOutsideScope } from '@/utils/labels';
-import { useAuth } from './AuthContext';
import { useGlobalOss } from './GlobalOssContext';
import { useLibrary } from './LibraryContext';
diff --git a/rsconcept/frontend/src/context/UserProfileContext.tsx b/rsconcept/frontend/src/context/UserProfileContext.tsx
deleted file mode 100644
index 392a9f2e..00000000
--- a/rsconcept/frontend/src/context/UserProfileContext.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-'use client';
-
-import { createContext, useCallback, useContext, useEffect, useState } from 'react';
-
-import { DataCallback } from '@/backend/apiTransport';
-import { getProfile, patchProfile } from '@/backend/users';
-import { ErrorData } from '@/components/info/InfoError';
-import { IUserProfile, IUserUpdateData } from '@/models/user';
-import { contextOutsideScope } from '@/utils/labels';
-
-interface IUserProfileContext {
- user: IUserProfile | undefined;
- loading: boolean;
- processing: boolean;
- error: ErrorData;
- errorProcessing: ErrorData;
- setError: (error: ErrorData) => void;
- updateUser: (data: IUserUpdateData, callback?: DataCallback) => void;
-}
-
-const ProfileContext = createContext(null);
-
-export const useUserProfile = () => {
- const context = useContext(ProfileContext);
- if (!context) {
- throw new Error(contextOutsideScope('useUserProfile', 'UserProfileState'));
- }
- return context;
-};
-
-export const UserProfileState = ({ children }: React.PropsWithChildren) => {
- const [user, setUser] = useState(undefined);
- const [loading, setLoading] = useState(true);
- const [processing, setProcessing] = useState(false);
- const [error, setError] = useState(undefined);
- const [errorProcessing, setErrorProcessing] = useState(undefined);
-
- const reload = useCallback(() => {
- setError(undefined);
- setUser(undefined);
- getProfile({
- showError: true,
- setLoading: setLoading,
- onError: setError,
- onSuccess: newData => setUser(newData)
- });
- }, [setUser]);
-
- const updateUser = useCallback(
- (data: IUserUpdateData, callback?: DataCallback) => {
- setErrorProcessing(undefined);
- patchProfile({
- data: data,
- showError: true,
- setLoading: setProcessing,
- onError: setErrorProcessing,
- onSuccess: newData => {
- setUser(newData);
- // TODO: reload users / optimistic update
- callback?.(newData);
- }
- });
- },
- [setUser]
- );
-
- useEffect(() => {
- reload();
- }, [reload]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/rsconcept/frontend/src/dialogs/DlgChangeLocation.tsx b/rsconcept/frontend/src/dialogs/DlgChangeLocation.tsx
index 5f8c20f4..20b621f9 100644
--- a/rsconcept/frontend/src/dialogs/DlgChangeLocation.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgChangeLocation.tsx
@@ -3,12 +3,12 @@
import clsx from 'clsx';
import { useState } from 'react';
+import { useAuth } from '@/backend/auth/useAuth';
import SelectLocationContext from '@/components/select/SelectLocationContext';
import SelectLocationHead from '@/components/select/SelectLocationHead';
import Label from '@/components/ui/Label';
import Modal from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { LocationHead } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI';
diff --git a/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx b/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx
index 6690c6dd..c8107adf 100644
--- a/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgCloneLibraryItem.tsx
@@ -5,6 +5,7 @@ import { useState } from 'react';
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
import { VisibilityIcon } from '@/components/DomainIcons';
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
import SelectLocationContext from '@/components/select/SelectLocationContext';
@@ -15,7 +16,6 @@ import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
diff --git a/rsconcept/frontend/src/hooks/useOssDetails.ts b/rsconcept/frontend/src/hooks/useOssDetails.ts
index f9d0b71a..44be1ecf 100644
--- a/rsconcept/frontend/src/hooks/useOssDetails.ts
+++ b/rsconcept/frontend/src/hooks/useOssDetails.ts
@@ -2,15 +2,15 @@
import { useCallback, useEffect, useState } from 'react';
+import { useAuth } from '@/backend/auth/useAuth';
import { getOssDetails } from '@/backend/oss';
import { type ErrorData } from '@/components/info/InfoError';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
import { OssLoader } from '@/models/OssLoader';
function useOssDetails({ target }: { target?: string }) {
- const { loading: userLoading } = useAuth();
+ const { isLoading: userLoading } = useAuth();
const library = useLibrary();
const [schema, setInner] = useState(undefined);
const [loading, setLoading] = useState(target != undefined);
diff --git a/rsconcept/frontend/src/hooks/useRSFormDetails.ts b/rsconcept/frontend/src/hooks/useRSFormDetails.ts
index 34308211..603acfef 100644
--- a/rsconcept/frontend/src/hooks/useRSFormDetails.ts
+++ b/rsconcept/frontend/src/hooks/useRSFormDetails.ts
@@ -2,14 +2,14 @@
import { useCallback, useEffect, useState } from 'react';
+import { useAuth } from '@/backend/auth/useAuth';
import { getRSFormDetails } from '@/backend/rsforms';
import { type ErrorData } from '@/components/info/InfoError';
-import { useAuth } from '@/context/AuthContext';
import { IRSForm, IRSFormData } from '@/models/rsform';
import { RSFormLoader } from '@/models/RSFormLoader';
function useRSFormDetails({ target, version }: { target?: string; version?: string }) {
- const { loading: userLoading } = useAuth();
+ const { isLoading: userLoading } = useAuth();
const [schema, setInnerSchema] = useState(undefined);
const [loading, setLoading] = useState(target != undefined);
const [error, setError] = useState(undefined);
diff --git a/rsconcept/frontend/src/models/user.ts b/rsconcept/frontend/src/models/user.ts
index f7e9b7c2..08fd9b03 100644
--- a/rsconcept/frontend/src/models/user.ts
+++ b/rsconcept/frontend/src/models/user.ts
@@ -26,35 +26,9 @@ export interface IUser {
* Represents CurrentUser information.
*/
export interface ICurrentUser extends Pick {
- subscriptions: LibraryItemID[];
editor: LibraryItemID[];
}
-/**
- * Represents login data, used to authenticate users.
- */
-export interface IUserLoginData extends Pick {
- password: string;
-}
-
-/**
- * Represents password reset data.
- */
-export interface IResetPasswordData {
- password: string;
- token: string;
-}
-
-/**
- * Represents password token data.
- */
-export interface IPasswordTokenData extends Pick {}
-
-/**
- * Represents password reset request data.
- */
-export interface IRequestPasswordData extends Pick {}
-
/**
* Represents signup data, used to create new users.
*/
@@ -63,11 +37,6 @@ export interface IUserSignupData extends Omit {
password2: string;
}
-/**
- * Represents user data, intended to update user profile in persistent storage.
- */
-export interface IUserUpdateData extends Omit {}
-
/**
* Represents user profile for viewing and editing {@link IUser}.
*/
@@ -78,14 +47,6 @@ export interface IUserProfile extends Omit {}
*/
export interface IUserInfo extends Omit {}
-/**
- * Represents data needed to update password for current user.
- */
-export interface IUserUpdatePassword {
- old_password: string;
- new_password: string;
-}
-
/**
* Represents target {@link User}.
*/
diff --git a/rsconcept/frontend/src/pages/CreateItemPage/FormCreateItem.tsx b/rsconcept/frontend/src/pages/CreateItemPage/FormCreateItem.tsx
index 9b9d3f77..b5b712fc 100644
--- a/rsconcept/frontend/src/pages/CreateItemPage/FormCreateItem.tsx
+++ b/rsconcept/frontend/src/pages/CreateItemPage/FormCreateItem.tsx
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
import { VisibilityIcon } from '@/components/DomainIcons';
import { IconDownload } from '@/components/Icons';
import InfoError from '@/components/info/InfoError';
@@ -19,7 +20,6 @@ import Overlay from '@/components/ui/Overlay';
import SubmitButton from '@/components/ui/SubmitButton';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
diff --git a/rsconcept/frontend/src/pages/HomePage.tsx b/rsconcept/frontend/src/pages/HomePage.tsx
index 9d4a3f84..102da9e3 100644
--- a/rsconcept/frontend/src/pages/HomePage.tsx
+++ b/rsconcept/frontend/src/pages/HomePage.tsx
@@ -1,17 +1,17 @@
import { useEffect } from 'react';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
import Loader from '@/components/ui/Loader';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { PARAMETER } from '@/utils/constants';
function HomePage() {
const router = useConceptNavigation();
- const { user, loading } = useAuth();
+ const { user, isLoading } = useAuth();
useEffect(() => {
- if (!loading) {
+ if (!isLoading) {
if (!user) {
setTimeout(() => {
router.replace(urls.manuals);
@@ -22,7 +22,7 @@ function HomePage() {
}, PARAMETER.refreshTimeout);
}
}
- }, [router, user, loading]);
+ }, [router, user, isLoading]);
return ;
}
diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx
index 24d7ca21..0b4dbe1b 100644
--- a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx
+++ b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx
@@ -1,13 +1,13 @@
import clsx from 'clsx';
import { toast } from 'react-toastify';
+import { useAuth } from '@/backend/auth/useAuth';
import { SubfoldersIcon } from '@/components/DomainIcons';
import { IconFolderEdit, IconFolderTree } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import { CProps } from '@/components/props';
import SelectLocation from '@/components/select/SelectLocation';
import MiniButton from '@/components/ui/MiniButton';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import useWindowSize from '@/hooks/useWindowSize';
import { FolderNode, FolderTree } from '@/models/FolderTree';
diff --git a/rsconcept/frontend/src/pages/LoginPage.tsx b/rsconcept/frontend/src/pages/LoginPage.tsx
index ffff58cb..c79e2b7e 100644
--- a/rsconcept/frontend/src/pages/LoginPage.tsx
+++ b/rsconcept/frontend/src/pages/LoginPage.tsx
@@ -5,15 +5,15 @@ import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
+import { useLogin } from '@/backend/auth/useLogin';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import useQueryStrings from '@/hooks/useQueryStrings';
-import { IUserLoginData } from '@/models/user';
import { resources } from '@/utils/constants';
function LoginPage() {
@@ -21,23 +21,20 @@ function LoginPage() {
const query = useQueryStrings();
const userQuery = query.get('username');
- const { user, login, loading, error, setError } = useAuth();
+ const { user } = useAuth();
+ const { login, isPending, error, reset } = useLogin();
const [username, setUsername] = useState(userQuery || '');
const [password, setPassword] = useState('');
useEffect(() => {
- setError(undefined);
- }, [username, password, setError]);
+ reset();
+ }, [username, password, reset]);
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
- if (!loading) {
- const data: IUserLoginData = {
- username: username,
- password: password
- };
- login(data, () => {
+ if (!isPending) {
+ login(username, password, () => {
if (router.canBack()) {
router.back();
} else {
@@ -78,7 +75,7 @@ function LoginPage() {
diff --git a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx
index a96894f5..a40d349b 100644
--- a/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx
+++ b/rsconcept/frontend/src/pages/OssPage/MenuOssTabs.tsx
@@ -1,6 +1,7 @@
'use client';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
import {
IconAdmin,
IconAlert,
@@ -19,7 +20,6 @@ import Button from '@/components/ui/Button';
import Divider from '@/components/ui/Divider';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext';
import useDropdown from '@/hooks/useDropdown';
diff --git a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx
index 4289e853..ab52702d 100644
--- a/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx
+++ b/rsconcept/frontend/src/pages/OssPage/OssEditContext.tsx
@@ -4,7 +4,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
-import { useAuth } from '@/context/AuthContext';
+import { useAuth } from '@/backend/auth/useAuth';
import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext';
diff --git a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx
index ef1c0e67..b96ae033 100644
--- a/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx
+++ b/rsconcept/frontend/src/pages/OssPage/OssTabs.tsx
@@ -7,12 +7,12 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
+import { useAuth } from '@/backend/auth/useAuth';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
-import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext';
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
import { useOSS } from '@/context/OssContext';
@@ -46,7 +46,7 @@ function OssTabs() {
useBlockNavigation(
isModified &&
schema !== undefined &&
- user !== undefined &&
+ !!user &&
(user.is_staff || user.id == schema.owner || schema.editors.includes(user.id))
);
diff --git a/rsconcept/frontend/src/pages/PasswordChangePage.tsx b/rsconcept/frontend/src/pages/PasswordChangePage.tsx
index 070de72b..5c4c2e5a 100644
--- a/rsconcept/frontend/src/pages/PasswordChangePage.tsx
+++ b/rsconcept/frontend/src/pages/PasswordChangePage.tsx
@@ -5,20 +5,20 @@ import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { urls } from '@/app/urls';
+import { IPasswordTokenDTO, IResetPasswordDTO } from '@/backend/auth/api';
+import { useResetPassword } from '@/backend/auth/useResetPassword';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import DataLoader from '@/components/wrap/DataLoader';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import useQueryStrings from '@/hooks/useQueryStrings';
-import { IPasswordTokenData, IResetPasswordData } from '@/models/user';
function PasswordChangePage() {
const router = useConceptNavigation();
const token = useQueryStrings().get('token');
- const { validateToken, resetPassword, loading, error, setError } = useAuth();
+ const { validateToken, resetPassword, isPending, error, reset } = useResetPassword();
const [isTokenValid, setIsTokenValid] = useState(false);
const [newPassword, setNewPassword] = useState('');
@@ -31,8 +31,8 @@ function PasswordChangePage() {
function handleSubmit(event: React.FormEvent
) {
event.preventDefault();
- if (!loading) {
- const data: IResetPasswordData = {
+ if (!isPending) {
+ const data: IResetPasswordDTO = {
password: newPassword,
token: token!
};
@@ -44,21 +44,18 @@ function PasswordChangePage() {
}
useEffect(() => {
- setError(undefined);
- }, [newPassword, newPasswordRepeat, setError]);
+ reset();
+ }, [newPassword, newPasswordRepeat, reset]);
useEffect(() => {
- const data: IPasswordTokenData = {
+ const data: IPasswordTokenDTO = {
token: token ?? ''
};
validateToken(data, () => setIsTokenValid(true));
}, [token, validateToken]);
- if (error) {
- return ;
- }
return (
-
+
-
+
{error ? : null}
diff --git a/rsconcept/frontend/src/pages/RegisterPage/RegisterPage.tsx b/rsconcept/frontend/src/pages/RegisterPage/RegisterPage.tsx
index 2f8a1307..bb5a0bb2 100644
--- a/rsconcept/frontend/src/pages/RegisterPage/RegisterPage.tsx
+++ b/rsconcept/frontend/src/pages/RegisterPage/RegisterPage.tsx
@@ -1,13 +1,13 @@
+import { useAuth } from '@/backend/auth/useAuth';
import Loader from '@/components/ui/Loader';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
-import { useAuth } from '@/context/AuthContext';
import FormSignup from './FormSignup';
function RegisterPage() {
- const { user, loading } = useAuth();
+ const { user, isLoading } = useAuth();
- if (loading) {
+ if (isLoading) {
return ;
}
if (user) {
diff --git a/rsconcept/frontend/src/pages/RestorePasswordPage.tsx b/rsconcept/frontend/src/pages/RestorePasswordPage.tsx
index 67a3735f..161e4549 100644
--- a/rsconcept/frontend/src/pages/RestorePasswordPage.tsx
+++ b/rsconcept/frontend/src/pages/RestorePasswordPage.tsx
@@ -4,32 +4,28 @@ import axios from 'axios';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
+import { useRequestPasswordReset } from '@/backend/auth/useRequestPasswordReset';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL';
-import { useAuth } from '@/context/AuthContext';
-import { IRequestPasswordData } from '@/models/user';
function RestorePasswordPage() {
- const { requestPasswordReset, loading, error, setError } = useAuth();
+ const { requestPasswordReset, isPending, error, reset } = useRequestPasswordReset();
const [isCompleted, setIsCompleted] = useState(false);
const [email, setEmail] = useState('');
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
- if (!loading) {
- const data: IRequestPasswordData = {
- email: email
- };
- requestPasswordReset(data, () => setIsCompleted(true));
+ if (!isPending) {
+ requestPasswordReset({ email: email }, () => setIsCompleted(true));
}
}
useEffect(() => {
- setError(undefined);
- }, [email, setError]);
+ reset();
+ }, [email, reset]);
if (isCompleted) {
return (
@@ -55,7 +51,7 @@ function RestorePasswordPage() {
{error ? : null}
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/EditorPassword.tsx b/rsconcept/frontend/src/pages/UserProfilePage/EditorPassword.tsx
index fd6960f4..c5330aa8 100644
--- a/rsconcept/frontend/src/pages/UserProfilePage/EditorPassword.tsx
+++ b/rsconcept/frontend/src/pages/UserProfilePage/EditorPassword.tsx
@@ -6,18 +6,18 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { urls } from '@/app/urls';
+import { IChangePasswordDTO } from '@/backend/auth/api';
+import { useChangePassword } from '@/backend/auth/useChangePassword';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import FlexColumn from '@/components/ui/FlexColumn';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
-import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
-import { IUserUpdatePassword } from '@/models/user';
import { errors, information } from '@/utils/labels';
function EditorPassword() {
const router = useConceptNavigation();
- const { updatePassword, error, setError, loading } = useAuth();
+ const { changePassword, isPending, error, reset } = useChangePassword();
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
@@ -34,19 +34,19 @@ function EditorPassword() {
toast.error(errors.passwordsMismatch);
return;
}
- const data: IUserUpdatePassword = {
+ const data: IChangePasswordDTO = {
old_password: oldPassword,
new_password: newPassword
};
- updatePassword(data, () => {
+ changePassword(data, () => {
toast.success(information.changesSaved);
router.push(urls.login);
});
}
useEffect(() => {
- setError(undefined);
- }, [newPassword, oldPassword, newPasswordRepeat, setError]);
+ reset();
+ }, [newPassword, oldPassword, newPasswordRepeat, reset]);
return (
{error ? : null}
-
+
);
}
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx
index 3ff1f69c..22a08c4b 100644
--- a/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx
+++ b/rsconcept/frontend/src/pages/UserProfilePage/EditorProfile.tsx
@@ -4,45 +4,44 @@ import axios from 'axios';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
+import { IUpdateProfileDTO } from '@/backend/users/api';
+import { useProfileSuspense } from '@/backend/users/useProfile';
+import { useUpdateProfile } from '@/backend/users/useUpdateProfile';
import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import { useBlockNavigation } from '@/context/NavigationContext';
-import { useUserProfile } from '@/context/UserProfileContext';
-import { IUserUpdateData } from '@/models/user';
import { information } from '@/utils/labels';
function EditorProfile() {
- const { updateUser, user, errorProcessing, processing } = useUserProfile();
+ const { profile } = useProfileSuspense();
+ const { updateProfile, isPending, error } = useUpdateProfile();
- const [username, setUsername] = useState(user?.username ?? '');
- const [email, setEmail] = useState(user?.email ?? '');
- const [first_name, setFirstName] = useState(user?.first_name ?? '');
- const [last_name, setLastName] = useState(user?.last_name ?? '');
+ const [username, setUsername] = useState(profile.username);
+ const [email, setEmail] = useState(profile.email);
+ const [first_name, setFirstName] = useState(profile.first_name);
+ const [last_name, setLastName] = useState(profile.last_name);
- const isModified =
- user != undefined && (user.email !== email || user.first_name !== first_name || user.last_name !== last_name);
+ const isModified = profile.email !== email || profile.first_name !== first_name || profile.last_name !== last_name;
useBlockNavigation(isModified);
useEffect(() => {
- if (user) {
- setUsername(user.username);
- setEmail(user.email);
- setFirstName(user.first_name);
- setLastName(user.last_name);
- }
- }, [user]);
+ setUsername(profile.username);
+ setEmail(profile.email);
+ setFirstName(profile.first_name);
+ setLastName(profile.last_name);
+ }, [profile]);
function handleSubmit(event: React.FormEvent) {
event.preventDefault();
- const data: IUserUpdateData = {
+ const data: IUpdateProfileDTO = {
username: username,
email: email,
first_name: first_name,
last_name: last_name
};
- updateUser(data, () => toast.success(information.changesSaved));
+ updateProfile(data, () => toast.success(information.changesSaved));
}
return (
@@ -79,11 +78,11 @@ function EditorProfile() {
value={email}
onChange={event => setEmail(event.target.value)}
/>
- {errorProcessing ? : null}
+ {error ? : null}
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserContents.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserContents.tsx
deleted file mode 100644
index 83c82651..00000000
--- a/rsconcept/frontend/src/pages/UserProfilePage/UserContents.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-'use client';
-
-import DataLoader from '@/components/wrap/DataLoader';
-import { useUserProfile } from '@/context/UserProfileContext';
-
-import EditorPassword from './EditorPassword';
-import EditorProfile from './EditorProfile';
-
-function UserContents() {
- const { user, error, loading } = useUserProfile();
-
- return (
-
-
-
Учетные данные пользователя
-
-
-
-
-
-
- );
-}
-
-export default UserContents;
diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserProfilePage.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserProfilePage.tsx
index 6f8f608e..bd63b0a1 100644
--- a/rsconcept/frontend/src/pages/UserProfilePage/UserProfilePage.tsx
+++ b/rsconcept/frontend/src/pages/UserProfilePage/UserProfilePage.tsx
@@ -1,14 +1,23 @@
-import RequireAuth from '@/components/wrap/RequireAuth';
-import { UserProfileState } from '@/context/UserProfileContext';
+import { Suspense } from 'react';
-import UserContents from './UserContents';
+import Loader from '@/components/ui/Loader';
+import RequireAuth from '@/components/wrap/RequireAuth';
+
+import EditorPassword from './EditorPassword';
+import EditorProfile from './EditorProfile';
function UserProfilePage() {
return (
-
-
-
+ }>
+
+
Учетные данные пользователя
+
+
+
+
+
+
);
}
diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts
index b39c3cce..88424ab3 100644
--- a/rsconcept/frontend/src/utils/labels.ts
+++ b/rsconcept/frontend/src/utils/labels.ts
@@ -958,8 +958,9 @@ export const information = {
noDataToExport: 'Нет данных для экспорта',
substitutionsCorrect: 'Таблица отождествлений прошла проверку',
- addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
newLibraryItem: 'Схема успешно создана',
+ addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
+ newUser: (username: string) => `Пользователь успешно создан: ${username}`,
newVersion: (version: string) => `Версия создана: ${version}`,
newConstituent: (alias: string) => `Конституента добавлена: ${alias}`,
newOperation: (alias: string) => `Операция добавлена: ${alias}`,