diff --git a/frontend/src/contexts/apiClient.context.tsx b/frontend/src/contexts/apiClient.context.tsx new file mode 100644 index 0000000..a3fbeeb --- /dev/null +++ b/frontend/src/contexts/apiClient.context.tsx @@ -0,0 +1,51 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import axios from "axios"; +import { createContext, ReactNode, FC } from "react"; + +import { getApiClientByEntity, useAxiosInstance } from "@/hooks/useApiClient"; +import { ApiClient, EntityApiClient } from "@/services/api.class"; +import { EntityType } from "@/services/types"; +import { IBaseSchema } from "@/types/base.types"; + +interface ApiClientContextProps { + children: ReactNode; +} + +export interface ApiClientContext { + apiClient: ApiClient; + getApiClientByEntity: ( + type: EntityType, + ) => EntityApiClient; +} + +export const ApiClientContext = createContext({ + apiClient: new ApiClient(axios.create()), + getApiClientByEntity: () => { + throw new Error( + "getApiClientByEntity must be used within an ApiClientProvider", + ); + }, +}); + +export const ApiClientProvider: FC = ({ children }) => { + const axiosInstance = useAxiosInstance(); + const apiClient = new ApiClient(axiosInstance); + + return ( + getApiClientByEntity(type, apiClient), + }} + > + {children} + + ); +}; diff --git a/frontend/src/hooks/useAuth.tsx b/frontend/src/contexts/auth.context.tsx old mode 100755 new mode 100644 similarity index 75% rename from frontend/src/hooks/useAuth.tsx rename to frontend/src/contexts/auth.context.tsx index 4d98a03..f908a06 --- a/frontend/src/hooks/useAuth.tsx +++ b/frontend/src/contexts/auth.context.tsx @@ -8,27 +8,29 @@ import getConfig from "next/config"; import { useRouter } from "next/router"; -import React, { createContext, ReactNode, useContext, useState } from "react"; +import { useState, useEffect, createContext, ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { + useQueryClient, + useQuery, QueryObserverResult, RefetchOptions, UseMutateFunction, - useQuery, - useQueryClient, } from "react-query"; import { Progress } from "@/app-components/displays/Progress"; +import { useLogout } from "@/hooks/entities/auth-hooks"; +import { useApiClient } from "@/hooks/useApiClient"; +import { + useLogoutRedirection, + CURRENT_USER_KEY, + PUBLIC_PATHS, +} from "@/hooks/useAuth"; +import { useToast } from "@/hooks/useToast"; import { RouterType } from "@/services/types"; import { IUser } from "@/types/user.types"; import { getFromQuery } from "@/utils/URL"; -import { useLogout } from "./entities/auth-hooks"; -import { useApiClient } from "./useApiClient"; -import { useToast } from "./useToast"; - -const { publicRuntimeConfig } = getConfig(); - export interface AuthContextValue { user: IUser | undefined; isAuthenticated: boolean; @@ -41,7 +43,7 @@ export interface AuthContextValue { error: Error | null; } -const AuthContext = createContext(null); +export const AuthContext = createContext(null); AuthContext.displayName = "AuthContext"; @@ -49,14 +51,7 @@ export interface AuthProviderProps { children: ReactNode; } -const PUBLIC_PATHS = [ - "/login/[[...token]]", - "/register/[token]", - "/reset/[token]", - "/reset", -]; - -export const CURRENT_USER_KEY = "current-user"; +const { publicRuntimeConfig } = getConfig(); export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { const router = useRouter(); @@ -113,7 +108,7 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { }; const isAuthenticated = !!user; - React.useEffect(() => { + useEffect(() => { const search = location.search; setSearch(search); @@ -138,33 +133,3 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { ); }; - -export const useAuth = () => { - const context = useContext(AuthContext); - - if (!context) { - throw new Error(`useAuth must be used within an AuthProvider`); - } - - return context; -}; - -export const useLogoutRedirection = () => { - const router = useRouter(); - const hasPublicPath = PUBLIC_PATHS.includes(router.pathname); - const logoutRedirection = async (fullReload: boolean = false) => { - if (!hasPublicPath) { - const redirectUrl = `/${RouterType.LOGIN}?redirect=${encodeURIComponent( - router.pathname, - )}`; - - if (fullReload) { - window.location.replace(redirectUrl); - } else { - await router.replace(redirectUrl); - } - } - }; - - return { logoutRedirection }; -}; diff --git a/frontend/src/hooks/useConfig.tsx b/frontend/src/contexts/config.context.tsx similarity index 52% rename from frontend/src/hooks/useConfig.tsx rename to frontend/src/contexts/config.context.tsx index abd2921..d84f28e 100644 --- a/frontend/src/hooks/useConfig.tsx +++ b/frontend/src/contexts/config.context.tsx @@ -1,6 +1,14 @@ -import { createContext, useContext, useEffect, useState } from "react"; +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ -const ConfigContext = createContext(null); +import { useState, useEffect, createContext } from "react"; + +export const ConfigContext = createContext(null); export interface IConfig { apiUrl: string; @@ -35,13 +43,3 @@ export const ConfigProvider = ({ children }) => { {children} ); }; - -export const useConfig = () => { - const context = useContext(ConfigContext); - - if (!context) { - throw new Error("useConfig must be used within a ConfigProvider"); - } - - return context; -}; diff --git a/frontend/src/hooks/useHasPermission.tsx b/frontend/src/contexts/permission.context.tsx similarity index 76% rename from frontend/src/hooks/useHasPermission.tsx rename to frontend/src/contexts/permission.context.tsx index 4a6a926..46cc417 100644 --- a/frontend/src/hooks/useHasPermission.tsx +++ b/frontend/src/contexts/permission.context.tsx @@ -6,21 +6,14 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { - createContext, - ReactNode, - useCallback, - useContext, - useMemo, -} from "react"; +import { createContext, ReactNode, useCallback, useMemo } from "react"; import { Progress } from "@/app-components/displays/Progress"; +import { useUserPermissions } from "@/hooks/entities/auth-hooks"; import { EntityType } from "@/services/types"; import { PermissionAction } from "@/types/permission.types"; -import { useUserPermissions } from "./entities/auth-hooks"; - -const PermissionContext = createContext<{ +export const PermissionContext = createContext<{ getAllowedActions: (_type: EntityType) => undefined | PermissionAction[]; }>({ getAllowedActions: (_type: EntityType) => undefined, @@ -68,17 +61,3 @@ export const PermissionProvider = ({ ); }; - -export const useHasPermission = () => { - const { getAllowedActions } = useContext(PermissionContext); - const hasPermission = useCallback( - (type: EntityType, action: PermissionAction) => { - const allowedActions = getAllowedActions(type); - - return allowedActions?.includes(action); - }, - [getAllowedActions], - ); - - return hasPermission; -}; diff --git a/frontend/src/hooks/useSetting.tsx b/frontend/src/contexts/setting.context.tsx similarity index 75% rename from frontend/src/hooks/useSetting.tsx rename to frontend/src/contexts/setting.context.tsx index 3da38bb..a047c21 100644 --- a/frontend/src/hooks/useSetting.tsx +++ b/frontend/src/contexts/setting.context.tsx @@ -6,14 +6,13 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -import { createContext, ReactNode, useContext } from "react"; +import { createContext, ReactNode } from "react"; import { Progress } from "@/app-components/displays/Progress"; +import { useLoadSettings } from "@/hooks/entities/auth-hooks"; import { ISetting } from "@/types/setting.types"; -import { useLoadSettings } from "./entities/auth-hooks"; - -const SettingsContext = createContext<{ +export const SettingsContext = createContext<{ settings: { [key: string]: ISetting[] } | undefined; }>({ settings: undefined }); @@ -40,12 +39,3 @@ export const SettingsProvider = ({ ); }; - -export const useSetting = (type: string, label: string) => { - const { settings } = useContext(SettingsContext); - const value = settings?.[type]?.find( - (setting) => setting.label === label, - )?.value; - - return value; -}; diff --git a/frontend/src/hooks/useApiClient.tsx b/frontend/src/hooks/useApiClient.ts similarity index 73% rename from frontend/src/hooks/useApiClient.tsx rename to frontend/src/hooks/useApiClient.ts index 2e620d7..6144d6b 100644 --- a/frontend/src/hooks/useApiClient.tsx +++ b/frontend/src/hooks/useApiClient.ts @@ -8,9 +8,10 @@ import axios from "axios"; import { stringify } from "qs"; -import React, { createContext, ReactNode, useContext, useMemo } from "react"; +import { useContext, useMemo } from "react"; import { useTranslation } from "react-i18next"; +import { ApiClientContext } from "@/contexts/apiClient.context"; import { ApiClient, EntityApiClient } from "@/services/api.class"; import { EntityType } from "@/services/types"; import { IBaseSchema } from "@/types/base.types"; @@ -65,20 +66,13 @@ export const useAxiosInstance = () => { return axiosInstance; }; -interface ApiClientContextProps { - children: ReactNode; -} - export const entityApiClients = new Map(); -interface ApiClientContext { - apiClient: ApiClient; - getApiClientByEntity: ( - type: EntityType, - ) => EntityApiClient; -} - -const getApiClientByEntity = ( +export const getApiClientByEntity = < + TAttr, + TStub extends IBaseSchema, + TFull = never, +>( type: EntityType, apiClient: ApiClient, ) => { @@ -90,32 +84,6 @@ const getApiClientByEntity = ( return entityApiClients.get(type) as EntityApiClient; }; -const ApiClientContext = createContext({ - apiClient: new ApiClient(axios.create()), - getApiClientByEntity: () => { - throw new Error( - "getApiClientByEntity must be used within an ApiClientProvider", - ); - }, -}); - -export const ApiClientProvider: React.FC = ({ - children, -}) => { - const axiosInstance = useAxiosInstance(); - const apiClient = new ApiClient(axiosInstance); - - return ( - getApiClientByEntity(type, apiClient), - }} - > - {children} - - ); -}; export const useApiClient = (): ApiClientContext => { const context = useContext(ApiClientContext); diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100755 index 0000000..2f8c7bf --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,51 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useRouter } from "next/router"; +import { useContext } from "react"; + +import { AuthContext } from "@/contexts/auth.context"; +import { RouterType } from "@/services/types"; + +export const CURRENT_USER_KEY = "current-user"; +export const PUBLIC_PATHS = [ + "/login/[[...token]]", + "/register/[token]", + "/reset/[token]", + "/reset", +]; + +export const useAuth = () => { + const context = useContext(AuthContext); + + if (!context) { + throw new Error(`useAuth must be used within an AuthProvider`); + } + + return context; +}; + +export const useLogoutRedirection = () => { + const router = useRouter(); + const hasPublicPath = PUBLIC_PATHS.includes(router.pathname); + const logoutRedirection = async (fullReload: boolean = false) => { + if (!hasPublicPath) { + const redirectUrl = `/${RouterType.LOGIN}?redirect=${encodeURIComponent( + router.pathname, + )}`; + + if (fullReload) { + window.location.replace(redirectUrl); + } else { + await router.replace(redirectUrl); + } + } + }; + + return { logoutRedirection }; +}; diff --git a/frontend/src/hooks/useConfig.ts b/frontend/src/hooks/useConfig.ts new file mode 100644 index 0000000..0179511 --- /dev/null +++ b/frontend/src/hooks/useConfig.ts @@ -0,0 +1,21 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useContext } from "react"; + +import { ConfigContext } from "@/contexts/config.context"; + +export const useConfig = () => { + const context = useContext(ConfigContext); + + if (!context) { + throw new Error("useConfig must be used within a ConfigProvider"); + } + + return context; +}; diff --git a/frontend/src/hooks/useHasPermission.ts b/frontend/src/hooks/useHasPermission.ts new file mode 100644 index 0000000..09b6be6 --- /dev/null +++ b/frontend/src/hooks/useHasPermission.ts @@ -0,0 +1,27 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useCallback, useContext } from "react"; + +import { PermissionContext } from "@/contexts/permission.context"; +import { EntityType } from "@/services/types"; +import { PermissionAction } from "@/types/permission.types"; + +export const useHasPermission = () => { + const { getAllowedActions } = useContext(PermissionContext); + const hasPermission = useCallback( + (type: EntityType, action: PermissionAction) => { + const allowedActions = getAllowedActions(type); + + return allowedActions?.includes(action); + }, + [getAllowedActions], + ); + + return hasPermission; +}; diff --git a/frontend/src/hooks/useSetting.ts b/frontend/src/hooks/useSetting.ts new file mode 100644 index 0000000..36bfc99 --- /dev/null +++ b/frontend/src/hooks/useSetting.ts @@ -0,0 +1,20 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useContext } from "react"; + +import { SettingsContext } from "@/contexts/setting.context"; + +export const useSetting = (type: string, label: string) => { + const { settings } = useContext(SettingsContext); + const value = settings?.[type]?.find( + (setting) => setting.label === label, + )?.value; + + return value; +}; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index a84a475..a0b2067 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -17,14 +17,15 @@ import { QueryClient, QueryClientProvider } from "react-query"; import { ReactQueryDevtools } from "react-query/devtools"; import { SnackbarCloseButton } from "@/app-components/displays/Toast/CloseButton"; -import { ApiClientProvider } from "@/hooks/useApiClient"; -import { AuthProvider } from "@/hooks/useAuth"; -import { ConfigProvider } from "@/hooks/useConfig"; -import { PermissionProvider } from "@/hooks/useHasPermission"; -import { SettingsProvider } from "@/hooks/useSetting"; +import { ApiClientProvider } from "@/contexts/apiClient.context"; +import { AuthProvider } from "@/contexts/auth.context"; +import { ConfigProvider } from "@/contexts/config.context"; +import { PermissionProvider } from "@/contexts/permission.context"; +import { SettingsProvider } from "@/contexts/setting.context"; import { ToastProvider } from "@/hooks/useToast"; import { theme } from "@/layout/themes/theme"; import { SocketProvider } from "@/websocket/socket-hooks"; + import "../components/inbox/inbox.css"; import "../i18n/config"; import "../styles/globals.css";