@@ -104,6 +108,10 @@ export function SettingsRoutes() {
element={}
/>
} />
+ {/* }
+ /> */}
}
@@ -112,14 +120,18 @@ export function SettingsRoutes() {
path={SettingsPath.Experience}
element={}
/> */}
- }
- />
- }
- />
+ {isOs && (
+ }
+ />
+ )}
+ {isOs && (
+ }
+ />
+ )}
{/* }
@@ -149,7 +161,10 @@ export function SettingsRoutes() {
path={SettingsWorkspacePath.AutomationsCatchAll}
element={}
/>
- } />
+ }
+ />
}
diff --git a/frontend/core-ui/src/modules/settings/apps/components/AppsHeader.tsx b/frontend/core-ui/src/modules/settings/apps/components/AppsHeader.tsx
index 866ac40b97..286208ee39 100644
--- a/frontend/core-ui/src/modules/settings/apps/components/AppsHeader.tsx
+++ b/frontend/core-ui/src/modules/settings/apps/components/AppsHeader.tsx
@@ -1,9 +1,10 @@
import { PageHeader, PageHeaderEnd, PageHeaderStart } from 'ui-modules';
import { Breadcrumb, Button } from 'erxes-ui';
-import { Link } from 'react-router-dom';
-import { IconShieldCog } from '@tabler/icons-react';
+import { Link, useLocation } from 'react-router-dom';
+import { IconApi, IconPlus } from '@tabler/icons-react';
export function AppsHeader() {
+ const { pathname } = useLocation();
return (
@@ -12,17 +13,31 @@ export function AppsHeader() {
+ {pathname.includes('/create-new-app') && (
+ <>
+
+
+
+
+ >
+ )}
- <>>
- {/* You can add any additional components or buttons here */}
+
);
diff --git a/frontend/core-ui/src/modules/settings/apps/components/AppsSettings.tsx b/frontend/core-ui/src/modules/settings/apps/components/AppsSettings.tsx
index af743766fe..c8302c365b 100644
--- a/frontend/core-ui/src/modules/settings/apps/components/AppsSettings.tsx
+++ b/frontend/core-ui/src/modules/settings/apps/components/AppsSettings.tsx
@@ -1,9 +1,26 @@
-import { AppsHeader } from '@/settings/apps/components/AppsHeader';
+import { appsSettingsColumns } from '@/settings/apps/components/table/AppsSettingsColumns';
+import { useAppsTokens } from '@/settings/apps/hooks/useAppsTokens';
+import { IApp } from '@/settings/apps/types';
+import { RecordTable } from 'erxes-ui';
export const AppsSettings = () => {
+ const { apps, loading } = useAppsTokens();
return (
- <>
-
- >
+
+
+
+
+
+
+
+
+ {loading && }
+
+
+
+
);
};
diff --git a/frontend/core-ui/src/modules/settings/apps/components/CreateToken.tsx b/frontend/core-ui/src/modules/settings/apps/components/CreateToken.tsx
new file mode 100644
index 0000000000..ce180ff529
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/components/CreateToken.tsx
@@ -0,0 +1,159 @@
+import { useAddAppToken } from '@/settings/apps/hooks/useAddAppToken';
+import { useCreateAppForm } from '@/settings/apps/hooks/useCreateAppForm';
+import { TCreateAppForm } from '@/settings/apps/types';
+import { SettingsWorkspacePath } from '@/types/paths/SettingsPath';
+import { IconChevronLeft, IconPlus } from '@tabler/icons-react';
+import {
+ Button,
+ DatePicker,
+ Form,
+ Input,
+ Spinner,
+ Switch,
+ toast,
+} from 'erxes-ui';
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { SelectUsersGroup } from 'ui-modules';
+
+export const CreateToken = () => {
+ const navigate = useNavigate();
+ const { methods } = useCreateAppForm();
+ const { control, handleSubmit, reset, watch } = methods;
+ const { appsAdd, loading } = useAddAppToken();
+
+ const [noExpire, allowAllPermission] = watch([
+ 'noExpire',
+ 'allowAllPermission',
+ ]);
+
+ const onSubmit = (data: TCreateAppForm) => {
+ appsAdd({
+ variables: data,
+ onCompleted: () => {
+ toast({ title: 'Created a token' });
+ navigate(SettingsWorkspacePath.Apps);
+ reset();
+ },
+ onError: (error) =>
+ toast({ title: error.message, variant: 'destructive' }),
+ });
+ };
+
+ return (
+
+
+ );
+};
diff --git a/frontend/core-ui/src/modules/settings/apps/components/table/AppsSettingsColumns.tsx b/frontend/core-ui/src/modules/settings/apps/components/table/AppsSettingsColumns.tsx
index 04f5f091d7..0961bd96a7 100644
--- a/frontend/core-ui/src/modules/settings/apps/components/table/AppsSettingsColumns.tsx
+++ b/frontend/core-ui/src/modules/settings/apps/components/table/AppsSettingsColumns.tsx
@@ -1,42 +1,108 @@
+import useRemoveToken from '@/settings/apps/hooks/useRemoveToken';
import { IApp } from '@/settings/apps/types';
-import { ColumnDef } from '@tanstack/table-core';
+import { IconCopy, IconTrash } from '@tabler/icons-react';
+import { Cell, ColumnDef } from '@tanstack/table-core';
import { format } from 'date-fns';
-import { RecordTableInlineCell } from 'erxes-ui';
+import { Button, RecordTableInlineCell, toast, useConfirm } from 'erxes-ui';
+
+const RemoveButton = ({ cell }: { cell: Cell }) => {
+ const { _id, name } = cell.row.original;
+ const { appsRemove } = useRemoveToken();
+ const { confirm } = useConfirm();
+ const confirmOptions = { confirmationValue: 'delete' };
+
+ return (
+
+ );
+};
+
+const CopyTokenButton = ({ cell }: { cell: Cell }) => {
+ const { accessToken } = cell.row.original;
+ async function copy() {
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
+ try {
+ await navigator.clipboard.writeText(accessToken);
+ toast({
+ title: 'Token copied to clipboard',
+ });
+ } catch {
+ toast({
+ title: 'Failed to copy token',
+ variant: 'destructive',
+ });
+ }
+ }
+ }
+ return (
+
+ );
+};
export const appsSettingsColumns: ColumnDef[] = [
{
id: 'name',
accessorKey: 'name',
header: 'App Name',
- cell: ({ cell }) => (
- {cell.getValue() as string}
- ),
- },
- {
- id: 'clientId',
- accessorKey: 'clientId',
- header: 'Client ID',
- cell: ({ cell }) => (
- {cell.getValue() as string}
- ),
+ cell: ({ cell }) => {
+ const { name } = cell.row.original;
+ return {name ?? ''};
+ },
+ size: 250,
},
{
- id: 'clientSecret',
- accessorKey: 'clientSecret',
- header: 'Client Secret',
- cell: ({ cell }) => (
- {cell.getValue() as string}
- ),
+ id: 'expireDate',
+ accessorKey: 'expireDate',
+ header: 'Expiration',
+ cell: ({ cell }) => {
+ const { expireDate } = cell.row.original;
+ return (
+
+ {format(expireDate, 'yyyy-MM-dd')}
+
+ );
+ },
},
{
- id: 'createdAt',
- accessorKey: 'createdAt',
- header: 'Created At',
- cell: ({ cell }) => (
-
- {format(new Date(cell.getValue() as string), 'yyyy/MM/dd') ||
- 'YYYY/MM/DD'}
-
- ),
+ id: 'actions',
+ cell: ({ cell }) => {
+ return (
+
+
+
+
+ );
+ },
+ size: 100,
},
];
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/index.ts b/frontend/core-ui/src/modules/settings/apps/graphql/index.ts
new file mode 100644
index 0000000000..3cf1ef310b
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/index.ts
@@ -0,0 +1 @@
+export * from './queries';
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/mutations/addToken.ts b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/addToken.ts
new file mode 100644
index 0000000000..e2977cdacd
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/addToken.ts
@@ -0,0 +1,32 @@
+import { gql } from '@apollo/client';
+
+const ADD_TOKEN = gql`
+ mutation AppsAdd(
+ $name: String
+ $userGroupId: String
+ $expireDate: Date
+ $allowAllPermission: Boolean
+ $noExpire: Boolean
+ ) {
+ appsAdd(
+ name: $name
+ userGroupId: $userGroupId
+ expireDate: $expireDate
+ allowAllPermission: $allowAllPermission
+ noExpire: $noExpire
+ ) {
+ _id
+ accessToken
+ allowAllPermission
+ createdAt
+ expireDate
+ isEnabled
+ name
+ noExpire
+ refreshToken
+ userGroupId
+ }
+ }
+`;
+
+export { ADD_TOKEN };
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/mutations/index.ts b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/index.ts
new file mode 100644
index 0000000000..d0a40dc069
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/index.ts
@@ -0,0 +1,2 @@
+export * from './addToken';
+export * from './removeToken';
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/mutations/removeToken.ts b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/removeToken.ts
new file mode 100644
index 0000000000..8d2d4fa2f6
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/mutations/removeToken.ts
@@ -0,0 +1,9 @@
+import { gql } from '@apollo/client';
+
+const REMOVE_TOKEN = gql`
+ mutation AppsRemove($_id: String!) {
+ appsRemove(_id: $_id)
+ }
+`;
+
+export { REMOVE_TOKEN };
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/queries/getApps.ts b/frontend/core-ui/src/modules/settings/apps/graphql/queries/getApps.ts
new file mode 100644
index 0000000000..b69112c017
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/queries/getApps.ts
@@ -0,0 +1,19 @@
+import { gql } from '@apollo/client';
+
+const GET_APPS = gql`
+ query Apps {
+ apps {
+ _id
+ accessToken
+ allowAllPermission
+ createdAt
+ expireDate
+ isEnabled
+ name
+ noExpire
+ userGroupId
+ }
+ }
+`;
+
+export { GET_APPS };
diff --git a/frontend/core-ui/src/modules/settings/apps/graphql/queries/index.ts b/frontend/core-ui/src/modules/settings/apps/graphql/queries/index.ts
new file mode 100644
index 0000000000..d8b69e8119
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/graphql/queries/index.ts
@@ -0,0 +1 @@
+export * from './getApps';
diff --git a/frontend/core-ui/src/modules/settings/apps/hooks/useAddAppToken.tsx b/frontend/core-ui/src/modules/settings/apps/hooks/useAddAppToken.tsx
new file mode 100644
index 0000000000..7d16b7113a
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/hooks/useAddAppToken.tsx
@@ -0,0 +1,16 @@
+import { ADD_TOKEN } from '@/settings/apps/graphql/mutations';
+import { IApp } from '@/settings/apps/types';
+import { MutationFunctionOptions, useMutation } from '@apollo/client';
+
+interface IResult {
+ appsAdd: IApp;
+}
+
+export const useAddAppToken = () => {
+ const [mutate, { loading, error }] = useMutation(ADD_TOKEN);
+ return {
+ appsAdd: mutate,
+ loading,
+ error,
+ };
+};
diff --git a/frontend/core-ui/src/modules/settings/apps/hooks/useAppsTokens.tsx b/frontend/core-ui/src/modules/settings/apps/hooks/useAppsTokens.tsx
new file mode 100644
index 0000000000..70069c0736
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/hooks/useAppsTokens.tsx
@@ -0,0 +1,19 @@
+import { GET_APPS } from '@/settings/apps/graphql';
+import { IApp } from '@/settings/apps/types';
+import { QueryHookOptions, useQuery } from '@apollo/client';
+
+interface IApssResponse {
+ apps: IApp[];
+}
+
+export const useAppsTokens = (options?: QueryHookOptions) => {
+ const { data, error, loading } = useQuery(GET_APPS, {
+ ...options,
+ });
+ const apps = data?.apps || [];
+ return {
+ apps,
+ loading,
+ error,
+ };
+};
diff --git a/frontend/core-ui/src/modules/settings/apps/hooks/useCreateAppForm.tsx b/frontend/core-ui/src/modules/settings/apps/hooks/useCreateAppForm.tsx
new file mode 100644
index 0000000000..f58eda97a9
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/hooks/useCreateAppForm.tsx
@@ -0,0 +1,21 @@
+import { CREATE_TOKEN_SCHEMA } from '@/settings/apps/schema';
+import { TCreateAppForm } from '@/settings/apps/types';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+
+export const useCreateAppForm = () => {
+ const form = useForm({
+ mode: 'onBlur',
+ defaultValues: {
+ name: '',
+ noExpire: false,
+ allowAllPermission: false,
+ expireDate: new Date(),
+ userGroupId: '',
+ },
+ resolver: zodResolver(CREATE_TOKEN_SCHEMA),
+ });
+ return {
+ methods: form,
+ };
+};
diff --git a/frontend/core-ui/src/modules/settings/apps/hooks/useRemoveToken.tsx b/frontend/core-ui/src/modules/settings/apps/hooks/useRemoveToken.tsx
new file mode 100644
index 0000000000..daa67a89a9
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/hooks/useRemoveToken.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { MutationFunctionOptions, useMutation } from '@apollo/client';
+import { REMOVE_TOKEN } from '../graphql/mutations';
+import { toast } from 'erxes-ui';
+
+interface IRemoveResults {
+ appsRemove: boolean;
+}
+
+const useRemoveToken = () => {
+ const [mutate, { loading, error }] =
+ useMutation(REMOVE_TOKEN);
+
+ const handleRemoveToken = (
+ options: MutationFunctionOptions,
+ ) => {
+ return mutate({
+ ...options,
+ onError: (error) => {
+ toast({
+ title: 'Error',
+ description: error.message,
+ variant: 'destructive',
+ });
+ },
+ refetchQueries: ['Apps'],
+ });
+ };
+ return {
+ appsRemove: handleRemoveToken,
+ loading,
+ error,
+ };
+};
+
+export default useRemoveToken;
diff --git a/frontend/core-ui/src/modules/settings/apps/schema.ts b/frontend/core-ui/src/modules/settings/apps/schema.ts
new file mode 100644
index 0000000000..ecee6ed7b2
--- /dev/null
+++ b/frontend/core-ui/src/modules/settings/apps/schema.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+export const CREATE_TOKEN_SCHEMA = z.object({
+ name: z
+ .string()
+ .trim()
+ .min(1, 'Name is required')
+ .max(100, 'Name must be ≤ 100 chars'),
+ userGroupId: z.string().trim().optional(),
+ expireDate: z.coerce.date().optional(),
+ allowAllPermission: z.boolean().default(false),
+ noExpire: z.boolean().default(false),
+});
diff --git a/frontend/core-ui/src/modules/settings/apps/types.ts b/frontend/core-ui/src/modules/settings/apps/types.ts
index 6986f8e549..8f833bb1cb 100644
--- a/frontend/core-ui/src/modules/settings/apps/types.ts
+++ b/frontend/core-ui/src/modules/settings/apps/types.ts
@@ -1,7 +1,17 @@
+import { CREATE_TOKEN_SCHEMA } from '@/settings/apps/schema';
+import { z } from 'zod';
+
export interface IApp {
_id: string;
- clientId: string;
- clientSecret: string;
+ accessToken: string;
+ allowAllPermission: boolean;
+ createdAt: Date;
+ expireDate: Date;
+ isEnabled: boolean;
name: string;
- createdAt: string;
+ noExpire: boolean;
+ refreshToken: string;
+ userGroupId: string;
}
+
+export type TCreateAppForm = z.infer;
diff --git a/frontend/core-ui/src/modules/settings/components/SettingsSidebar.tsx b/frontend/core-ui/src/modules/settings/components/SettingsSidebar.tsx
index 33530a33a2..aef365d986 100644
--- a/frontend/core-ui/src/modules/settings/components/SettingsSidebar.tsx
+++ b/frontend/core-ui/src/modules/settings/components/SettingsSidebar.tsx
@@ -6,16 +6,20 @@ import { Sidebar, IUIConfig, NavigationMenuLinkItem } from 'erxes-ui';
import { AppPath } from '@/types/paths/AppPath';
import { CORE_MODULES } from '~/plugins/constants/core-plugins.constants';
-import { pluginsConfigState } from 'ui-modules';
+import { pluginsConfigState, currentOrganizationState } from 'ui-modules';
import { useAtomValue } from 'jotai';
import { SETTINGS_PATH_DATA } from '../constants/data';
import { useMemo } from 'react';
import { usePageTrackerStore } from 'react-page-tracker';
+import { SettingsWorkspacePath } from '@/types/paths/SettingsPath';
export function SettingsSidebar() {
const pluginsMetaData = useAtomValue(pluginsConfigState) || {};
+ const currentOrganization = useAtomValue(currentOrganizationState);
+ const isOs = currentOrganization?.type === 'os';
+
const pluginsWithSettingsModules: Map =
useMemo(() => {
if (pluginsMetaData) {
@@ -54,14 +58,22 @@ export function SettingsSidebar() {
))}
- {SETTINGS_PATH_DATA.nav.map((item) => (
-
- ))}
+ {SETTINGS_PATH_DATA.nav
+ .filter((item) => {
+ const isRestricted =
+ item.path === SettingsWorkspacePath.FileUpload ||
+ item.path === SettingsWorkspacePath.MailConfig;
+ return isOs || !isRestricted;
+ })
+ .map((item) => (
+
+
+
+ ))}
diff --git a/frontend/core-ui/src/modules/settings/constants/data.ts b/frontend/core-ui/src/modules/settings/constants/data.ts
index fa0ca57122..e21d26a40e 100644
--- a/frontend/core-ui/src/modules/settings/constants/data.ts
+++ b/frontend/core-ui/src/modules/settings/constants/data.ts
@@ -9,6 +9,7 @@ import {
IconMail,
IconPassword,
IconTag,
+ IconApi,
IconUserCircle,
IconUserCog,
IconUsersGroup,
@@ -118,6 +119,11 @@ export const SETTINGS_PATH_DATA: { [key: string]: TSettingPath[] } = {
// icon: IconColorSwatch,
// path: SettingsPath.Experience,
// },
+ // {
+ // name: 'Notification',
+ // icon: IconBellRinging,
+ // path: SettingsPath.Notification,
+ // },
],
nav: [
{
@@ -161,5 +167,10 @@ export const SETTINGS_PATH_DATA: { [key: string]: TSettingPath[] } = {
icon: IconChessKnight,
path: SettingsWorkspacePath.Brands,
},
+ {
+ name: 'Apps',
+ icon: IconApi,
+ path: SettingsWorkspacePath.Apps,
+ },
],
};
diff --git a/frontend/core-ui/src/modules/types/paths/SettingsPath.ts b/frontend/core-ui/src/modules/types/paths/SettingsPath.ts
index 24419ff1f1..89a54594f6 100644
--- a/frontend/core-ui/src/modules/types/paths/SettingsPath.ts
+++ b/frontend/core-ui/src/modules/types/paths/SettingsPath.ts
@@ -12,6 +12,7 @@ export enum SettingsWorkspacePath {
FileUpload = 'file-upload',
MailConfig = 'mail-config',
Apps = 'apps',
+ AppsCatchAll = 'apps/*',
Permissions = 'permissions',
Properties = 'properties',
TeamMember = 'team-member',
diff --git a/frontend/core-ui/src/pages/settings/workspace/AppSettingsPage.tsx b/frontend/core-ui/src/pages/settings/workspace/AppSettingsPage.tsx
index 160d574fcb..88877c874a 100644
--- a/frontend/core-ui/src/pages/settings/workspace/AppSettingsPage.tsx
+++ b/frontend/core-ui/src/pages/settings/workspace/AppSettingsPage.tsx
@@ -1,10 +1,26 @@
+import { AppsHeader } from '@/settings/apps/components/AppsHeader';
import { AppsSettings } from '@/settings/apps/components/AppsSettings';
-import { PageContainer } from 'erxes-ui';
+import { CreateToken } from '@/settings/apps/components/CreateToken';
+import { PageContainer, Spinner } from 'erxes-ui';
+import { Suspense } from 'react';
+import { Route, Routes } from 'react-router-dom';
export function AppSettingsPage() {
return (
-
+
+
+
+
+ }
+ >
+
+ } />
+ } />
+
+
);
}
diff --git a/frontend/libs/ui-modules/src/modules/structure/components/SelectPositions.tsx b/frontend/libs/ui-modules/src/modules/structure/components/SelectPositions.tsx
index 0774f07417..4019aa3256 100644
--- a/frontend/libs/ui-modules/src/modules/structure/components/SelectPositions.tsx
+++ b/frontend/libs/ui-modules/src/modules/structure/components/SelectPositions.tsx
@@ -317,7 +317,7 @@ const SelectPositionsBadgesView = () => {
{
if (!position) return;
if (positionIds.includes(position._id)) {
diff --git a/frontend/libs/ui-modules/src/states/currentOrganizationState.ts b/frontend/libs/ui-modules/src/states/currentOrganizationState.ts
index 5990bb537f..e7db123dca 100644
--- a/frontend/libs/ui-modules/src/states/currentOrganizationState.ts
+++ b/frontend/libs/ui-modules/src/states/currentOrganizationState.ts
@@ -12,6 +12,7 @@ export type CurrentOrganization = {
name: string;
url: string;
}[];
+ type?: string;
};
export const currentOrganizationState = atom(null);