Replace sign-in mocked metadata with hardcoded BackgroundMock (#20308)

## Summary

When the user is logged out, we render the auth modal on top of a sample
table to make the empty page feel alive. So far this was achieved by
**loading a full set of mocked object / field / view / navigation-menu
metadata into the runtime metadata store** and then mounting the real
`RecordTable` and `AppNavigationDrawer` behind the modal. This had a few
downsides:

- Significant bundle weight pulled in for unauthenticated users (mocked
GraphQL fixtures + the real `RecordTable` virtualization stack).
- Plenty of code paths that had to know about the "showAuthModal" case
(`useRecordIndexTableQuery`, `useTriggerInitialRecordTableDataLoad`,
`MainContextStoreProvider`, `IsMinimalMetadataReadyEffect`...).
- Any change to metadata-store internals or to the record-table runtime
risked breaking the logged-out background.

This PR replaces the entire flow with a small, self-contained
`BackgroundMock` component tree that **does not consume any metadata**
and **does not load any mocked metadata at runtime**.

### What changed

- New module under `sign-in-background-mock`:
- `BackgroundMockPage` + `BackgroundMockViewBar` + `BackgroundMockTable`
+ `BackgroundMockTableRow` render a hardcoded "Companies" table that
visually mirrors the real one.
- `BackgroundMockNavigationDrawer` renders a hardcoded sidebar with
People / Companies / Opportunities / Tasks / Notes (with their standard
colors).
- Hardcoded constants in `BackgroundMockCompanies.ts`,
`BackgroundMockColumns.ts`, `BackgroundMockNavigationItems.ts`.
- `MinimalMetadataLoadEffect` no longer calls `loadMockedMetadataAtomic`
for unauthenticated users — it just doesn't load anything.
- `IsMinimalMetadataReadyEffect` now reports ready immediately when
there is no access token pair, so the skeleton loader doesn't hang
waiting for metadata that will never come.
- `MainContextStoreProvider`, `useRecordIndexTableQuery`, and
`useTriggerInitialRecordTableDataLoad` drop their `showAuthModal`
branches — the real `RecordTable` is no longer mounted behind the modal.
- `DefaultLayout` and `NotFound` now lazily load `BackgroundMockPage` /
`BackgroundMockNavigationDrawer` instead of the deleted
`SignInBackgroundMockPage` / `SignInAppNavigationDrawerMock`.
- Removed: `SignInBackgroundMockPage`, `SignInBackgroundMockContainer`,
`SignInBackgroundMockContainerEffect`, `SignInAppNavigationDrawerMock`,
`SignInBackgroundMockColumnDefinitions`,
`SignInBackgroundMockCompanies`, `SignInBackgroundMockViewFields`.

`useLoadMockedMetadata` and `preloadMockedMetadata` are kept on purpose:
Storybook decorators (`ObjectMetadataItemsDecorator`,
`WorkflowStepDecorator`) still rely on the mocked metadata fixtures, but
**production** unauthenticated runtime no longer touches them.

### Visual parity

Side-by-side at 1440×900 on `/sign-in`:

**Before** (loads mocked metadata + real RecordTable):

![before](https://github.com/user-attachments/assets/before-placeholder)

**After** (purely hardcoded BackgroundMock):

![after](https://github.com/user-attachments/assets/after-placeholder)

## Test plan

- [ ] `npx nx typecheck twenty-front`  (passes locally)
- [ ] `npx nx lint:diff-with-main twenty-front`  (oxlint + prettier
clean)
- [ ] `npx jest useRecordIndexTableQuery` 
- [ ] Manually verify `/sign-in` renders the table + nav drawer behind
the modal
- [ ] Manually verify `/not-found` still renders the background
- [ ] Verify CI: storybook, unit tests, e2e tests
This commit is contained in:
Charles Bochet
2026-05-06 11:49:24 +02:00
committed by GitHub
parent 26874c3603
commit ee6c0ef904
26 changed files with 975 additions and 2132 deletions
@@ -3,7 +3,6 @@ import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
@@ -38,8 +37,6 @@ const getViewId = (
return undefined;
};
const SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL = 'companies';
export const MainContextStoreProvider = () => {
const location = useLocation();
const isRecordIndexPage = isMatchingLocation(
@@ -49,15 +46,10 @@ export const MainContextStoreProvider = () => {
const isRecordShowPage = isMatchingLocation(location, AppPath.RecordShowPage);
const isStandalonePage = isMatchingLocation(location, AppPath.PageLayoutPage);
const isSettingsPage = useIsSettingsPage();
const showAuthModal = useShowAuthModal();
const objectNamePluralFromParams = useParams().objectNamePlural ?? '';
const objectNamePlural = useParams().objectNamePlural ?? '';
const objectNameSingular = useParams().objectNameSingular ?? '';
const objectNamePlural = showAuthModal
? SIGN_IN_BACKGROUND_OBJECT_NAME_PLURAL
: objectNamePluralFromParams;
const [searchParams] = useSearchParams();
const viewIdQueryParamRaw = searchParams.get('viewId');
@@ -120,8 +112,7 @@ export const MainContextStoreProvider = () => {
(isRecordIndexPage ||
isRecordShowPage ||
isStandalonePage ||
isSettingsPage ||
showAuthModal) &&
isSettingsPage) &&
metadataStore.status === 'up-to-date';
if (!shouldComputeContextStore) {
@@ -28,21 +28,24 @@ export const IsMinimalMetadataReadyEffect = () => {
);
useEffect(() => {
if (!hasAccessTokenPair) {
setIsMinimalMetadataReady(true);
return;
}
const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
const areObjectsLoaded = metadataStore.status === 'up-to-date';
const areViewsLoaded = metadataStoreViews.status === 'up-to-date';
const isReady = !areObjectsLoaded
? false
: !hasAccessTokenPair ||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
if (!areObjectsLoaded) {
setIsMinimalMetadataReady(false);
return;
}
const isReady =
isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded);
if (isReady) {
setIsMinimalMetadataReady(true);
}
@@ -2,70 +2,41 @@ import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { useLoadMinimalMetadata } from '@/metadata-store/hooks/useLoadMinimalMetadata';
import { useLoadMockedMetadata } from '@/metadata-store/hooks/useLoadMockedMetadata';
import { useLoadStaleMetadataEntities } from '@/metadata-store/hooks/useLoadStaleMetadataEntities';
import { metadataLoadedVersionState } from '@/metadata-store/states/metadataLoadedVersionState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useEffect, useState } from 'react';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
type LoadedState = 'none' | 'mocked' | 'real';
const computeDesiredLoadState = (
hasAccessTokenPair: boolean,
isActiveWorkspace: boolean,
): LoadedState => {
if (hasAccessTokenPair && isActiveWorkspace) {
return 'real';
}
return 'mocked';
};
export const MinimalMetadataLoadEffect = () => {
const hasAccessTokenPair = useHasAccessTokenPair();
const isCurrentUserLoaded = useAtomStateValue(isCurrentUserLoadedState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const metadataLoadedVersion = useAtomStateValue(metadataLoadedVersionState);
const [lastMetadataLoadData, setLastMetadataLoadData] = useState<{
state: LoadedState;
version: number;
}>({ state: 'none', version: -1 });
const [lastLoadedVersion, setLastLoadedVersion] = useState<number>(-1);
const { loadMinimalMetadata } = useLoadMinimalMetadata();
const { loadMockedMetadataAtomic } = useLoadMockedMetadata();
const { loadStaleMetadataEntities } = useLoadStaleMetadataEntities();
const isActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
const desiredLoadState = computeDesiredLoadState(
hasAccessTokenPair,
isActiveWorkspace,
);
const shouldLoadRealMetadata = hasAccessTokenPair && isActiveWorkspace;
useEffect(() => {
if (!isCurrentUserLoaded) {
return;
}
const versionChanged =
metadataLoadedVersion !== lastMetadataLoadData.version;
if (!versionChanged && lastMetadataLoadData.state === desiredLoadState) {
if (!shouldLoadRealMetadata) {
return;
}
setLastMetadataLoadData({
state: desiredLoadState,
version: metadataLoadedVersion,
});
if (metadataLoadedVersion === lastLoadedVersion) {
return;
}
setLastLoadedVersion(metadataLoadedVersion);
const performLoad = async () => {
if (desiredLoadState === 'mocked') {
await loadMockedMetadataAtomic();
return;
}
const result = await loadMinimalMetadata();
if (result?.staleEntityKeys && result.staleEntityKeys.length > 0) {
@@ -76,13 +47,10 @@ export const MinimalMetadataLoadEffect = () => {
performLoad();
}, [
isCurrentUserLoaded,
hasAccessTokenPair,
isActiveWorkspace,
desiredLoadState,
lastMetadataLoadData,
shouldLoadRealMetadata,
lastLoadedVersion,
metadataLoadedVersion,
loadMinimalMetadata,
loadMockedMetadataAtomic,
loadStaleMetadataEntities,
]);
@@ -2,12 +2,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useRelevantRecordsGqlFields } from '@/object-record/record-field/hooks/useRelevantRecordsGqlFields';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
export const useRecordIndexTableQuery = (objectNameSingular: string) => {
const showAuthModal = useShowAuthModal();
const params = useFindManyRecordIndexTableParams(objectNameSingular);
const { objectMetadataItem } = useObjectMetadataItem({
@@ -28,12 +24,11 @@ export const useRecordIndexTableQuery = (objectNameSingular: string) => {
} = useFindManyRecords({
...params,
recordGqlFields,
skip: showAuthModal,
});
return {
records: showAuthModal ? SIGN_IN_BACKGROUND_MOCK_COMPANIES : records,
loading: showAuthModal ? false : loading,
records,
loading,
hasNextPage,
queryIdentifier,
totalCount,
@@ -30,8 +30,6 @@ import { recordIdByRealIndexComponentState } from '@/object-record/record-table/
import { scrollAtRealIndexComponentState } from '@/object-record/record-table/virtualization/states/scrollAtRealIndexComponentState';
import { totalNumberOfRecordsToVirtualizeComponentState } from '@/object-record/record-table/virtualization/states/totalNumberOfRecordsToVirtualizeComponentState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
@@ -41,8 +39,6 @@ import { isDefined } from 'twenty-shared/utils';
export const useTriggerInitialRecordTableDataLoad = () => {
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
const showAuthModal = useShowAuthModal();
const { findManyRecordsLazy } =
useRecordIndexTableLazyQuery(objectNameSingular);
@@ -155,40 +151,35 @@ export const useTriggerInitialRecordTableDataLoad = () => {
let records: ObjectRecord[] | null = null;
let totalCount = 0;
if (showAuthModal) {
records = SIGN_IN_BACKGROUND_MOCK_COMPANIES;
totalCount = SIGN_IN_BACKGROUND_MOCK_COMPANIES.length;
} else {
const newRecordIdByRealIndex = new Map(
store.get(recordIdByRealIndexCallbackState),
);
const newDataLoadingStatusByRealIndex = new Map(
store.get(dataLoadingStatusByRealIndexCallbackState),
);
const newRecordIdByRealIndex = new Map(
store.get(recordIdByRealIndexCallbackState),
);
const newDataLoadingStatusByRealIndex = new Map(
store.get(dataLoadingStatusByRealIndexCallbackState),
);
for (const [realIndex] of currentRecordIds.entries()) {
newDataLoadingStatusByRealIndex.set(realIndex, 'not-loaded');
newRecordIdByRealIndex.delete(realIndex);
}
store.set(recordIdByRealIndexCallbackState, newRecordIdByRealIndex);
store.set(
dataLoadingStatusByRealIndexCallbackState,
newDataLoadingStatusByRealIndex,
);
store.set(
recordIndexRecordIdsByGroupFamilyState(NO_RECORD_GROUP_FAMILY_KEY),
[],
);
const { records: findManyRecords, totalCount: findManyTotalCount } =
await findManyRecordsLazy();
records = findManyRecords;
totalCount = findManyTotalCount;
for (const [realIndex] of currentRecordIds.entries()) {
newDataLoadingStatusByRealIndex.set(realIndex, 'not-loaded');
newRecordIdByRealIndex.delete(realIndex);
}
store.set(recordIdByRealIndexCallbackState, newRecordIdByRealIndex);
store.set(
dataLoadingStatusByRealIndexCallbackState,
newDataLoadingStatusByRealIndex,
);
store.set(
recordIndexRecordIdsByGroupFamilyState(NO_RECORD_GROUP_FAMILY_KEY),
[],
);
const { records: findManyRecords, totalCount: findManyTotalCount } =
await findManyRecordsLazy();
records = findManyRecords;
totalCount = findManyTotalCount;
store.set(totalNumberOfRecordsToVirtualizeCallbackState, totalCount);
if (isDefined(records)) {
@@ -230,7 +221,6 @@ export const useTriggerInitialRecordTableDataLoad = () => {
recordIndexAllRecordIds,
recordIndexRecordIdsByGroupFamilyState,
store,
showAuthModal,
dataPagesLoadedCallbackState,
isRecordTableInitialLoading,
lastScrollPositionCallbackState,
@@ -1,11 +1,3 @@
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -13,28 +5,27 @@ import { getSettingsPath } from 'twenty-shared/utils';
import { IconSearch, IconSettings } from 'twenty-ui/display';
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
import { BACKGROUND_MOCK_OTHER_ITEMS } from '@/sign-in-background-mock/constants/BackgroundMockOtherItems';
import { BACKGROUND_MOCK_WORKSPACE_ITEMS } from '@/sign-in-background-mock/constants/BackgroundMockNavigationItems';
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
const StyledMainSectionWrapper = styled.div`
min-height: fit-content;
`;
const WORKSPACE_FAVORITES = [
'person',
'company',
'opportunity',
'task',
'note',
];
export type SignInAppNavigationDrawerMockProps = {
export type BackgroundMockNavigationDrawerProps = {
className?: string;
};
export const SignInAppNavigationDrawerMock = ({
export const BackgroundMockNavigationDrawer = ({
className,
}: SignInAppNavigationDrawerMockProps) => {
}: BackgroundMockNavigationDrawerProps) => {
const isMobile = useIsMobile();
const { t } = useLingui();
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
return (
<NavigationDrawer className={className} title={DEFAULT_WORKSPACE_NAME}>
@@ -56,12 +47,30 @@ export const SignInAppNavigationDrawerMock = ({
</NavigationDrawerSection>
</StyledMainSectionWrapper>
)}
<NavigationDrawerSectionForObjectMetadataItems
sectionTitle={t`Workspace`}
objectMetadataItems={objectMetadataItems.filter((item) =>
WORKSPACE_FAVORITES.includes(item.nameSingular),
)}
/>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label={t`Workspace`} />
{BACKGROUND_MOCK_WORKSPACE_ITEMS.map((item, index) => (
<NavigationDrawerItem
key={item.label}
label={item.label}
Icon={item.Icon}
iconColor={item.color}
active={index === 0}
onClick={() => {}}
/>
))}
</NavigationDrawerSection>
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label={t`Other`} />
{BACKGROUND_MOCK_OTHER_ITEMS.map((item) => (
<NavigationDrawerItem
key={item.label}
label={item.label}
Icon={item.Icon}
onClick={() => {}}
/>
))}
</NavigationDrawerSection>
</NavigationDrawer>
);
};
@@ -0,0 +1,63 @@
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import {
IconBuildingSkyscraper,
IconDotsVertical,
IconLayoutSidebarRight,
IconPlus,
TintedIconTile,
} from 'twenty-ui/display';
import { Button, LightIconButton } from 'twenty-ui/input';
import { BackgroundMockTable } from '@/sign-in-background-mock/components/BackgroundMockTable';
import { BackgroundMockViewBar } from '@/sign-in-background-mock/components/BackgroundMockViewBar';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
const StyledTableContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow: hidden;
width: 100%;
`;
export const BackgroundMockPage = () => {
return (
<PageContainer>
<PageHeader
title={t`Companies`}
Icon={() => (
<TintedIconTile Icon={IconBuildingSkyscraper} color="blue" />
)}
>
<Button
Icon={IconPlus}
title={t`New Company`}
variant="primary"
accent="default"
size="small"
/>
<LightIconButton
Icon={IconDotsVertical}
accent="tertiary"
size="small"
/>
<Button
Icon={IconLayoutSidebarRight}
variant="secondary"
accent="default"
size="small"
/>
</PageHeader>
<PageBody>
<StyledTableContainer>
<BackgroundMockViewBar />
<BackgroundMockTable />
</StyledTableContainer>
</PageBody>
</PageContainer>
);
};
@@ -0,0 +1,232 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { BackgroundMockTableRow } from '@/sign-in-background-mock/components/BackgroundMockTableRow';
import { BACKGROUND_MOCK_COLUMNS } from '@/sign-in-background-mock/constants/BackgroundMockColumns';
import { BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/BackgroundMockCompanies';
import { BACKGROUND_MOCK_TABLE_DIMENSIONS } from '@/sign-in-background-mock/constants/BackgroundMockTableDimensions';
import {
IconChevronDown,
IconPlus,
OverflowingTextWithTooltip,
useIcons,
} from 'twenty-ui/display';
import { Checkbox } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledTableWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
width: 100%;
`;
const StyledTable = styled.div`
background: ${themeCssVariables.background.primary};
display: flex;
flex: 1;
flex-direction: column;
font-size: ${themeCssVariables.font.size.md};
overflow: hidden;
position: relative;
`;
const StyledHeaderRow = styled.div`
background: ${themeCssVariables.background.primary};
display: flex;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
`;
const StyledDragHandleColumn = styled.div`
background: ${themeCssVariables.background.primary};
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.dragHandleColumnWidth}px;
`;
const StyledCheckboxHeaderColumn = styled.div`
align-items: center;
background: ${themeCssVariables.background.primary};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
box-sizing: border-box;
display: flex;
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.checkboxColumnWidth}px;
`;
const StyledHeaderCell = styled.div<{ width: number }>`
align-items: center;
background: ${themeCssVariables.background.primary};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
border-right: 1px solid ${themeCssVariables.border.color.light};
box-sizing: border-box;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
flex-shrink: 0;
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
overflow: hidden;
padding: 0 ${themeCssVariables.spacing[2]};
width: ${({ width }) => width}px;
`;
const StyledHeaderLabel = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledAddColumnHeaderCell = styled.div`
align-items: center;
background: ${themeCssVariables.background.primary};
border-bottom: 1px solid ${themeCssVariables.border.color.light};
box-sizing: border-box;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
flex: 1;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
min-width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.addColumnButtonWidth}px;
`;
const StyledAddColumnIconWrapper = styled.div`
align-items: center;
display: flex;
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
justify-content: center;
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.addColumnButtonWidth}px;
`;
const StyledTableBody = styled.div`
display: flex;
flex-direction: column;
flex-shrink: 0;
`;
const StyledFooterRow = styled.div`
display: flex;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
`;
const StyledFooterCheckboxColumn = styled.div`
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.checkboxColumnWidth}px;
`;
const StyledFooterCell = styled.div<{ width: number }>`
align-items: center;
box-sizing: border-box;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
flex-shrink: 0;
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
justify-content: space-between;
overflow: hidden;
padding: 0 ${themeCssVariables.spacing[2]};
white-space: nowrap;
width: ${({ width }) => width}px;
`;
const StyledFooterLabel = styled.div`
flex: 1;
min-width: 0;
`;
const StyledFooterValue = styled.span`
color: ${themeCssVariables.font.color.primary};
flex-shrink: 0;
`;
const StyledLastEmptyCell = styled.div`
flex: 1;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
min-width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.addColumnButtonWidth}px;
`;
export const BackgroundMockTable = () => {
const { theme } = useContext(ThemeContext);
const { getIcon } = useIcons();
return (
<StyledTableWrapper>
<StyledTable>
<StyledHeaderRow>
<StyledDragHandleColumn />
<StyledCheckboxHeaderColumn>
<Checkbox hoverable checked={false} />
</StyledCheckboxHeaderColumn>
{BACKGROUND_MOCK_COLUMNS.map((column) => {
const Icon = getIcon(column.iconName);
return (
<StyledHeaderCell key={column.label} width={column.width}>
{Icon !== undefined && (
<Icon
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
)}
<StyledHeaderLabel>{column.label}</StyledHeaderLabel>
</StyledHeaderCell>
);
})}
<StyledAddColumnHeaderCell>
<StyledAddColumnIconWrapper>
<IconPlus
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
/>
</StyledAddColumnIconWrapper>
</StyledAddColumnHeaderCell>
</StyledHeaderRow>
<StyledTableBody>
{BACKGROUND_MOCK_COMPANIES.map((company) => (
<BackgroundMockTableRow key={company.id} company={company} />
))}
</StyledTableBody>
<StyledFooterRow>
<StyledDragHandleColumn />
<StyledFooterCheckboxColumn />
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[0].width}>
<StyledFooterLabel>
<OverflowingTextWithTooltip text="Calculate" />
</StyledFooterLabel>
<IconChevronDown
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
/>
</StyledFooterCell>
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[1].width}>
<StyledFooterLabel>
<OverflowingTextWithTooltip text="Count all" />
</StyledFooterLabel>
<StyledFooterValue>599</StyledFooterValue>
</StyledFooterCell>
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[2].width} />
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[3].width} />
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[4].width} />
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[5].width}>
<StyledFooterLabel>
<OverflowingTextWithTooltip text="Max of Employees" />
</StyledFooterLabel>
<StyledFooterValue>284,571</StyledFooterValue>
</StyledFooterCell>
<StyledFooterCell width={BACKGROUND_MOCK_COLUMNS[6].width}>
<StyledFooterLabel>
<OverflowingTextWithTooltip text="Not empty of Address" />
</StyledFooterLabel>
<StyledFooterValue>599</StyledFooterValue>
</StyledFooterCell>
<StyledLastEmptyCell />
</StyledFooterRow>
</StyledTable>
</StyledTableWrapper>
);
};
@@ -0,0 +1,164 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { type BackgroundMockCompany } from '@/sign-in-background-mock/constants/BackgroundMockCompanies';
import { BACKGROUND_MOCK_COLUMN_WIDTHS } from '@/sign-in-background-mock/constants/BackgroundMockColumnWidths';
import { BACKGROUND_MOCK_TABLE_DIMENSIONS } from '@/sign-in-background-mock/constants/BackgroundMockTableDimensions';
import { Avatar, IconLink } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { Chip, ChipAccent, ChipSize, ChipVariant } from 'twenty-ui/components';
import { Checkbox } from 'twenty-ui/input';
import { getLogoUrlFromDomainName } from 'twenty-shared/utils';
const StyledRow = styled.div`
display: flex;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
`;
const StyledDragHandleColumn = styled.div`
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.dragHandleColumnWidth}px;
`;
const StyledCheckboxColumn = styled.div`
align-items: center;
border-bottom: 1px solid ${themeCssVariables.border.color.light};
box-sizing: border-box;
display: flex;
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
justify-content: center;
padding-right: ${themeCssVariables.spacing[1]};
width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.checkboxColumnWidth}px;
`;
const StyledCell = styled.div<{ width: number }>`
align-items: center;
border-bottom: 1px solid ${themeCssVariables.border.color.light};
border-right: 1px solid ${themeCssVariables.border.color.light};
box-sizing: border-box;
color: ${themeCssVariables.font.color.primary};
display: flex;
flex-shrink: 0;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
overflow: hidden;
padding-left: ${themeCssVariables.spacing[2]};
white-space: nowrap;
width: ${({ width }) => width}px;
`;
const StyledTruncated = styled.span`
overflow: hidden;
text-overflow: ellipsis;
`;
const StyledMutedText = styled.span`
color: ${themeCssVariables.font.color.tertiary};
overflow: hidden;
text-overflow: ellipsis;
`;
const StyledLastEmptyCell = styled.div`
border-bottom: 1px solid ${themeCssVariables.border.color.light};
flex: 1;
height: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.rowHeight}px;
min-width: ${BACKGROUND_MOCK_TABLE_DIMENSIONS.addColumnButtonWidth}px;
`;
type BackgroundMockTableRowProps = {
company: BackgroundMockCompany;
};
const formatNumber = (value: number) => value.toLocaleString('en-US');
const PersonChip = ({ fullName }: { fullName: string | null }) => {
if (fullName === null) {
return null;
}
return (
<Chip
label={fullName}
size={ChipSize.Small}
variant={ChipVariant.Transparent}
accent={ChipAccent.TextPrimary}
clickable={false}
leftComponent={
<Avatar
type="rounded"
placeholder={fullName}
placeholderColorSeed={fullName}
size="md"
/>
}
/>
);
};
export const BackgroundMockTableRow = ({
company,
}: BackgroundMockTableRowProps) => {
const { theme } = useContext(ThemeContext);
const logoUrl = getLogoUrlFromDomainName(company.domainName);
return (
<StyledRow>
<StyledDragHandleColumn />
<StyledCheckboxColumn>
<Checkbox hoverable checked={false} />
</StyledCheckboxColumn>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS.Name}>
<Chip
label={company.name}
size={ChipSize.Small}
variant={ChipVariant.Transparent}
accent={ChipAccent.TextPrimary}
clickable={false}
leftComponent={
<Avatar
type="squared"
avatarUrl={logoUrl}
placeholder={company.name}
placeholderColorSeed={company.id}
size="md"
/>
}
/>
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS.Domain}>
<Chip
label={company.domainName}
size={ChipSize.Small}
variant={ChipVariant.Transparent}
accent={ChipAccent.TextSecondary}
clickable={false}
leftComponent={
<IconLink
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
color={theme.font.color.tertiary}
/>
}
/>
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS['Created by']}>
<PersonChip fullName={company.createdBy} />
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS['Account Owner']}>
<PersonChip fullName={company.accountOwner} />
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS['Creation date']}>
<StyledMutedText>{company.creationDate}</StyledMutedText>
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS.Employees}>
<StyledTruncated>{formatNumber(company.employees)}</StyledTruncated>
</StyledCell>
<StyledCell width={BACKGROUND_MOCK_COLUMN_WIDTHS.Address}>
<StyledTruncated>{company.address}</StyledTruncated>
</StyledCell>
<StyledLastEmptyCell />
</StyledRow>
);
};
@@ -0,0 +1,61 @@
import { styled } from '@linaria/react';
import { useContext } from 'react';
import { TopBar } from '@/ui/layout/top-bar/components/TopBar';
import {
IconBuildingSkyscraper,
IconChevronDown,
TintedIconTile,
} from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledViewPicker = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
display: flex;
font-weight: ${themeCssVariables.font.weight.medium};
gap: ${themeCssVariables.spacing[1]};
height: 100%;
padding: 0 ${themeCssVariables.spacing[2]};
`;
const StyledViewPickerCount = styled.span`
color: ${themeCssVariables.font.color.tertiary};
`;
const StyledRightAction = styled.div`
align-items: center;
color: ${themeCssVariables.font.color.tertiary};
display: flex;
font-weight: ${themeCssVariables.font.weight.regular};
height: 100%;
padding: 0 ${themeCssVariables.spacing[2]};
`;
export const BackgroundMockViewBar = () => {
const { theme } = useContext(ThemeContext);
return (
<TopBar
leftComponent={
<StyledViewPicker>
<TintedIconTile Icon={IconBuildingSkyscraper} color="blue" />
<span>All Companies</span>
<StyledViewPickerCount>· 599</StyledViewPickerCount>
<IconChevronDown
size={theme.icon.size.sm}
stroke={theme.icon.stroke.sm}
/>
</StyledViewPicker>
}
rightComponent={
<>
<StyledRightAction>Filter</StyledRightAction>
<StyledRightAction>Sort</StyledRightAction>
<StyledRightAction>Options</StyledRightAction>
</>
}
/>
);
};
@@ -1,113 +0,0 @@
import { styled } from '@linaria/react';
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
export const SignInBackgroundMockContainer = () => {
const objectNamePlural = 'companies';
const objectNameSingular = 'company';
const recordIndexId = 'sign-up-mock-record-table-id';
const viewBarId = 'companies-mock';
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === contextStoreCurrentObjectMetadataItemId,
);
const {
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
} = useRecordIndexFieldMetadataDerivedStates(
objectMetadataItem,
recordIndexId,
);
return (
<StyledContainer>
<RecordIndexContextProvider
value={{
objectPermissionsByObjectMetadataId: {},
recordIndexId,
viewBarInstanceId: recordIndexId,
objectNamePlural,
objectNameSingular,
objectMetadataItem: objectMetadataItem ?? objectMetadataItems[0],
onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '',
fieldDefinitionByFieldMetadataItemId,
fieldMetadataItemByFieldMetadataItemId,
labelIdentifierFieldMetadataItem,
recordFieldByFieldMetadataItemId,
}}
>
<ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
<RecordComponentInstanceContextsWrapper
componentInstanceId={recordIndexId}
>
<ContextStoreComponentInstanceContext.Provider
value={{
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
}}
>
<SignInBackgroundMockContainerEffect
objectNamePlural={objectNamePlural}
recordTableId={recordIndexId}
viewId={viewBarId}
/>
<CommandMenuComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}
>
{isDefined(objectMetadataItem) && (
<>
<ViewBar
viewBarId={viewBarId}
optionsDropdownButton={<></>}
isReadOnly
/>
<RecordTableWithWrappers
objectNameSingular={objectNameSingular}
recordTableId={recordIndexId}
viewBarId={viewBarId}
/>
</>
)}
</CommandMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</RecordComponentInstanceContextsWrapper>
</ViewComponentInstanceContext.Provider>
</RecordIndexContextProvider>
</StyledContainer>
);
};
@@ -1,86 +0,0 @@
import { useEffect } from 'react';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { isDefined } from 'twenty-shared/utils';
import { useInitViewBar } from '@/views/hooks/useInitViewBar';
type SignInBackgroundMockContainerEffectProps = {
objectNamePlural: string;
recordTableId: string;
viewId: string;
};
export const SignInBackgroundMockContainerEffect = ({
objectNamePlural,
recordTableId,
viewId,
}: SignInBackgroundMockContainerEffectProps) => {
const [
contextStoreCurrentObjectMetadataItemId,
setContextStoreCurrentObjectMetadataItemId,
] = useAtomComponentState(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const setCurrentRecordFields = useSetAtomComponentState(
currentRecordFieldsComponentState,
recordTableId,
);
const objectMetadataItem = useAtomFamilySelectorValue(
objectMetadataItemFamilySelector,
{ objectName: objectNamePlural, objectNameType: 'plural' },
);
const { setAvailableFieldDefinitions, setViewObjectMetadataId } =
useInitViewBar(viewId);
useEffect(() => {
if (!isDefined(objectMetadataItem)) {
return;
}
setViewObjectMetadataId?.(objectMetadataItem.id);
setAvailableFieldDefinitions?.(SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS);
const recordFields = SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS.filter(
(fieldDefinition) => fieldDefinition.fieldMetadataId !== '',
).map(
(columnDefinitionMock) =>
({
fieldMetadataItemId: columnDefinitionMock.fieldMetadataId,
id: columnDefinitionMock.fieldMetadataId,
isVisible: columnDefinitionMock.isVisible,
position: columnDefinitionMock.position,
size: columnDefinitionMock.size,
}) satisfies RecordField as RecordField,
);
setCurrentRecordFields(recordFields);
if (contextStoreCurrentObjectMetadataItemId !== objectMetadataItem.id) {
setContextStoreCurrentObjectMetadataItemId(objectMetadataItem.id);
}
}, [
setViewObjectMetadataId,
setAvailableFieldDefinitions,
objectMetadataItem,
recordTableId,
setContextStoreCurrentObjectMetadataItemId,
contextStoreCurrentObjectMetadataItemId,
setCurrentRecordFields,
]);
return <></>;
};
@@ -1,27 +0,0 @@
import { t } from '@lingui/core/macro';
import { styled } from '@linaria/react';
import { SignInBackgroundMockContainer } from '@/sign-in-background-mock/components/SignInBackgroundMockContainer';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { IconBuildingSkyscraper } from 'twenty-ui/display';
const StyledTableContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
export const SignInBackgroundMockPage = () => {
return (
<PageContainer>
<PageHeader title={t`Companies`} Icon={IconBuildingSkyscraper} />
<PageBody>
<StyledTableContainer>
<SignInBackgroundMockContainer />
</StyledTableContainer>
</PageBody>
</PageContainer>
);
};
@@ -0,0 +1,9 @@
export const BACKGROUND_MOCK_COLUMN_WIDTHS = {
Name: 180,
Domain: 130,
'Created by': 130,
'Account Owner': 130,
'Creation date': 130,
Employees: 110,
Address: 200,
} as const;
@@ -0,0 +1,45 @@
import { BACKGROUND_MOCK_COLUMN_WIDTHS } from '@/sign-in-background-mock/constants/BackgroundMockColumnWidths';
export type BackgroundMockColumn = {
label: keyof typeof BACKGROUND_MOCK_COLUMN_WIDTHS;
iconName: string;
width: number;
};
export const BACKGROUND_MOCK_COLUMNS = [
{
label: 'Name',
iconName: 'IconBuildingSkyscraper',
width: BACKGROUND_MOCK_COLUMN_WIDTHS.Name,
},
{
label: 'Domain',
iconName: 'IconLink',
width: BACKGROUND_MOCK_COLUMN_WIDTHS.Domain,
},
{
label: 'Created by',
iconName: 'IconUserCircle',
width: BACKGROUND_MOCK_COLUMN_WIDTHS['Created by'],
},
{
label: 'Account Owner',
iconName: 'IconUserCircle',
width: BACKGROUND_MOCK_COLUMN_WIDTHS['Account Owner'],
},
{
label: 'Creation date',
iconName: 'IconCalendar',
width: BACKGROUND_MOCK_COLUMN_WIDTHS['Creation date'],
},
{
label: 'Employees',
iconName: 'IconUsers',
width: BACKGROUND_MOCK_COLUMN_WIDTHS.Employees,
},
{
label: 'Address',
iconName: 'IconMap',
width: BACKGROUND_MOCK_COLUMN_WIDTHS.Address,
},
] satisfies BackgroundMockColumn[];
@@ -0,0 +1,223 @@
export type BackgroundMockCompany = {
id: string;
name: string;
domainName: string;
createdBy: string | null;
accountOwner: string | null;
creationDate: string;
employees: number;
address: string;
};
export const BACKGROUND_MOCK_COMPANIES = [
{
id: 'google',
name: 'Google',
domainName: 'goo.gle',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 284571,
address: 'Mountain View',
},
{
id: 'microsoft',
name: 'Microsoft',
domainName: 'microsoft.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 226067,
address: 'Redmond',
},
{
id: 'meta',
name: 'Meta',
domainName: 'metacareers.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 119511,
address: 'Menlo Park',
},
{
id: 'slb',
name: 'SLB',
domainName: 'slb.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 113151,
address: 'Houston',
},
{
id: 'cisco',
name: 'Cisco',
domainName: 'cisco.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 99625,
address: 'San Jose',
},
{
id: 'uber',
name: 'Uber',
domainName: 'uber.com',
createdBy: 'Tim Apple',
accountOwner: 'Tim Apple',
creationDate: 'about 13 hours ago',
employees: 90545,
address: 'San Francisco',
},
{
id: 'salesforce',
name: 'Salesforce',
domainName: 'salesforce.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 71322,
address: 'San Francisco',
},
{
id: 'amdocs',
name: 'Amdocs',
domainName: 'amdocs.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 35731,
address: 'Chesterfield',
},
{
id: 'vmware',
name: 'VMware',
domainName: 'vmware.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 34759,
address: 'Palo Alto',
},
{
id: 'globallogic',
name: 'GlobalLogic',
domainName: 'globallogic.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 24461,
address: 'Santa Clara',
},
{
id: 'servicenow',
name: 'ServiceNow',
domainName: 'servicenow.com',
createdBy: 'Tim Apple',
accountOwner: 'Tim Apple',
creationDate: 'about 13 hours ago',
employees: 24104,
address: 'Santa Clara',
},
{
id: 'ssctech',
name: 'SS&C Technologies',
domainName: 'ssctech.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 20311,
address: 'Windsor',
},
{
id: 'workday',
name: 'Workday',
domainName: 'workday.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 20036,
address: 'Pleasanton',
},
{
id: 'redhat',
name: 'Red Hat',
domainName: 'redhat.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 19945,
address: 'Raleigh',
},
{
id: 'netsuite',
name: 'NetSuite',
domainName: 'netsuite.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 19269,
address: 'Austin',
},
{
id: 'synopsys',
name: 'Synopsys Inc',
domainName: 'synopsys.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 18061,
address: 'Sunnyvale',
},
{
id: 'siemens',
name: 'Siemens Digital Industries',
domainName: 'sw.siemens.com',
createdBy: 'Tim Apple',
accountOwner: 'Tim Apple',
creationDate: 'about 13 hours ago',
employees: 17262,
address: 'Plano',
},
{
id: 'sas',
name: 'SAS',
domainName: 'sas.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 16287,
address: 'Cary',
},
{
id: 'intuit',
name: 'Intuit',
domainName: 'intuit.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 15851,
address: 'Mountain View',
},
{
id: 'broadcom',
name: 'Broadcom Software',
domainName: 'broadcom.com',
createdBy: 'Tim Apple',
accountOwner: 'Phil Schiller',
creationDate: 'about 13 hours ago',
employees: 15127,
address: 'San Jose',
},
{
id: 'autodesk',
name: 'Autodesk',
domainName: 'autodesk.com',
createdBy: 'Tim Apple',
accountOwner: 'Jony Ive',
creationDate: 'about 13 hours ago',
employees: 14593,
address: 'San Francisco',
},
] satisfies BackgroundMockCompany[];
@@ -0,0 +1,38 @@
import {
IconBuildingSkyscraper,
IconCalendarEvent,
IconCheckbox,
type IconComponent,
IconFileText,
IconHeart,
IconLayoutDashboard,
IconNotes,
IconRocket,
IconStar,
IconTargetArrow,
IconUser,
IconUserCircle,
} from 'twenty-ui/display';
import { type ThemeColor } from 'twenty-ui/theme';
export type BackgroundMockNavigationItem = {
label: string;
Icon: IconComponent;
color: ThemeColor;
};
export const BACKGROUND_MOCK_WORKSPACE_ITEMS = [
{ label: 'Companies', Icon: IconBuildingSkyscraper, color: 'blue' },
{ label: 'People', Icon: IconUser, color: 'blue' },
{ label: 'Opportunities', Icon: IconTargetArrow, color: 'red' },
{ label: 'Tasks', Icon: IconCheckbox, color: 'turquoise' },
{ label: 'Notes', Icon: IconNotes, color: 'turquoise' },
{ label: 'Dashboards', Icon: IconLayoutDashboard, color: 'orange' },
{ label: 'Workflows', Icon: IconRocket, color: 'pink' },
{ label: 'Rockets', Icon: IconRocket, color: 'sky' },
{ label: 'Pets', Icon: IconHeart, color: 'orange' },
{ label: 'Survey results', Icon: IconStar, color: 'yellow' },
{ label: 'Employment Histories', Icon: IconUserCircle, color: 'green' },
{ label: 'Pet Care Agreements', Icon: IconFileText, color: 'purple' },
{ label: 'Star History', Icon: IconCalendarEvent, color: 'red' },
] satisfies BackgroundMockNavigationItem[];
@@ -0,0 +1,8 @@
import { IconFileText, IconSettings } from 'twenty-ui/display';
import { type BackgroundMockNavigationItem } from '@/sign-in-background-mock/constants/BackgroundMockNavigationItems';
export const BACKGROUND_MOCK_OTHER_ITEMS = [
{ label: 'Settings', Icon: IconSettings, color: 'gray' },
{ label: 'Documentation', Icon: IconFileText, color: 'gray' },
] satisfies BackgroundMockNavigationItem[];
@@ -0,0 +1,6 @@
export const BACKGROUND_MOCK_TABLE_DIMENSIONS = {
rowHeight: 32,
dragHandleColumnWidth: 12,
checkboxColumnWidth: 28,
addColumnButtonWidth: 32,
} as const;
@@ -1,302 +0,0 @@
/* oxlint-disable twenty/max-consts-per-file */
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { findByProperty } from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const COMPANY_MOCK_OBJECT = getMockObjectMetadataItemOrThrow('company');
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
[
{
position: 0,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'name'))?.id ??
'',
label: 'Name',
size: 100,
type: FieldMetadataType.TEXT,
metadata: {
fieldName: 'name',
placeHolder: 'Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBuildingSkyscraper',
isVisible: true,
defaultValue: '',
},
{
position: 1,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'domainName'))
?.id ?? '',
label: 'Domain Name',
size: 100,
type: FieldMetadataType.LINKS,
metadata: {
fieldName: 'domainName',
placeHolder: 'Domain Name',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconLink',
isVisible: true,
defaultValue: '',
},
{
position: 2,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'employees'))
?.id ?? '',
label: 'Employees',
size: 100,
type: FieldMetadataType.NUMBER,
metadata: {
fieldName: 'employees',
placeHolder: 'Employees',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUsers',
isVisible: true,
defaultValue: 0,
},
{
position: 3,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'people'))?.id ??
'',
label: 'People',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'people',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUsers',
isVisible: true,
defaultValue: [],
},
{
position: 4,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'address'))
?.id ?? '',
label: 'Address',
size: 100,
type: FieldMetadataType.ADDRESS,
metadata: {
fieldName: 'address',
placeHolder: 'Address',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconMap',
isVisible: true,
defaultValue: '',
},
{
position: 5,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'accountOwner'))
?.id ?? '',
label: 'Account Owner',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'accountOwner',
relationType: RelationType.MANY_TO_ONE,
relationObjectMetadataNameSingular: 'workspaceMember',
relationObjectMetadataNamePlural: 'workspaceMembers',
objectMetadataNameSingular: 'company',
},
iconName: 'IconUserCircle',
isVisible: true,
defaultValue: null,
},
{
position: 6,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'attachments'))
?.id ?? '',
label: 'Attachments',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'attachments',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconFileImport',
isVisible: true,
defaultValue: [],
},
{
position: 7,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'createdAt'))
?.id ?? '',
label: 'Creation date',
size: 100,
type: FieldMetadataType.DATE_TIME,
metadata: {
fieldName: 'createdAt',
placeHolder: 'Creation date',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconCalendar',
isVisible: true,
defaultValue: '',
},
{
position: 8,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(
findByProperty('name', 'idealCustomerProfile'),
)?.id ?? '',
label: 'ICP',
size: 100,
type: FieldMetadataType.BOOLEAN,
metadata: {
fieldName: 'idealCustomerProfile',
placeHolder: 'ICP',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconTarget',
isVisible: true,
defaultValue: false,
},
{
position: 9,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'linkedinLink'))
?.id ?? '',
label: 'Linkedin',
size: 100,
type: FieldMetadataType.LINKS,
metadata: {
fieldName: 'linkedinLink',
placeHolder: 'Linkedin',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBrandLinkedin',
isVisible: true,
defaultValue: '',
},
{
position: 10,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'opportunities'))
?.id ?? '',
label: 'Opportunities',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'opportunities',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconTargetArrow',
isVisible: true,
defaultValue: [],
},
{
position: 11,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'xLink'))?.id ??
'',
label: 'X',
size: 100,
type: FieldMetadataType.LINKS,
metadata: {
fieldName: 'xLink',
placeHolder: 'X',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconBrandX',
isVisible: true,
defaultValue: '',
},
{
position: 12,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(
findByProperty('name', 'activityTargets'),
)?.id ?? '',
label: 'Activities',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'activityTargets',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconCheckbox',
isVisible: true,
defaultValue: [],
},
{
position: 13,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(
findByProperty('name', 'annualRecurringRevenue'),
)?.id ?? '',
label: 'ARR',
size: 100,
type: FieldMetadataType.CURRENCY,
metadata: {
fieldName: 'annualRecurringRevenue',
placeHolder: 'ARR',
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconMoneybag',
isVisible: true,
defaultValue: 0,
},
{
position: 14,
fieldMetadataId:
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'favorites'))
?.id ?? '',
label: 'Favorites',
size: 100,
type: FieldMetadataType.RELATION,
metadata: {
fieldName: 'favorites',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNameSingular: '',
relationObjectMetadataNamePlural: '',
objectMetadataNameSingular: 'company',
},
iconName: 'IconHeart',
isVisible: true,
defaultValue: [],
},
] satisfies ColumnDefinition<FieldMetadata>[]
).filter(filterAvailableTableColumns);
@@ -1,78 +0,0 @@
import { type ViewField } from '@/views/types/ViewField';
export const SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS = [
{
id: '5168be09-f200-40f5-9e04-29d607de06e5',
fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04',
size: 150,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 4,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: '5ece850b-76fd-4135-9b99-06d49cad14ae',
fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1',
size: 170,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 5,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: '604dbdbb-df01-4e47-921b-f9963109f912',
fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268',
size: 150,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 2,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: '7cbc36c8-37c6-4561-8c46-ddb316ddd121',
fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
size: 150,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 3,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: 'a7d19be3-1ce9-479b-9453-2930a381e07c',
fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889',
size: 100,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 1,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: 'cafacdc8-cbfc-4545-8242-94787f144ace',
fieldMetadataId: 'REPLACE_ME',
size: 180,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 0,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
{
id: 'f0cc50c9-b9b6-405b-a1c0-23f7698ea731',
fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939',
size: 170,
createdAt: '2023-11-23T15:38:03.706Z',
viewId: '20202020-2441-4424-8163-4002c523d415',
position: 6,
isVisible: true,
updatedAt: '2023-11-23T15:38:03.706Z',
},
] as (ViewField & {
createdAt: string;
viewId: string;
updatedAt: string;
})[];
@@ -11,12 +11,12 @@ import { MobileNavigationBar } from '@/navigation/components/MobileNavigationBar
import { PageDragDropProvider } from '@/navigation-menu-item/display/dnd/providers/PageDragDropProvider';
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
import { SignInAppNavigationDrawerMock } from '@/sign-in-background-mock/components/SignInAppNavigationDrawerMock';
import { BackgroundMockNavigationDrawer } from '@/sign-in-background-mock/components/BackgroundMockNavigationDrawer';
import { Suspense, lazy, useContext } from 'react';
const SignInBackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
(module) => ({ default: module.SignInBackgroundMockPage }),
const BackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/BackgroundMockPage').then(
(module) => ({ default: module.BackgroundMockPage }),
),
);
import { useShowFullscreen } from '@/ui/layout/fullscreen/hooks/useShowFullscreen';
@@ -95,7 +95,7 @@ export const DefaultLayout = () => {
{!showAuthModal && <KeyboardShortcutMenu />}
{showAuthModal ? (
<StyledNavigationDrawerWrapper>
<SignInAppNavigationDrawerMock />
<BackgroundMockNavigationDrawer />
</StyledNavigationDrawerWrapper>
) : useShowFullScreen ? null : (
<StyledNavigationDrawerWrapper>
@@ -106,7 +106,7 @@ export const DefaultLayout = () => {
<>
<StyledMainContainer>
<Suspense fallback={null}>
<SignInBackgroundMockPage />
<BackgroundMockPage />
</Suspense>
</StyledMainContainer>
<AnimatePresence mode="wait">
@@ -1,9 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { lazy, Suspense } from 'react';
const SignInBackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
(module) => ({ default: module.SignInBackgroundMockPage }),
const BackgroundMockPage = lazy(() =>
import('@/sign-in-background-mock/components/BackgroundMockPage').then(
(module) => ({ default: module.BackgroundMockPage }),
),
);
import { AppPath } from 'twenty-shared/types';
@@ -69,7 +69,7 @@ export const NotFound = () => {
</AnimatedPlaceholderErrorContainer>
</StyledBackDrop>
<Suspense fallback={null}>
<SignInBackgroundMockPage />
<BackgroundMockPage />
</Suspense>
</>
);
@@ -0,0 +1,31 @@
import { useLoadMockedMetadata } from '@/metadata-store/hooks/useLoadMockedMetadata';
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useEffect } from 'react';
export const MockedMetadataLoadEffect = () => {
const { loadMockedMetadataAtomic } = useLoadMockedMetadata();
const metadataStore = useAtomFamilyStateValue(
metadataStoreState,
'objectMetadataItems',
);
const setIsMinimalMetadataReady = useSetAtomState(
isMinimalMetadataReadyState,
);
useEffect(() => {
void loadMockedMetadataAtomic();
}, [loadMockedMetadataAtomic]);
useEffect(() => {
if (metadataStore.status === 'up-to-date') {
setIsMinimalMetadataReady(true);
} else {
setIsMinimalMetadataReady(false);
}
}, [metadataStore.status, setIsMinimalMetadataReady]);
return null;
};
@@ -16,8 +16,7 @@ import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks_
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
import { MinimalMetadataGater } from '@/metadata-store/components/MinimalMetadataGater';
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
import { IsMinimalMetadataReadyEffect } from '@/metadata-store/effect-components/IsMinimalMetadataReadyEffect';
import { MinimalMetadataLoadEffect } from '@/metadata-store/effect-components/MinimalMetadataLoadEffect';
import { MockedMetadataLoadEffect } from '~/testing/decorators/MockedMetadataLoadEffect';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { useState } from 'react';
import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider';
@@ -87,8 +86,7 @@ const Providers = () => {
<ClientConfigProviderEffect />
<ClientConfigProvider>
<UserMetadataProviderInitialEffect />
<MinimalMetadataLoadEffect />
<IsMinimalMetadataReadyEffect />
<MockedMetadataLoadEffect />
<WorkspaceProviderEffect />
<MinimalMetadataGater>
<ApolloCoreClientMockedProvider>