[Apps] Apps marketplace (first draft) (#17562)
https://github.com/user-attachments/assets/c4e63edb-6e98-44bc-841f-ee110ae712d4 How it works - `manifest.json` files are now committed when apps are published to our repo - to display available apps, from the server, we read into our github repo, using a pod-scoped cache - feature flagged - app installation will be behind permission gate MARKETPLACE_APPS Limitations and what is yet to develop - content and settings tabs - installed apps tab - app installation - test and potentially fix reading from .manifest.json once [Reshape manifest structure](https://github.com/twentyhq/core-team-issues/issues/2183) is done. additional work is expected on assets notably. (couldnt properly do it here as manifest.json will only be committed after this pr) - we only read in community/ folder for now - we may want tochange that - the cache is rather artisanal for now and scoped by pod - we may want to change that
This commit is contained in:
@@ -7,8 +7,10 @@ import { type FileUIPart } from 'ai';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { buildSignedPath, isDefined } from 'twenty-shared/utils';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useUploadFileMutation } from '~/generated-metadata/graphql';
|
||||
import { FileFolder } from '~/generated/graphql';
|
||||
import {
|
||||
FileFolder,
|
||||
useUploadFileMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useAIChatFileUpload = () => {
|
||||
const coreClient = useApolloCoreClient();
|
||||
|
||||
@@ -159,6 +159,14 @@ const SettingsApplicationDetails = lazy(() =>
|
||||
),
|
||||
);
|
||||
|
||||
const SettingsAvailableApplicationDetails = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/applications/SettingsAvailableApplicationDetails'
|
||||
).then((module) => ({
|
||||
default: module.SettingsAvailableApplicationDetails,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAgentForm = lazy(() =>
|
||||
import('~/pages/settings/ai/SettingsAgentForm').then((module) => ({
|
||||
default: module.SettingsAgentForm,
|
||||
@@ -581,6 +589,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
|
||||
path={SettingsPath.ApplicationDetail}
|
||||
element={<SettingsApplicationDetails />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.AvailableApplicationDetail}
|
||||
element={<SettingsAvailableApplicationDetails />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.ApplicationLogicFunctionDetail}
|
||||
element={<SettingsLogicFunctionDetail />}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const INSTALL_MARKETPLACE_APP = gql`
|
||||
mutation InstallMarketplaceApp() {
|
||||
installMarketplaceApp()
|
||||
}
|
||||
`;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const FIND_MANY_MARKETPLACE_APPS = gql`
|
||||
query FindManyMarketplaceApps {
|
||||
findManyMarketplaceApps {
|
||||
id
|
||||
name
|
||||
description
|
||||
icon
|
||||
version
|
||||
author
|
||||
category
|
||||
logo
|
||||
screenshots
|
||||
aboutDescription
|
||||
providers
|
||||
websiteUrl
|
||||
termsUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,9 @@
|
||||
export const useInstallMarketplaceApp = () => {
|
||||
const install = async () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
return {
|
||||
install,
|
||||
};
|
||||
};
|
||||
+17
-6
@@ -2,16 +2,19 @@ import styled from '@emotion/styled';
|
||||
|
||||
type StyledCardContentProps = {
|
||||
disabled?: boolean;
|
||||
alignItems?: 'center' | 'flex-start';
|
||||
fullHeight?: boolean;
|
||||
};
|
||||
|
||||
export const StyledSettingsOptionCardContent = styled.div<StyledCardContentProps>`
|
||||
align-items: center;
|
||||
export const StyledSettingsCardContent = styled.div<StyledCardContentProps>`
|
||||
align-items: ${({ alignItems }) => alignItems ?? 'center'};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
height: ${({ fullHeight }) => (fullHeight ? '100%' : 'auto')};
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
export const StyledSettingsOptionCardIcon = styled.div`
|
||||
export const StyledSettingsCardIcon = styled.div`
|
||||
align-items: center;
|
||||
border: 2px solid ${({ theme }) => theme.border.color.light};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
@@ -23,15 +26,17 @@ export const StyledSettingsOptionCardIcon = styled.div`
|
||||
min-width: ${({ theme }) => theme.icon.size.md};
|
||||
`;
|
||||
|
||||
export const StyledSettingsOptionCardTitle = styled.div`
|
||||
export const StyledSettingsCardTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const StyledSettingsOptionCardDescription = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
export const StyledSettingsCardDescription = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
|
||||
a {
|
||||
position: relative;
|
||||
@@ -39,3 +44,9 @@ export const StyledSettingsOptionCardDescription = styled.div`
|
||||
pointer-events: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledSettingsCardThirdLine = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
+12
-12
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
StyledSettingsOptionCardContent,
|
||||
StyledSettingsOptionCardDescription,
|
||||
StyledSettingsOptionCardIcon,
|
||||
StyledSettingsOptionCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
@@ -28,19 +28,19 @@ export const SettingsOptionCardContentButton = ({
|
||||
Button,
|
||||
}: SettingsOptionCardContentButtonProps) => {
|
||||
return (
|
||||
<StyledSettingsOptionCardContent disabled={disabled}>
|
||||
<StyledSettingsCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsOptionCardIcon>
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsOptionCardIcon>
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsOptionCardDescription>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsOptionCardDescription>
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
{Button && <StyledButtonContainer>{Button}</StyledButtonContainer>}
|
||||
</StyledSettingsOptionCardContent>
|
||||
</StyledSettingsCardContent>
|
||||
);
|
||||
};
|
||||
|
||||
+12
-12
@@ -1,10 +1,10 @@
|
||||
import { SettingsCounter } from '@/settings/components/SettingsCounter';
|
||||
import {
|
||||
StyledSettingsOptionCardContent,
|
||||
StyledSettingsOptionCardDescription,
|
||||
StyledSettingsOptionCardIcon,
|
||||
StyledSettingsOptionCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
|
||||
@@ -32,18 +32,18 @@ export const SettingsOptionCardContentCounter = ({
|
||||
showButtons = true,
|
||||
}: SettingsOptionCardContentCounterProps) => {
|
||||
return (
|
||||
<StyledSettingsOptionCardContent disabled={disabled}>
|
||||
<StyledSettingsCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsOptionCardIcon>
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsOptionCardIcon>
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
{description && (
|
||||
<StyledSettingsOptionCardDescription>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsOptionCardDescription>
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</div>
|
||||
<SettingsCounter
|
||||
@@ -54,6 +54,6 @@ export const SettingsOptionCardContentCounter = ({
|
||||
disabled={disabled}
|
||||
showButtons={showButtons}
|
||||
/>
|
||||
</StyledSettingsOptionCardContent>
|
||||
</StyledSettingsCardContent>
|
||||
);
|
||||
};
|
||||
|
||||
+12
-12
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
StyledSettingsOptionCardContent,
|
||||
StyledSettingsOptionCardDescription,
|
||||
StyledSettingsOptionCardIcon,
|
||||
StyledSettingsOptionCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import styled from '@emotion/styled';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
@@ -30,19 +30,19 @@ export const SettingsOptionCardContentSelect = ({
|
||||
children,
|
||||
}: SettingsOptionCardContentSelectProps) => {
|
||||
return (
|
||||
<StyledSettingsOptionCardContent disabled={disabled}>
|
||||
<StyledSettingsCardContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsOptionCardIcon>
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsOptionCardIcon>
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsOptionCardTitle>{title}</StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsOptionCardDescription>
|
||||
<StyledSettingsCardTitle>{title}</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsOptionCardDescription>
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
<StyledSelectContainer>{children}</StyledSelectContainer>
|
||||
</StyledSettingsOptionCardContent>
|
||||
</StyledSettingsCardContent>
|
||||
);
|
||||
};
|
||||
|
||||
+18
-20
@@ -1,10 +1,10 @@
|
||||
import { Separator } from '@/settings/components/Separator';
|
||||
import {
|
||||
StyledSettingsOptionCardContent,
|
||||
StyledSettingsOptionCardDescription,
|
||||
StyledSettingsOptionCardIcon,
|
||||
StyledSettingsOptionCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTitle,
|
||||
} from '@/settings/components/SettingsOptions/SettingsCardContentBase';
|
||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -12,9 +12,7 @@ import { useId } from 'react';
|
||||
import { type IconComponent } from 'twenty-ui/display';
|
||||
import { Toggle } from 'twenty-ui/input';
|
||||
|
||||
const StyledSettingsOptionCardToggleContent = styled(
|
||||
StyledSettingsOptionCardContent,
|
||||
)`
|
||||
const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
|
||||
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
|
||||
position: relative;
|
||||
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
|
||||
@@ -24,7 +22,7 @@ const StyledSettingsOptionCardToggleContent = styled(
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSettingsOptionCardToggleButton = styled(Toggle)<{
|
||||
const StyledSettingsCardToggleButton = styled(Toggle)<{
|
||||
toggleCentered?: boolean;
|
||||
}>`
|
||||
align-self: ${({ toggleCentered }) =>
|
||||
@@ -32,7 +30,7 @@ const StyledSettingsOptionCardToggleButton = styled(Toggle)<{
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const StyledSettingsOptionCardToggleCover = styled.span`
|
||||
const StyledSettingsCardToggleCover = styled.span`
|
||||
cursor: pointer;
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
@@ -66,24 +64,24 @@ export const SettingsOptionCardContentToggle = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSettingsOptionCardToggleContent disabled={disabled}>
|
||||
<StyledSettingsCardToggleContent disabled={disabled}>
|
||||
{Icon && (
|
||||
<StyledSettingsOptionCardIcon>
|
||||
<StyledSettingsCardIcon>
|
||||
<SettingsOptionIconCustomizer Icon={Icon} />
|
||||
</StyledSettingsOptionCardIcon>
|
||||
</StyledSettingsCardIcon>
|
||||
)}
|
||||
<div>
|
||||
<StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsCardTitle>
|
||||
<label htmlFor={toggleId}>
|
||||
{title}
|
||||
<StyledSettingsOptionCardToggleCover />
|
||||
<StyledSettingsCardToggleCover />
|
||||
</label>
|
||||
</StyledSettingsOptionCardTitle>
|
||||
<StyledSettingsOptionCardDescription>
|
||||
</StyledSettingsCardTitle>
|
||||
<StyledSettingsCardDescription>
|
||||
{description}
|
||||
</StyledSettingsOptionCardDescription>
|
||||
</StyledSettingsCardDescription>
|
||||
</div>
|
||||
<StyledSettingsOptionCardToggleButton
|
||||
<StyledSettingsCardToggleButton
|
||||
id={toggleId}
|
||||
value={checked}
|
||||
onChange={onChange}
|
||||
@@ -92,7 +90,7 @@ export const SettingsOptionCardContentToggle = ({
|
||||
color={advancedMode ? theme.color.yellow : theme.color.blue}
|
||||
toggleCentered={toggleCentered}
|
||||
/>
|
||||
</StyledSettingsOptionCardToggleContent>
|
||||
</StyledSettingsCardToggleContent>
|
||||
{divider && <Separator />}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user