[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:
Marie
2026-01-30 18:32:32 +01:00
committed by GitHub
parent 36cf388c81
commit 0001c2e7d0
98 changed files with 1713 additions and 280 deletions
@@ -9,7 +9,6 @@ import {
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRow';
import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/components/SettingsObjectInactiveMenuDropDown';
import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -21,18 +20,12 @@ import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/st
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
import { type ReactNode, useMemo, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import {
IconArchive,
IconChevronRight,
IconFilter,
IconSearch,
IconSettings,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { IconArchive, IconChevronRight, IconSettings } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
import type { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
@@ -42,18 +35,10 @@ const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledSearchAndFilterContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
align-items: center;
const StyledSearchInputContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledSearchInput = styled(SettingsTextInput)`
flex: 1;
width: 100%;
`;
export const SettingsObjectTable = ({
objectMetadataItems,
withSearchBar = true,
@@ -146,53 +131,47 @@ export const SettingsObjectTable = ({
return (
<>
{withSearchBar && (
<StyledSearchAndFilterContainer>
<StyledSearchInput
instanceId="settings-objects-search"
LeftIcon={IconSearch}
<StyledSearchInputContainer>
<SearchInput
placeholder={t`Search for an object...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<Dropdown
dropdownId="settings-objects-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={
<Button
Icon={IconFilter}
size="medium"
variant="secondary"
accent="default"
ariaLabel={t`Filter`}
filterDropdown={(filterButton: ReactNode) => (
<Dropdown
dropdownId="settings-objects-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconArchive}
onToggleChange={() =>
setShowDeactivated(!showDeactivated)
}
toggled={showDeactivated}
text={t`Deactivated`}
toggleSize="small"
/>
{isAdvancedModeEnabled && (
<MenuItemToggle
LeftIcon={IconSettings}
onToggleChange={() =>
setShowSystemObjects(!showSystemObjects)
}
toggled={showSystemObjects}
text={t`System objects`}
toggleSize="small"
/>
)}
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconArchive}
onToggleChange={() => setShowDeactivated(!showDeactivated)}
toggled={showDeactivated}
text={t`Deactivated`}
toggleSize="small"
/>
{isAdvancedModeEnabled && (
<MenuItemToggle
LeftIcon={IconSettings}
onToggleChange={() =>
setShowSystemObjects(!showSystemObjects)
}
toggled={showSystemObjects}
text={t`System objects`}
toggleSize="small"
/>
)}
</DropdownMenuItemsContainer>
</DropdownContent>
}
)}
/>
</StyledSearchAndFilterContainer>
</StyledSearchInputContainer>
)}
<Table>