Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions frontend/core-ui/src/modules/app/components/SettingsRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { SettingsExperiencePage } from '~/pages/settings/account/ExperiencePage'
import { getPluginsSettingsRoutes } from '@/app/hooks/usePluginsRouter';
import { Skeleton } from 'erxes-ui';
import { SettingsPageEffect } from '@/settings/components/SettingsPageEffect';
import { currentOrganizationState } from 'ui-modules';
import { useAtomValue } from 'jotai';

const SettingsProfile = lazy(() =>
import('~/pages/settings/account/ProfilePage').then((module) => ({
Expand Down Expand Up @@ -104,6 +106,8 @@ const PropertiesSettins = lazy(() =>
);

export function SettingsRoutes() {
const currentOrganization = useAtomValue(currentOrganizationState);
const isOs = currentOrganization?.type === 'os';
return (
<Suspense fallback={<Skeleton />}>
<Routes>
Expand All @@ -112,10 +116,10 @@ export function SettingsRoutes() {
element={<Navigate to={`${SettingsPath.Profile}`} replace />}
/>
<Route path={SettingsPath.Profile} element={<SettingsProfile />} />
<Route
{/* <Route
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove stale commented-out routes (e.g. Notification, Experience, General) if they are no longer needed to keep the code clean.

path={SettingsPath.Notification}
element={<NotificationSettingsRoutes />}
/>
/> */}
<Route
path={SettingsPath.ChangePassword}
element={<SettingsChangePassword />}
Expand All @@ -124,14 +128,18 @@ export function SettingsRoutes() {
path={SettingsPath.Experience}
element={<SettingsExperiencePage />}
/> */}
<Route
path={SettingsWorkspacePath.FileUpload}
element={<SettingsFileUpload />}
/>
<Route
path={SettingsWorkspacePath.MailConfig}
element={<SettingsMailConfig />}
/>
{isOs && (
<Route
path={SettingsWorkspacePath.FileUpload}
element={<SettingsFileUpload />}
/>
)}
{isOs && (
<Route
path={SettingsWorkspacePath.MailConfig}
element={<SettingsMailConfig />}
/>
)}
{/* <Route
path={SettingsWorkspacePath.General}
element={<GeneralSettings />}
Expand Down Expand Up @@ -161,7 +169,10 @@ export function SettingsRoutes() {
path={SettingsWorkspacePath.AutomationsCatchAll}
element={<AutomationSettingsRoutes />}
/>
<Route path={SettingsWorkspacePath.Apps} element={<AppsSettings />} />
<Route
path={SettingsWorkspacePath.AppsCatchAll}
element={<AppsSettings />}
/>
<Route
path={SettingsWorkspacePath.Properties}
element={<PropertiesSettins />}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<PageHeader>
<PageHeaderStart>
Expand All @@ -12,17 +13,31 @@ export function AppsHeader() {
<Breadcrumb.Item>
<Button variant="ghost" asChild>
<Link to="/settings/apps">
<IconShieldCog />
<IconApi />
Apps
</Link>
</Button>
</Breadcrumb.Item>
{pathname.includes('/create-new-app') && (
<>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Button variant={'ghost'} asChild>
<Link to="/settings/apps/create-new-app">New token</Link>
</Button>
</Breadcrumb.Item>
</>
)}
</Breadcrumb.List>
</Breadcrumb>
</PageHeaderStart>
<PageHeaderEnd>
<></>
{/* You can add any additional components or buttons here */}
<Button asChild>
<Link to="/settings/apps/create-new-app">
<IconPlus />
Create App token
</Link>
</Button>
</PageHeaderEnd>
</PageHeader>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<AppsHeader />
</>
<section className="max-w-xl w-full mx-auto">
<legend className="font-semibold text-lg pt-4 pb-6">Apps settings</legend>

<RecordTable.Provider
columns={appsSettingsColumns}
data={apps as IApp[]}
>
<RecordTable className='w-full'>
<RecordTable.Header />
<RecordTable.Body>
<RecordTable.RowList />
{loading && <RecordTable.RowSkeleton rows={30} />}
</RecordTable.Body>
</RecordTable>
</RecordTable.Provider>
</section>
);
};
157 changes: 157 additions & 0 deletions frontend/core-ui/src/modules/settings/apps/components/CreateToken.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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';
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' }),
});
};
Comment on lines +30 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Normalize payload before mutation (trim, gate fields, ISO date)

Prevents sending empty userGroupId or expireDate when toggles disable them; avoids backend reject.

Apply:

   const onSubmit = (data: TCreateAppForm) => {
-    appsAdd({
-      variables: data,
+    const variables = {
+      ...data,
+      name: data.name.trim(),
+      userGroupId: data.allowAllPermission
+        ? undefined
+        : data.userGroupId?.trim() || undefined,
+      expireDate: data.noExpire
+        ? undefined
+        : data.expireDate?.toISOString(),
+    };
+    appsAdd({
+      variables,
       onCompleted: () => {
         toast({ title: 'Created a token' });
         navigate(SettingsWorkspacePath.Apps);
         reset();
       },
       onError: (error) =>
         toast({ title: error.message, variant: 'destructive' }),
     });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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' }),
});
};
const onSubmit = (data: TCreateAppForm) => {
const variables = {
...data,
name: data.name.trim(),
userGroupId: data.allowAllPermission
? undefined
: data.userGroupId?.trim() || undefined,
expireDate: data.noExpire
? undefined
: data.expireDate?.toISOString(),
};
appsAdd({
variables,
onCompleted: () => {
toast({ title: 'Created a token' });
navigate(SettingsWorkspacePath.Apps);
reset();
},
onError: (error) =>
toast({ title: error.message, variant: 'destructive' }),
});
};
🤖 Prompt for AI Agents
In frontend/core-ui/src/modules/settings/apps/components/CreateToken.tsx around
lines 30 to 41, the mutation is sending raw form data which can include empty or
disabled fields and non-ISO dates; normalize the payload before calling appsAdd
by trimming string fields (e.g., name, description), omitting userGroupId when
the corresponding toggle is off or the value is empty, omitting expireDate when
the expiration toggle is off, and converting any expireDate to an ISO string
(e.g., expireDate.toISOString()) before passing variables; then call appsAdd
with this sanitized payload so the backend doesn't receive empty/invalid fields.


return (
<Form {...methods}>
<form
onSubmit={handleSubmit(onSubmit)}
className="max-w-lg w-full mx-auto flex flex-col space-y-4"
>
<fieldset className="grid grid-cols-2 gap-3">
<legend className="font-semibold text-lg pt-4 pb-6 flex items-center gap-x-1">
<Button
variant={'ghost'}
size={'icon'}
onClick={() => navigate(-1)}
>
<IconChevronLeft />
</Button>
New token
</legend>
<Form.Field
control={control}
name="name"
render={({ field }) => (
<Form.Item className="col-span-2">
<Form.Label>Name</Form.Label>
<Form.Control>
<Input {...field} placeholder="Name" />
</Form.Control>
<Form.Message />
</Form.Item>
)}
/>
<span className="grid grid-cols-2 col-span-2">
<Form.Field
control={control}
name="allowAllPermission"
render={({ field }) => (
<Form.Item className="flex flex-wrap">
<Form.Label className="w-full">Allow all</Form.Label>
<Form.Control>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</Form.Control>
<Form.Message />
</Form.Item>
)}
/>
{(allowAllPermission && null) || (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer using a clear conditional like {!allowAllPermission && ( ... )} rather than using {(allowAllPermission && null) || ( ... )}. This improves readability.

Suggested change
{(allowAllPermission && null) || (
{!allowAllPermission && (

<Form.Field
control={control}
name="userGroupId"
render={({ field }) => (
<Form.Item>
<Form.Label>User group</Form.Label>
<Form.Control>
<SelectUsersGroup.FormItem
value={field.value}
mode="single"
onValueChange={field.onChange}
/>
</Form.Control>
<Form.Message />
</Form.Item>
)}
/>
)}
</span>
<span className="grid grid-cols-2 col-span-2">
<Form.Field
control={control}
name="noExpire"
render={({ field }) => (
<Form.Item className="flex flex-wrap">
<Form.Label className="w-full">No expire</Form.Label>
<Form.Control>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</Form.Control>
<Form.Message />
</Form.Item>
)}
/>
{(noExpire && null) || (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a simpler conditional rendering, e.g. {!noExpire && ( ... )} instead of {(noExpire && null) || ( ... )}, for better clarity.

Suggested change
{(noExpire && null) || (
{!noExpire && (

<Form.Field
control={control}
name="expireDate"
render={({ field }) => (
<Form.Item>
<Form.Label>Expire date</Form.Label>
<Form.Control>
<DatePicker
defaultMonth={field.value || new Date()}
value={field.value}
onChange={field.onChange}
/>
</Form.Control>
<Form.Message />
</Form.Item>
)}
/>
)}
</span>
</fieldset>
<div role="group" className="w-full flex items-center pt-6">
<Button type="submit" className="isolate ml-auto" disabled={loading}>
{(loading && <Spinner />) || <IconPlus />}
Add
</Button>
</div>
</form>
</Form>
);
};
Loading