1859 extensibility improve application settings section (#16786)
## After <img width="934" height="750" alt="image" src="https://github.com/user-attachments/assets/f2d7f743-fa4f-4e7d-a060-033f6087e4b6" /> <img width="1008" height="652" alt="image" src="https://github.com/user-attachments/assets/3d36bfae-aa30-4946-88f2-e99bf074f768" />
This commit is contained in:
@@ -1083,7 +1083,7 @@ __metadata:
|
||||
resolution: "hello-world@workspace:."
|
||||
dependencies:
|
||||
"@types/node": "npm:^24.7.2"
|
||||
twenty-sdk: "npm:0.2.1"
|
||||
twenty-sdk: "npm:0.2.4"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -1904,9 +1904,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:0.2.1":
|
||||
version: 0.2.1
|
||||
resolution: "twenty-sdk@npm:0.2.1"
|
||||
"twenty-sdk@npm:0.2.4":
|
||||
version: 0.2.4
|
||||
resolution: "twenty-sdk@npm:0.2.4"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
axios: "npm:^1.6.0"
|
||||
@@ -1927,7 +1927,7 @@ __metadata:
|
||||
uuid: "npm:^13.0.0"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/5a4f4674129cc747e8e5091bf75025260db20087f76f1cee49896a073c8cf83b8c95e1dce3f2f2cbaaf9570aa1886c4c9a5585f0e61e0ee79552eab4d292fb4e
|
||||
checksum: 10c0/91bcc2c6a96d8bb7997c6584de338d370ffd4cebbc052ffdd5dd8bb8894f1851c039601a2ce0bb0f3b97e210f3bc877de0572125b8c3b601f6e1baac2f99a8c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500;600&display=swap"
|
||||
/>
|
||||
|
||||
<title>Twenty</title>
|
||||
<!-- BEGIN: Twenty Config -->
|
||||
|
||||
+10
-21
@@ -136,6 +136,13 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
fieldName: fieldMetadataItem.name,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-no-navigate-prefer-link
|
||||
const navigateToFieldEdit = () =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
});
|
||||
|
||||
const { activateMetadataField } = useFieldMetadataItem();
|
||||
|
||||
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();
|
||||
@@ -195,15 +202,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
|
||||
return (
|
||||
<StyledObjectFieldTableRow
|
||||
onClick={
|
||||
mode === 'view'
|
||||
? () =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
onClick={mode === 'view' ? navigateToFieldEdit : undefined}
|
||||
>
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledNameTableCell>
|
||||
@@ -251,12 +250,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
<StyledIconTableCell>
|
||||
{status === 'active' ? (
|
||||
mode === 'view' ? (
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
})}
|
||||
>
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
@@ -276,12 +270,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
isCustomField={fieldMetadataItem.isCustom === true}
|
||||
readonly={readonly}
|
||||
fieldMetadataItemId={fieldMetadataItem.id}
|
||||
onEdit={() =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
})
|
||||
}
|
||||
onEdit={navigateToFieldEdit}
|
||||
onActivate={() =>
|
||||
activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id)
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
label: t`Documentation`,
|
||||
onClick: () =>
|
||||
window.open(
|
||||
getDocumentationUrl(currentWorkspaceMember?.locale),
|
||||
getDocumentationUrl({ locale: currentWorkspaceMember?.locale }),
|
||||
'_blank',
|
||||
),
|
||||
Icon: IconHelpCircle,
|
||||
|
||||
+12
-8
@@ -1,19 +1,20 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { type ServerlessFunction } from '~/generated-metadata/graphql';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconChevronRight } from 'twenty-ui/display';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 312px 132px 68px;
|
||||
`;
|
||||
import { StyledTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable';
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledRuntimeTableCell = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
@@ -32,15 +33,18 @@ export const SettingsServerlessFunctionsFieldItemTableRow = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledApisFieldTableRow to={to}>
|
||||
<StyledTableRow to={to}>
|
||||
<StyledNameTableCell>{serverlessFunction.name}</StyledNameTableCell>
|
||||
<StyledNameTableCell>{serverlessFunction.runtime}</StyledNameTableCell>
|
||||
<StyledNameTableCell></StyledNameTableCell>
|
||||
<StyledRuntimeTableCell>
|
||||
{serverlessFunction.runtime}
|
||||
</StyledRuntimeTableCell>
|
||||
<StyledIconTableCell>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconTableCell>
|
||||
</StyledApisFieldTableRow>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-2
@@ -10,8 +10,8 @@ import { type ServerlessFunction } from '~/generated-metadata/graphql';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 312px 132px 68px;
|
||||
export const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 164px 1fr 96px 32px;
|
||||
`;
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
@@ -35,6 +35,7 @@ export const SettingsServerlessFunctionsTable = ({
|
||||
<Table>
|
||||
<StyledTableRow>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
<TableHeader>{t`Runtime`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
|
||||
+3
-3
@@ -1,4 +1,3 @@
|
||||
import { type EnvironmentVariable } from '~/pages/settings/applications/tabs/SettingsApplicationDetailEnvironmentVariablesTableRow';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import type { ApplicationVariable } from '~/generated/graphql';
|
||||
|
||||
const StyledEditModeTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px auto 56px;
|
||||
@@ -34,8 +34,8 @@ export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
|
||||
onDelete,
|
||||
initialEditMode = false,
|
||||
}: {
|
||||
envVariable: EnvironmentVariable;
|
||||
onChange: (newEnvVariable: EnvironmentVariable) => void;
|
||||
envVariable: ApplicationVariable;
|
||||
onChange: (newEnvVariable: ApplicationVariable) => void;
|
||||
onDelete: () => void;
|
||||
initialEditMode?: boolean;
|
||||
}) => {
|
||||
|
||||
@@ -4,17 +4,23 @@ const DOCUMENTATION_PATH = '/user-guide/introduction';
|
||||
// Locales that have documentation translations available
|
||||
const SUPPORTED_DOC_LOCALES = ['fr', 'pt', 'de', 'es', 'it', 'ja', 'ko', 'zh'];
|
||||
|
||||
export const getDocumentationUrl = (locale?: string | null): string => {
|
||||
export const getDocumentationUrl = ({
|
||||
locale,
|
||||
path = DOCUMENTATION_PATH,
|
||||
}: {
|
||||
locale?: string | null;
|
||||
path?: string;
|
||||
}): string => {
|
||||
if (!locale) {
|
||||
return `${DOCUMENTATION_BASE_URL}${DOCUMENTATION_PATH}`;
|
||||
return `${DOCUMENTATION_BASE_URL}${path}`;
|
||||
}
|
||||
|
||||
// Extract language code from locale (e.g., 'fr' from 'fr-FR')
|
||||
const langCode = locale.split('-')[0].toLowerCase();
|
||||
|
||||
if (SUPPORTED_DOC_LOCALES.includes(langCode)) {
|
||||
return `${DOCUMENTATION_BASE_URL}/l/${langCode}${DOCUMENTATION_PATH}`;
|
||||
return `${DOCUMENTATION_BASE_URL}/l/${langCode}${path}`;
|
||||
}
|
||||
|
||||
return `${DOCUMENTATION_BASE_URL}${DOCUMENTATION_PATH}`;
|
||||
return `${DOCUMENTATION_BASE_URL}${path}`;
|
||||
};
|
||||
|
||||
+4
-1
@@ -84,7 +84,10 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
};
|
||||
|
||||
const handleDocumentation = () => {
|
||||
window.open(getDocumentationUrl(currentWorkspaceMember?.locale), '_blank');
|
||||
window.open(
|
||||
getDocumentationUrl({ locale: currentWorkspaceMember?.locale }),
|
||||
'_blank',
|
||||
);
|
||||
closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
|
||||
};
|
||||
|
||||
|
||||
+6
-6
@@ -5,7 +5,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useFindOneApplicationQuery } from '~/generated-metadata/graphql';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { IconInfoCircle, IconSettings, IconBroadcast } from 'twenty-ui/display';
|
||||
import { IconInfoCircle, IconSettings, IconBox } from 'twenty-ui/display';
|
||||
import { SettingsApplicationDetailSkeletonLoader } from '~/pages/settings/applications/components/SettingsApplicationDetailSkeletonLoader';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
@@ -39,22 +39,22 @@ export const SettingsApplicationDetails = () => {
|
||||
|
||||
const tabs = [
|
||||
{ id: 'about', title: t`About`, Icon: IconInfoCircle },
|
||||
{ id: 'content', title: t`Content`, Icon: IconBox },
|
||||
{ id: 'settings', title: t`Settings`, Icon: IconSettings },
|
||||
{ id: 'content', title: t`Content`, Icon: IconBroadcast },
|
||||
];
|
||||
|
||||
const renderActiveTabContent = () => {
|
||||
switch (activeTabId) {
|
||||
case 'about':
|
||||
return <SettingsApplicationDetailAboutTab application={application} />;
|
||||
case 'settings':
|
||||
return (
|
||||
<SettingsApplicationDetailSettingsTab application={application} />
|
||||
);
|
||||
case 'content':
|
||||
return (
|
||||
<SettingsApplicationDetailContentTab application={application} />
|
||||
);
|
||||
case 'settings':
|
||||
return (
|
||||
<SettingsApplicationDetailSettingsTab application={application} />
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,56 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
|
||||
import { SettingsApplicationsTable } from '~/pages/settings/applications/components/SettingsApplicationsTable';
|
||||
import {
|
||||
H2Title,
|
||||
CommandBlock,
|
||||
IconCopy,
|
||||
IconFileInfo,
|
||||
} from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
|
||||
const StyledNoApplicationContainer = styled.div``;
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin: ${({ theme }) => theme.spacing(2)} 0;
|
||||
`;
|
||||
|
||||
export const SettingsApplications = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const { data } = useFindManyApplicationsQuery();
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
|
||||
const applications = data?.findManyApplications ?? [];
|
||||
|
||||
const commands = [
|
||||
// eslint-disable-next-line lingui/no-unlocalized-strings
|
||||
'npx create-twenty-app@latest my-twenty-app',
|
||||
// eslint-disable-next-line lingui/no-unlocalized-strings
|
||||
'cd my-twenty-app',
|
||||
];
|
||||
|
||||
const button = (
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyToClipboard(commands.join('\n'), t`Commands copied to clipboard`);
|
||||
}}
|
||||
ariaLabel={t`Copy commands`}
|
||||
Icon={IconCopy}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Applications`}
|
||||
@@ -29,22 +63,30 @@ export const SettingsApplications = () => {
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
{applications.length > 0 && (
|
||||
<SettingsApplicationsTable applications={applications} />
|
||||
)}
|
||||
<Section>
|
||||
{applications.length > 0 ? (
|
||||
<SettingsApplicationsTable applications={applications} />
|
||||
) : (
|
||||
<StyledNoApplicationContainer>
|
||||
<Trans>
|
||||
No installed application. Please check our{' '}
|
||||
<LinkDisplay
|
||||
value={{
|
||||
url: 'https://www.npmjs.com/package/twenty-cli',
|
||||
label: 'twenty-cli',
|
||||
}}
|
||||
/>
|
||||
</Trans>
|
||||
</StyledNoApplicationContainer>
|
||||
)}
|
||||
<H2Title
|
||||
title={t`Create an application`}
|
||||
description={t`You can either create a private app or share it to others`}
|
||||
/>
|
||||
<CommandBlock commands={commands} button={button} />
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconFileInfo}
|
||||
title={t`Read documentation`}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
getDocumentationUrl({
|
||||
locale: currentWorkspaceMember?.locale,
|
||||
path: '/developers/extend/capabilities/apps',
|
||||
}),
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
|
||||
+2
-2
@@ -13,11 +13,11 @@ export type SettingsApplicationTableRowProps = {
|
||||
};
|
||||
|
||||
export const StyledApplicationTableRow = styled(TableRow)`
|
||||
grid-template-columns: 1fr 120px 36px;
|
||||
grid-template-columns: 164px minmax(0, 1fr) 36px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
+57
-23
@@ -1,4 +1,4 @@
|
||||
import { IconChevronRight } from 'twenty-ui/display';
|
||||
import { H2Title, IconChevronRight, IconSearch } from 'twenty-ui/display';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
} from '~/pages/settings/applications/components/SettingsApplicationTableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { type ApplicationWithoutRelation } from '~/pages/settings/applications/types/applicationWithoutRelation';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||
@@ -20,6 +23,11 @@ const StyledTableHeaderRow = styled(StyledApplicationTableRow)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsApplicationsTable = ({
|
||||
applications,
|
||||
}: {
|
||||
@@ -29,28 +37,54 @@ export const SettingsApplicationsTable = ({
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const filteredApplications = useMemo(() => {
|
||||
return applications.filter(
|
||||
(application) =>
|
||||
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
application.description
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}, [applications, searchTerm]);
|
||||
|
||||
return (
|
||||
<StyledTable>
|
||||
<StyledTableHeaderRow>
|
||||
<TableHeader> {t`Name`}</TableHeader>
|
||||
<TableHeader> {t`Description`}</TableHeader>
|
||||
<TableHeader />
|
||||
</StyledTableHeaderRow>
|
||||
{applications.map((application) => (
|
||||
<SettingsApplicationTableRow
|
||||
key={application.id}
|
||||
application={application}
|
||||
action={
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
link={getSettingsPath(SettingsPath.ApplicationDetail, {
|
||||
applicationId: application.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTable>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Installed applications`}
|
||||
description={t`List installed applications. Use filter to search for a specific application`}
|
||||
/>
|
||||
<StyledSearchInput
|
||||
instanceId="env-var-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search an application`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<StyledTable>
|
||||
<StyledTableHeaderRow>
|
||||
<TableHeader> {t`Name`}</TableHeader>
|
||||
<TableHeader> {t`Description`}</TableHeader>
|
||||
<TableHeader> {''}</TableHeader>
|
||||
<TableHeader />
|
||||
</StyledTableHeaderRow>
|
||||
{filteredApplications.map((application) => (
|
||||
<SettingsApplicationTableRow
|
||||
key={application.id}
|
||||
application={application}
|
||||
action={
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
link={getSettingsPath(SettingsPath.ApplicationDetail, {
|
||||
applicationId: application.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTable>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
+75
-2
@@ -1,24 +1,65 @@
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { H2Title, IconTrash } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import type { Application } from '~/generated/graphql';
|
||||
import { SettingsApplicationVersionContainer } from '~/pages/settings/applications/components/SettingsApplicationVersionContainer';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useState } from 'react';
|
||||
import { useUninstallApplicationMutation } from '~/generated-metadata/graphql';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const UNINSTALL_APPLICATION_MODAL_ID = 'uninstall-application-modal';
|
||||
|
||||
export const SettingsApplicationDetailAboutTab = ({
|
||||
application,
|
||||
}: {
|
||||
application?: Omit<Application, 'objects' | 'universalIdentifier'> & {
|
||||
application?: Omit<Application, 'objects'> & {
|
||||
objects: { id: string }[];
|
||||
};
|
||||
}) => {
|
||||
const { openModal } = useModal();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
|
||||
const [uninstallApplication] = useUninstallApplicationMutation();
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
if (!isDefined(application)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { id, name, description } = application;
|
||||
|
||||
const handleUninstallApplication = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await uninstallApplication({
|
||||
variables: { universalIdentifier: application.universalIdentifier },
|
||||
});
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Application successfully uninstalled.`,
|
||||
});
|
||||
navigate(SettingsPath.Applications);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Error uninstalling application.` });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmationValue = t`yes`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
@@ -49,6 +90,38 @@ export const SettingsApplicationDetailAboutTab = ({
|
||||
/>
|
||||
<SettingsApplicationVersionContainer application={application} />
|
||||
</Section>
|
||||
{application.canBeUninstalled && (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Manage your app`}
|
||||
description={t`Uninstall this application`}
|
||||
/>
|
||||
<Button
|
||||
accent="danger"
|
||||
variant="secondary"
|
||||
title={t`Uninstall`}
|
||||
Icon={IconTrash}
|
||||
onClick={() => openModal(UNINSTALL_APPLICATION_MODAL_ID)}
|
||||
/>
|
||||
</Section>
|
||||
<ConfirmationModal
|
||||
confirmationPlaceholder={confirmationValue}
|
||||
confirmationValue={confirmationValue}
|
||||
modalId={UNINSTALL_APPLICATION_MODAL_ID}
|
||||
title={t`Uninstall Application?`}
|
||||
subtitle={
|
||||
<Trans>
|
||||
Please type {`"${confirmationValue}"`} to confirm you want to
|
||||
uninstall this application.
|
||||
</Trans>
|
||||
}
|
||||
onConfirmClick={handleUninstallApplication}
|
||||
confirmButtonText={t`Uninstall`}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-16
@@ -39,11 +39,23 @@ export const SettingsApplicationDetailContentTab = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldDisplayObjects && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Objects`}
|
||||
description={t`Objects managed by this app`}
|
||||
/>
|
||||
<SettingsObjectTable
|
||||
objectMetadataItems={applicationObjectMetadataItems}
|
||||
withSearchBar={false}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
{shouldDisplayServerlessFunctions && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Application serverless functions`}
|
||||
description={t`Serverless functions created by application`}
|
||||
title={t`Functions`}
|
||||
description={t`Serverless functions powering this app`}
|
||||
/>
|
||||
<SettingsServerlessFunctionsTable
|
||||
serverlessFunctions={serverlessFunctions}
|
||||
@@ -53,24 +65,12 @@ export const SettingsApplicationDetailContentTab = ({
|
||||
{shouldDisplayAgents && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Application agents`}
|
||||
description={t`Agents created by application`}
|
||||
title={t`Agents`}
|
||||
description={t`Agents powering this app`}
|
||||
/>
|
||||
<SettingsAIAgentsTable />
|
||||
</Section>
|
||||
)}
|
||||
{shouldDisplayObjects && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Application objects`}
|
||||
description={t`Objects created by application`}
|
||||
/>
|
||||
<SettingsObjectTable
|
||||
objectMetadataItems={applicationObjectMetadataItems}
|
||||
withSearchBar={false}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+41
-58
@@ -1,78 +1,61 @@
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import styled from '@emotion/styled';
|
||||
import { type ApplicationVariable } from '~/generated/graphql';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { H2Title, IconSearch } from 'twenty-ui/display';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
type EnvironmentVariable,
|
||||
SettingsApplicationDetailEnvironmentVariablesTableRow,
|
||||
StyledApplicationEnvironmentVariableTableRow,
|
||||
} from '~/pages/settings/applications/tabs/SettingsApplicationDetailEnvironmentVariablesTableRow';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
export const SettingsApplicationDetailEnvironmentVariablesTable = ({
|
||||
envVariables,
|
||||
onUpdate,
|
||||
readonly,
|
||||
}: {
|
||||
envVariables: EnvironmentVariable[];
|
||||
onUpdate: (newEnv: Pick<EnvironmentVariable, 'key' | 'value'>) => void;
|
||||
envVariables: ApplicationVariable[];
|
||||
onUpdate: (newEnv: Pick<ApplicationVariable, 'key' | 'value'>) => void;
|
||||
readonly?: boolean;
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const filteredEnvVariable = useMemo(() => {
|
||||
return envVariables.filter(
|
||||
({ key, value }) =>
|
||||
key.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
value.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}, [envVariables, searchTerm]);
|
||||
|
||||
const [editedEnvVariables, setEditedEnvVariables] = useState(envVariables);
|
||||
const onUpdateDebounced = useDebouncedCallback(
|
||||
(value: Pick<ApplicationVariable, 'key' | 'value'>) => {
|
||||
onUpdate(value);
|
||||
},
|
||||
250,
|
||||
);
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Configuration`}
|
||||
description={t`Set your application configuration variables`}
|
||||
/>
|
||||
<StyledSearchInput
|
||||
instanceId="env-var-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a variable`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<Table>
|
||||
<StyledApplicationEnvironmentVariableTableRow>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Value`}</TableHeader>
|
||||
<TableHeader>{t`Info`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledApplicationEnvironmentVariableTableRow>
|
||||
{filteredEnvVariable.length > 0 && (
|
||||
<StyledTableBody>
|
||||
{filteredEnvVariable.map((envVariable) => (
|
||||
<SettingsApplicationDetailEnvironmentVariablesTableRow
|
||||
key={envVariable.id}
|
||||
envVariable={envVariable}
|
||||
onChange={onUpdate}
|
||||
readonly={readonly}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
)}
|
||||
</Table>
|
||||
<StyledContainer>
|
||||
{editedEnvVariables.map((editedEnvVariable) => (
|
||||
<TextInput
|
||||
key={editedEnvVariable.key}
|
||||
label={editedEnvVariable.key}
|
||||
value={editedEnvVariable.value}
|
||||
onChange={(newValue) => {
|
||||
setEditedEnvVariables((prevState) =>
|
||||
prevState.map((val) => {
|
||||
if (val.key === editedEnvVariable.key) {
|
||||
return { ...val, value: newValue };
|
||||
}
|
||||
return val;
|
||||
}),
|
||||
);
|
||||
onUpdateDebounced({ ...editedEnvVariable, value: newValue });
|
||||
}}
|
||||
placeholder={t`Value`}
|
||||
fullWidth
|
||||
/>
|
||||
))}
|
||||
</StyledContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
AppTooltip,
|
||||
IconCheck,
|
||||
IconDotsVertical,
|
||||
IconInfoCircle,
|
||||
IconPencil,
|
||||
OverflowingTextWithTooltip,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { type ApplicationVariable } from '~/generated/graphql';
|
||||
|
||||
export const StyledApplicationEnvironmentVariableTableRow = styled(TableRow)`
|
||||
grid-template-columns: auto 200px 36px 36px;
|
||||
`;
|
||||
|
||||
export type EnvironmentVariable = ApplicationVariable;
|
||||
|
||||
export const SettingsApplicationDetailEnvironmentVariablesTableRow = ({
|
||||
envVariable,
|
||||
onChange,
|
||||
readonly,
|
||||
}: {
|
||||
envVariable: EnvironmentVariable;
|
||||
onChange: (
|
||||
newEnvVariable: Pick<EnvironmentVariable, 'key' | 'value'>,
|
||||
) => void;
|
||||
readonly?: boolean;
|
||||
}) => {
|
||||
const [editedEnvVariable, setEditedEnvVariable] = useState(envVariable);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const dropDownId = `settings-environment-variable-dropdown-${envVariable.key}`;
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
|
||||
const description =
|
||||
envVariable.description.length > 0
|
||||
? envVariable.description
|
||||
: t`No description`;
|
||||
|
||||
const InfoTableCell = (
|
||||
<TableCell>
|
||||
<IconInfoCircle
|
||||
id={`info-circle-id-description-${envVariable.key}`}
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
style={{ outline: 'none' }}
|
||||
/>
|
||||
<AppTooltip
|
||||
anchorSelect={`#info-circle-id-description-${envVariable.key}`}
|
||||
content={description}
|
||||
offset={5}
|
||||
noArrow
|
||||
place="bottom"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
return editMode && !readonly ? (
|
||||
<StyledApplicationEnvironmentVariableTableRow>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={envVariable.key} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TextInput
|
||||
value={editedEnvVariable.value}
|
||||
onChange={(newValue) =>
|
||||
setEditedEnvVariable({ ...editedEnvVariable, value: newValue })
|
||||
}
|
||||
placeholder={t`Value`}
|
||||
fullWidth
|
||||
/>
|
||||
</TableCell>
|
||||
{InfoTableCell}
|
||||
<TableCell>
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconCheck}
|
||||
onClick={() => {
|
||||
onChange(editedEnvVariable);
|
||||
setEditMode(false);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledApplicationEnvironmentVariableTableRow>
|
||||
) : (
|
||||
<StyledApplicationEnvironmentVariableTableRow
|
||||
onClick={() => setEditMode(true)}
|
||||
>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={envVariable.key} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={editedEnvVariable.value} />
|
||||
</TableCell>
|
||||
{InfoTableCell}
|
||||
<TableCell>
|
||||
{!readonly && (
|
||||
<Dropdown
|
||||
dropdownId={dropDownId}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
aria-label={t`Env Variable Options`}
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={t`Edit`}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
setEditMode(true);
|
||||
closeDropdown(dropDownId);
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
</StyledApplicationEnvironmentVariableTableRow>
|
||||
);
|
||||
};
|
||||
-77
@@ -2,19 +2,6 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import type { Application } from '~/generated/graphql';
|
||||
import { useUpdateOneApplicationVariable } from '~/pages/settings/applications/hooks/useUpdateOneApplicationVariable';
|
||||
import { SettingsApplicationDetailEnvironmentVariablesTable } from '~/pages/settings/applications/tabs/SettingsApplicationDetailEnvironmentVariablesTable';
|
||||
import { H2Title, IconTrash } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useState } from 'react';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useUninstallApplicationMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
const UNINSTALL_APPLICATION_MODAL_ID = 'uninstall-application-modal';
|
||||
|
||||
export const SettingsApplicationDetailSettingsTab = ({
|
||||
application,
|
||||
@@ -23,20 +10,8 @@ export const SettingsApplicationDetailSettingsTab = ({
|
||||
objects: { id: string }[];
|
||||
};
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { updateOneApplicationVariable } = useUpdateOneApplicationVariable();
|
||||
|
||||
const [uninstallApplication] = useUninstallApplicationMutation();
|
||||
|
||||
if (!isDefined(application)) {
|
||||
return null;
|
||||
}
|
||||
@@ -45,26 +20,6 @@ export const SettingsApplicationDetailSettingsTab = ({
|
||||
(a, b) => a.key.localeCompare(b.key),
|
||||
);
|
||||
|
||||
const handleUninstallApplication = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await uninstallApplication({
|
||||
variables: { universalIdentifier: application.universalIdentifier },
|
||||
});
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Application successfully uninstalled.`,
|
||||
});
|
||||
navigate(SettingsPath.Applications);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Error uninstalling application.` });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmationValue = t`yes`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsApplicationDetailEnvironmentVariablesTable
|
||||
@@ -77,38 +32,6 @@ export const SettingsApplicationDetailSettingsTab = ({
|
||||
})
|
||||
}
|
||||
/>
|
||||
{application.canBeUninstalled && (
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Danger zone`}
|
||||
description={t`Uninstall this application`}
|
||||
/>
|
||||
<Button
|
||||
accent="danger"
|
||||
variant="secondary"
|
||||
title={t`Uninstall`}
|
||||
Icon={IconTrash}
|
||||
onClick={() => openModal(UNINSTALL_APPLICATION_MODAL_ID)}
|
||||
/>
|
||||
</Section>
|
||||
<ConfirmationModal
|
||||
confirmationPlaceholder={confirmationValue}
|
||||
confirmationValue={confirmationValue}
|
||||
modalId={UNINSTALL_APPLICATION_MODAL_ID}
|
||||
title={t`Uninstall Application?`}
|
||||
subtitle={
|
||||
<Trans>
|
||||
Please type {`"${confirmationValue}"`} to confirm you want to
|
||||
uninstall this application.
|
||||
</Trans>
|
||||
}
|
||||
onConfirmClick={handleUninstallApplication}
|
||||
confirmButtonText={t`Uninstall`}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,6 +59,7 @@ export const SettingsObjectDetailPage = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { objectNamePlural = '' } = useParams();
|
||||
|
||||
const { findObjectMetadataItemByNamePlural } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
@@ -167,7 +168,10 @@ export const SettingsObjectDetailPage = () => {
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: t`Objects`, href: getSettingsPath(SettingsPath.Objects) },
|
||||
{
|
||||
children: t`Objects`,
|
||||
href: getSettingsPath(SettingsPath.Objects),
|
||||
},
|
||||
{
|
||||
children: objectMetadataItem.labelPlural,
|
||||
},
|
||||
|
||||
@@ -77,6 +77,7 @@ export const SettingsObjectFieldEdit = () => {
|
||||
] = useRecoilState(shouldNavigateBackToMemorizedUrlOnSaveState);
|
||||
|
||||
const { objectNamePlural = '', fieldName = '' } = useParams();
|
||||
|
||||
const { findObjectMetadataItemByNamePlural } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
|
||||
+8
-3
@@ -171,9 +171,14 @@ export const SettingsServerlessFunctionDetail = () => {
|
||||
},
|
||||
{
|
||||
children: `${applicationName}`,
|
||||
href: getSettingsPath(SettingsPath.ApplicationDetail, {
|
||||
applicationId,
|
||||
}),
|
||||
href: getSettingsPath(
|
||||
SettingsPath.ApplicationDetail,
|
||||
{
|
||||
applicationId,
|
||||
},
|
||||
undefined,
|
||||
'content',
|
||||
),
|
||||
},
|
||||
{ children: `${serverlessFunction?.name}` },
|
||||
]}
|
||||
|
||||
@@ -33,7 +33,7 @@ export enum SettingsPath {
|
||||
AIAgentTurnDetail = 'ai/agents/:agentId/turns/:turnId',
|
||||
Applications = 'applications',
|
||||
ApplicationDetail = 'applications/:applicationId',
|
||||
ApplicationServerlessFunctionDetail = 'applications/:applicationId/:serverlessFunctionId',
|
||||
ApplicationServerlessFunctionDetail = 'applications/:applicationId/serverlessFunctions/:serverlessFunctionId',
|
||||
ServerlessFunctions = 'functions',
|
||||
NewServerlessFunction = 'functions/new',
|
||||
ServerlessFunctionDetail = 'functions/:serverlessFunctionId',
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import styled from '@emotion/styled';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledCommandContain = styled.div`
|
||||
font-family: ${({ theme }) => theme.code.font.family};
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledLineContainer = styled.div``;
|
||||
|
||||
const StyledLineStartSpan = styled.span`
|
||||
color: ${({ theme }) => theme.code.text.orange};
|
||||
`;
|
||||
|
||||
const StyledLineSpan = styled.span`
|
||||
color: ${({ theme }) => theme.code.text.green};
|
||||
`;
|
||||
|
||||
type CommandBlockProps = {
|
||||
commands: string[];
|
||||
button?: ReactElement;
|
||||
};
|
||||
|
||||
export const CommandBlock = ({ commands, button }: CommandBlockProps) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledCommandContain>
|
||||
<>
|
||||
{commands.map((line, i) => (
|
||||
<StyledLineContainer key={i}>
|
||||
<StyledLineStartSpan>{'> '}</StyledLineStartSpan>
|
||||
<StyledLineSpan>{line}</StyledLineSpan>
|
||||
</StyledLineContainer>
|
||||
))}
|
||||
</>
|
||||
</StyledCommandContain>
|
||||
{button && <StyledButtonContainer>{button}</StyledButtonContainer>}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@@ -163,6 +163,7 @@ export {
|
||||
IconFileCheck,
|
||||
IconFileExport,
|
||||
IconFileImport,
|
||||
IconFileInfo,
|
||||
IconFilePencil,
|
||||
IconFileText,
|
||||
IconFileUpload,
|
||||
|
||||
@@ -28,6 +28,7 @@ export type {
|
||||
ColorSampleProps,
|
||||
} from './color/components/ColorSample';
|
||||
export { ColorSample } from './color/components/ColorSample';
|
||||
export { CommandBlock } from './command-block/components/CommandBlock';
|
||||
export { IconAddressBook } from './icon/components/IconAddressBook';
|
||||
export { IconChartBarHorizontal } from './icon/components/IconChartBarHorizontal';
|
||||
export { IconGmail } from './icon/components/IconGmail';
|
||||
@@ -228,6 +229,7 @@ export {
|
||||
IconFileCheck,
|
||||
IconFileExport,
|
||||
IconFileImport,
|
||||
IconFileInfo,
|
||||
IconFilePencil,
|
||||
IconFileText,
|
||||
IconFileUpload,
|
||||
|
||||
@@ -6,5 +6,7 @@ export const CODE_DARK = {
|
||||
sky: COLOR_DARK.sky10,
|
||||
pink: COLOR_DARK.pink10,
|
||||
orange: COLOR_DARK.orange8,
|
||||
green: COLOR_DARK.lime8,
|
||||
},
|
||||
font: { family: 'DM Mono' },
|
||||
};
|
||||
|
||||
@@ -6,5 +6,7 @@ export const CODE_LIGHT = {
|
||||
sky: COLOR_LIGHT.sky10,
|
||||
pink: COLOR_LIGHT.pink10,
|
||||
orange: COLOR_LIGHT.orange8,
|
||||
green: COLOR_LIGHT.lime8,
|
||||
},
|
||||
font: { family: 'DM Mono' },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user